mirror of https://github.com/apache/poi.git
Bug 54673 - [PATCH] Simple wildcard support in HLOOKUP, VOOLKUP, MATCH, COUNTIF
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1457243 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
0f299e5b5f
commit
4d2be80806
|
@ -43,6 +43,7 @@ import org.apache.poi.ss.usermodel.ErrorConstants;
|
|||
* </p>
|
||||
*
|
||||
* @author Josh Micich
|
||||
* @author Cedric Walter at innoveo.com
|
||||
*/
|
||||
public final class Countif extends Fixed2ArgFunction {
|
||||
|
||||
|
@ -309,7 +310,7 @@ public final class Countif extends Fixed2ArgFunction {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
private static final class StringMatcher extends MatcherBase {
|
||||
public static final class StringMatcher extends MatcherBase {
|
||||
|
||||
private final String _value;
|
||||
private final Pattern _pattern;
|
||||
|
@ -378,19 +379,19 @@ public final class Countif extends Fixed2ArgFunction {
|
|||
* 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) {
|
||||
public 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 '?':
|
||||
case '?': //Any single character
|
||||
hasWildCard = true;
|
||||
// match exactly one character
|
||||
sb.append('.');
|
||||
continue;
|
||||
case '*':
|
||||
case '*': //Zero or more characters
|
||||
hasWildCard = true;
|
||||
// match one or more occurrences of any character
|
||||
sb.append(".*");
|
||||
|
|
|
@ -29,10 +29,14 @@ import org.apache.poi.ss.formula.eval.StringEval;
|
|||
import org.apache.poi.ss.formula.eval.ValueEval;
|
||||
import org.apache.poi.ss.formula.TwoDEval;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Common functionality used by VLOOKUP, HLOOKUP, LOOKUP and MATCH
|
||||
*
|
||||
* @author Josh Micich
|
||||
* @author Cedric Walter at innoveo.com
|
||||
*/
|
||||
final class LookupUtils {
|
||||
|
||||
|
@ -167,6 +171,14 @@ final class LookupUtils {
|
|||
return EQUAL;
|
||||
}
|
||||
|
||||
public static final CompareResult valueOf(boolean matches) {
|
||||
if(matches) {
|
||||
return EQUAL ;
|
||||
}
|
||||
return LESS_THAN;
|
||||
}
|
||||
|
||||
|
||||
public boolean isTypeMismatch() {
|
||||
return _isTypeMismatch;
|
||||
}
|
||||
|
@ -243,16 +255,37 @@ final class LookupUtils {
|
|||
protected abstract String getValueAsString();
|
||||
}
|
||||
|
||||
private static final class StringLookupComparer extends LookupValueComparerBase {
|
||||
private String _value;
|
||||
|
||||
protected StringLookupComparer(StringEval se) {
|
||||
private static final class StringLookupComparer extends LookupValueComparerBase {
|
||||
|
||||
private String _value;
|
||||
private final Pattern _wildCardPattern;
|
||||
private boolean _matchExact;
|
||||
private boolean _isMatchFunction;
|
||||
|
||||
protected StringLookupComparer(StringEval se, boolean matchExact, boolean isMatchFunction) {
|
||||
super(se);
|
||||
_value = se.getStringValue();
|
||||
_wildCardPattern = Countif.StringMatcher.getWildCardPattern(_value);
|
||||
_matchExact = matchExact;
|
||||
_isMatchFunction = isMatchFunction;
|
||||
}
|
||||
|
||||
protected CompareResult compareSameType(ValueEval other) {
|
||||
StringEval se = (StringEval) other;
|
||||
return CompareResult.valueOf(_value.compareToIgnoreCase(se.getStringValue()));
|
||||
StringEval se = (StringEval) other;
|
||||
|
||||
String stringValue = se.getStringValue();
|
||||
if (_wildCardPattern != null) {
|
||||
Matcher matcher = _wildCardPattern.matcher(stringValue);
|
||||
boolean matches = matcher.matches();
|
||||
|
||||
if (_isMatchFunction ||
|
||||
!_matchExact) {
|
||||
return CompareResult.valueOf(matches);
|
||||
}
|
||||
}
|
||||
|
||||
return CompareResult.valueOf(_value.compareToIgnoreCase(stringValue));
|
||||
}
|
||||
protected String getValueAsString() {
|
||||
return _value;
|
||||
|
@ -423,7 +456,7 @@ final class LookupUtils {
|
|||
}
|
||||
|
||||
public static int lookupIndexOfValue(ValueEval lookupValue, ValueVector vector, boolean isRangeLookup) throws EvaluationException {
|
||||
LookupValueComparer lookupComparer = createLookupComparer(lookupValue);
|
||||
LookupValueComparer lookupComparer = createLookupComparer(lookupValue, isRangeLookup, false);
|
||||
int result;
|
||||
if(isRangeLookup) {
|
||||
result = performBinarySearch(vector, lookupComparer);
|
||||
|
@ -439,7 +472,7 @@ final class LookupUtils {
|
|||
|
||||
/**
|
||||
* Finds first (lowest index) exact occurrence of specified value.
|
||||
* @param lookupValue the value to be found in column or row vector
|
||||
* @param lookupComparer the value to be found in column or row vector
|
||||
* @param vector the values to be searched. For VLOOKUP this is the first column of the
|
||||
* tableArray. For HLOOKUP this is the first row of the tableArray.
|
||||
* @return zero based index into the vector, -1 if value cannot be found
|
||||
|
@ -581,7 +614,7 @@ final class LookupUtils {
|
|||
return maxIx - 1;
|
||||
}
|
||||
|
||||
public static LookupValueComparer createLookupComparer(ValueEval lookupValue) {
|
||||
public static LookupValueComparer createLookupComparer(ValueEval lookupValue, boolean matchExact, boolean isMatchFunction) {
|
||||
|
||||
if (lookupValue == BlankEval.instance) {
|
||||
// blank eval translates to zero
|
||||
|
@ -590,7 +623,8 @@ final class LookupUtils {
|
|||
return new NumberLookupComparer(NumberEval.ZERO);
|
||||
}
|
||||
if (lookupValue instanceof StringEval) {
|
||||
return new StringLookupComparer((StringEval) lookupValue);
|
||||
//TODO eventually here return a WildcardStringLookupComparer
|
||||
return new StringLookupComparer((StringEval) lookupValue, matchExact, isMatchFunction);
|
||||
}
|
||||
if (lookupValue instanceof NumberEval) {
|
||||
return new NumberLookupComparer((NumberEval) lookupValue);
|
||||
|
|
|
@ -62,6 +62,7 @@ import org.apache.poi.ss.formula.TwoDEval;
|
|||
*
|
||||
*
|
||||
* @author Josh Micich
|
||||
* @author Cedric Walter at innoveo.com
|
||||
*/
|
||||
public final class Match extends Var2or3ArgFunction {
|
||||
|
||||
|
@ -232,14 +233,7 @@ public final class Match extends Var2or3ArgFunction {
|
|||
}
|
||||
|
||||
private static LookupValueComparer createLookupComparer(ValueEval lookupValue, boolean matchExact) {
|
||||
if (matchExact && lookupValue instanceof StringEval) {
|
||||
String stringValue = ((StringEval) lookupValue).getStringValue();
|
||||
if(isLookupValueWild(stringValue)) {
|
||||
throw new RuntimeException("Wildcard lookup values '" + stringValue + "' not supported yet");
|
||||
}
|
||||
|
||||
}
|
||||
return LookupUtils.createLookupComparer(lookupValue);
|
||||
return LookupUtils.createLookupComparer(lookupValue, matchExact, true);
|
||||
}
|
||||
|
||||
private static boolean isLookupValueWild(String stringValue) {
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.apache.poi.ss.formula.TwoDEval;
|
|||
* the lookup_value. If FALSE, only exact matches will be considered<br/>
|
||||
*
|
||||
* @author Josh Micich
|
||||
* @author Cedric Walter at innoveo.com
|
||||
*/
|
||||
public final class Vlookup extends Var3or4ArgFunction {
|
||||
private static final ValueEval DEFAULT_ARG3 = BoolEval.TRUE;
|
||||
|
@ -47,16 +48,16 @@ public final class Vlookup extends Var3or4ArgFunction {
|
|||
return evaluate(srcRowIndex, srcColumnIndex, arg0, arg1, arg2, DEFAULT_ARG3);
|
||||
}
|
||||
|
||||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1,
|
||||
ValueEval arg2, ValueEval arg3) {
|
||||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval lookup_value, ValueEval table_array,
|
||||
ValueEval col_index, ValueEval range_lookup) {
|
||||
try {
|
||||
// Evaluation order:
|
||||
// arg0 lookup_value, arg1 table_array, arg3 range_lookup, find lookup value, arg2 col_index, fetch result
|
||||
ValueEval lookupValue = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
|
||||
TwoDEval tableArray = LookupUtils.resolveTableArrayArg(arg1);
|
||||
boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcRowIndex, srcColumnIndex);
|
||||
// lookup_value , table_array, range_lookup, find lookup value, col_index, fetch result
|
||||
ValueEval lookupValue = OperandResolver.getSingleValue(lookup_value, srcRowIndex, srcColumnIndex);
|
||||
TwoDEval tableArray = LookupUtils.resolveTableArrayArg(table_array);
|
||||
boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(range_lookup, srcRowIndex, srcColumnIndex);
|
||||
int rowIndex = LookupUtils.lookupIndexOfValue(lookupValue, LookupUtils.createColumnVector(tableArray, 0), isRangeLookup);
|
||||
int colIndex = LookupUtils.resolveRowOrColIndexArg(arg2, srcRowIndex, srcColumnIndex);
|
||||
int colIndex = LookupUtils.resolveRowOrColIndexArg(col_index, srcRowIndex, srcColumnIndex);
|
||||
ValueVector resultCol = createResultColumnVector(tableArray, colIndex);
|
||||
return resultCol.getItem(rowIndex);
|
||||
} catch (EvaluationException e) {
|
||||
|
|
|
@ -0,0 +1,360 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.ss.formula.functions;
|
||||
|
||||
import java.io.PrintStream;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import junit.framework.AssertionFailedError;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.apache.poi.hssf.HSSFTestDataSamples;
|
||||
import org.apache.poi.ss.formula.eval.ErrorEval;
|
||||
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;
|
||||
import org.apache.poi.ss.usermodel.CellValue;
|
||||
|
||||
/**
|
||||
* @author Josh Micich
|
||||
* @author Cedric Walter at innoveo.com
|
||||
*/
|
||||
public abstract class BaseTestFunctionsFromSpreadsheet 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) */
|
||||
|
||||
|
||||
/** Name of the first sheet in the spreadsheet (contains comments) */
|
||||
public final static String README_SHEET_NAME = "Read Me";
|
||||
|
||||
/** Row (zero-based) in each sheet where the evaluation cases start. */
|
||||
public static final int START_TEST_CASES_ROW_INDEX = 4; // Row '5'
|
||||
/** Index of the column that contains the function names */
|
||||
public static final int COLUMN_INDEX_MARKER = 0; // Column 'A'
|
||||
public static final int COLUMN_INDEX_EVALUATION = 1; // Column 'B'
|
||||
public static final int COLUMN_INDEX_EXPECTED_RESULT = 2; // Column 'C'
|
||||
public static final int COLUMN_ROW_COMMENT = 3; // Column 'D'
|
||||
|
||||
/** Used to indicate when there are no more test cases on the current sheet */
|
||||
public static final String TEST_CASES_END_MARKER = "<end>";
|
||||
/** Used to indicate that the test on the current row should be ignored */
|
||||
public static final String SKIP_CURRENT_TEST_CASE_MARKER = "<skip>";
|
||||
|
||||
}
|
||||
|
||||
// Note - multiple failures are aggregated before ending.
|
||||
// If one or more functions fail, a single AssertionFailedError is thrown at the end
|
||||
private int _sheetFailureCount;
|
||||
private int _sheetSuccessCount;
|
||||
private int _evaluationFailureCount;
|
||||
private int _evaluationSuccessCount;
|
||||
|
||||
|
||||
|
||||
private static void confirmExpectedResult(String msg, HSSFCell expected, 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_ERROR) {
|
||||
confirmErrorResult(msg, expected.getErrorCellValue(), actual);
|
||||
return;
|
||||
}
|
||||
if(actual.getCellType() == HSSFCell.CELL_TYPE_ERROR) {
|
||||
throw unexpectedError(msg, expected, actual.getErrorValue());
|
||||
}
|
||||
if(actual.getCellType() != expected.getCellType()) {
|
||||
throw wrongTypeError(msg, expected, actual);
|
||||
}
|
||||
|
||||
|
||||
switch (expected.getCellType()) {
|
||||
case HSSFCell.CELL_TYPE_BOOLEAN:
|
||||
assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue());
|
||||
break;
|
||||
case HSSFCell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation
|
||||
throw new IllegalStateException("Cannot expect formula as result of formula evaluation: " + msg);
|
||||
case HSSFCell.CELL_TYPE_NUMERIC:
|
||||
assertEquals(expected.getNumericCellValue(), actual.getNumberValue(), 0.0);
|
||||
break;
|
||||
case HSSFCell.CELL_TYPE_STRING:
|
||||
assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getStringValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static AssertionFailedError wrongTypeError(String msgPrefix, HSSFCell expectedCell, CellValue actualValue) {
|
||||
return new AssertionFailedError(msgPrefix + " Result type mismatch. Evaluated result was "
|
||||
+ actualValue.formatAsString()
|
||||
+ " but the expected result was "
|
||||
+ formatValue(expectedCell)
|
||||
);
|
||||
}
|
||||
private static AssertionFailedError unexpectedError(String msgPrefix, HSSFCell expected, int actualErrorCode) {
|
||||
return new AssertionFailedError(msgPrefix + " Error code ("
|
||||
+ ErrorEval.getText(actualErrorCode)
|
||||
+ ") was evaluated, but the expected result was "
|
||||
+ formatValue(expected)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private static void confirmErrorResult(String msgPrefix, int expectedErrorCode, CellValue actual) {
|
||||
if(actual.getCellType() != HSSFCell.CELL_TYPE_ERROR) {
|
||||
throw new AssertionFailedError(msgPrefix + " Expected cell error ("
|
||||
+ ErrorEval.getText(expectedErrorCode) + ") but actual value was "
|
||||
+ actual.formatAsString());
|
||||
}
|
||||
if(expectedErrorCode != actual.getErrorValue()) {
|
||||
throw new AssertionFailedError(msgPrefix + " Expected cell error code ("
|
||||
+ ErrorEval.getText(expectedErrorCode)
|
||||
+ ") but actual error code was ("
|
||||
+ ErrorEval.getText(actual.getErrorValue())
|
||||
+ ")");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static String formatValue(HSSFCell expecedCell) {
|
||||
switch (expecedCell.getCellType()) {
|
||||
case HSSFCell.CELL_TYPE_BLANK: return "<blank>";
|
||||
case HSSFCell.CELL_TYPE_BOOLEAN: return String.valueOf(expecedCell.getBooleanCellValue());
|
||||
case HSSFCell.CELL_TYPE_NUMERIC: return String.valueOf(expecedCell.getNumericCellValue());
|
||||
case HSSFCell.CELL_TYPE_STRING: return expecedCell.getRichStringCellValue().getString();
|
||||
}
|
||||
throw new RuntimeException("Unexpected cell type of expected value (" + expecedCell.getCellType() + ")");
|
||||
}
|
||||
|
||||
|
||||
protected void setUp() {
|
||||
_sheetFailureCount = 0;
|
||||
_sheetSuccessCount = 0;
|
||||
_evaluationFailureCount = 0;
|
||||
_evaluationSuccessCount = 0;
|
||||
}
|
||||
|
||||
public void testFunctionsFromTestSpreadsheet() {
|
||||
HSSFWorkbook workbook = HSSFTestDataSamples.openSampleWorkbook(this.getFilename());
|
||||
|
||||
confirmReadMeSheet(workbook);
|
||||
int nSheets = workbook.getNumberOfSheets();
|
||||
for(int i=1; i< nSheets; i++) {
|
||||
int sheetResult = processTestSheet(workbook, i, workbook.getSheetName(i));
|
||||
switch(sheetResult) {
|
||||
case Result.ALL_EVALUATIONS_SUCCEEDED: _sheetSuccessCount ++; break;
|
||||
case Result.SOME_EVALUATIONS_FAILED: _sheetFailureCount ++; break;
|
||||
}
|
||||
}
|
||||
|
||||
// confirm results
|
||||
String successMsg = "There were "
|
||||
+ _sheetSuccessCount + " successful sheets(s) and "
|
||||
+ _evaluationSuccessCount + " function(s) without error";
|
||||
if(_sheetFailureCount > 0) {
|
||||
String msg = _sheetFailureCount + " sheets(s) failed with "
|
||||
+ _evaluationFailureCount + " evaluation(s). " + successMsg;
|
||||
throw new AssertionFailedError(msg);
|
||||
}
|
||||
if(false) { // normally no output for successful tests
|
||||
System.out.println(getClass().getName() + ": " + successMsg);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract String getFilename();
|
||||
|
||||
private int processTestSheet(HSSFWorkbook workbook, int sheetIndex, String sheetName) {
|
||||
HSSFSheet sheet = workbook.getSheetAt(sheetIndex);
|
||||
HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(workbook);
|
||||
int maxRows = sheet.getLastRowNum()+1;
|
||||
int result = Result.NO_EVALUATIONS_FOUND; // so far
|
||||
|
||||
String currentGroupComment = null;
|
||||
for(int rowIndex=SS.START_TEST_CASES_ROW_INDEX; rowIndex<maxRows; rowIndex++) {
|
||||
HSSFRow r = sheet.getRow(rowIndex);
|
||||
String newMarkerValue = getMarkerColumnValue(r);
|
||||
if(r == null) {
|
||||
continue;
|
||||
}
|
||||
if(SS.TEST_CASES_END_MARKER.equalsIgnoreCase(newMarkerValue)) {
|
||||
// normal exit point
|
||||
return result;
|
||||
}
|
||||
if(SS.SKIP_CURRENT_TEST_CASE_MARKER.equalsIgnoreCase(newMarkerValue)) {
|
||||
// currently disabled test case row
|
||||
continue;
|
||||
}
|
||||
if(newMarkerValue != null) {
|
||||
currentGroupComment = newMarkerValue;
|
||||
}
|
||||
HSSFCell c = r.getCell(SS.COLUMN_INDEX_EVALUATION);
|
||||
if (c == null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {
|
||||
continue;
|
||||
}
|
||||
CellValue actualValue = evaluator.evaluate(c);
|
||||
HSSFCell expectedValueCell = r.getCell(SS.COLUMN_INDEX_EXPECTED_RESULT);
|
||||
String rowComment = getRowCommentColumnValue(r);
|
||||
|
||||
String msgPrefix = formatTestCaseDetails(sheetName, r.getRowNum(), c, currentGroupComment, rowComment);
|
||||
try {
|
||||
confirmExpectedResult(msgPrefix, expectedValueCell, actualValue);
|
||||
_evaluationSuccessCount ++;
|
||||
if(result != Result.SOME_EVALUATIONS_FAILED) {
|
||||
result = Result.ALL_EVALUATIONS_SUCCEEDED;
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
_evaluationFailureCount ++;
|
||||
printShortStackTrace(System.err, e);
|
||||
result = Result.SOME_EVALUATIONS_FAILED;
|
||||
} catch (AssertionFailedError e) {
|
||||
_evaluationFailureCount ++;
|
||||
printShortStackTrace(System.err, e);
|
||||
result = Result.SOME_EVALUATIONS_FAILED;
|
||||
}
|
||||
|
||||
}
|
||||
throw new RuntimeException("Missing end marker '" + SS.TEST_CASES_END_MARKER
|
||||
+ "' on sheet '" + sheetName + "'");
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static String formatTestCaseDetails(String sheetName, int rowIndex, HSSFCell c, String currentGroupComment,
|
||||
String rowComment) {
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
CellReference cr = new CellReference(sheetName, rowIndex, c.getColumnIndex(), false, false);
|
||||
sb.append(cr.formatAsString());
|
||||
sb.append(" {=").append(c.getCellFormula()).append("}");
|
||||
|
||||
if(currentGroupComment != null) {
|
||||
sb.append(" '");
|
||||
sb.append(currentGroupComment);
|
||||
if(rowComment != null) {
|
||||
sb.append(" - ");
|
||||
sb.append(rowComment);
|
||||
}
|
||||
sb.append("' ");
|
||||
} else {
|
||||
if(rowComment != null) {
|
||||
sb.append(" '");
|
||||
sb.append(rowComment);
|
||||
sb.append("' ");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the 'read me' comment page exists, and has this class' name in one of the
|
||||
* cells. This back-link is to make it easy to find this class if a reader encounters the
|
||||
* spreadsheet first.
|
||||
*/
|
||||
private void confirmReadMeSheet(HSSFWorkbook workbook) {
|
||||
String firstSheetName = workbook.getSheetName(0);
|
||||
if(!firstSheetName.equalsIgnoreCase(SS.README_SHEET_NAME)) {
|
||||
throw new RuntimeException("First sheet's name was '" + firstSheetName + "' but expected '" + SS.README_SHEET_NAME + "'");
|
||||
}
|
||||
HSSFSheet sheet = workbook.getSheetAt(0);
|
||||
String specifiedClassName = sheet.getRow(2).getCell(0).getRichStringCellValue().getString();
|
||||
assertEquals("Test class name in spreadsheet comment", getClass().getName(), specifiedClassName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Useful to keep output concise when expecting many failures to be reported by this test case
|
||||
*/
|
||||
private static void printShortStackTrace(PrintStream ps, Throwable 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String getRowCommentColumnValue(HSSFRow r) {
|
||||
return getCellTextValue(r, SS.COLUMN_ROW_COMMENT, "row comment");
|
||||
}
|
||||
|
||||
private static String getMarkerColumnValue(HSSFRow r) {
|
||||
return getCellTextValue(r, SS.COLUMN_INDEX_MARKER, "marker");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>null</code> if cell is missing, empty or blank
|
||||
*/
|
||||
private static String getCellTextValue(HSSFRow r, int colIndex, String columnName) {
|
||||
if(r == null) {
|
||||
return null;
|
||||
}
|
||||
HSSFCell cell = r.getCell(colIndex);
|
||||
if(cell == null) {
|
||||
return null;
|
||||
}
|
||||
if(cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) {
|
||||
return null;
|
||||
}
|
||||
if(cell.getCellType() == HSSFCell.CELL_TYPE_STRING) {
|
||||
return cell.getRichStringCellValue().getString();
|
||||
}
|
||||
|
||||
throw new RuntimeException("Bad cell type for '" + columnName + "' column: ("
|
||||
+ cell.getCellType() + ") row (" + (r.getRowNum() +1) + ")");
|
||||
}
|
||||
|
||||
}
|
|
@ -43,6 +43,7 @@ import org.apache.poi.ss.util.CellReference;
|
|||
* Test cases for COUNT(), COUNTA() COUNTIF(), COUNTBLANK()
|
||||
*
|
||||
* @author Josh Micich
|
||||
* @author Cedric Walter at innoveo.com
|
||||
*/
|
||||
public final class TestCountFuncs extends TestCase {
|
||||
|
||||
|
|
|
@ -17,234 +17,16 @@
|
|||
|
||||
package org.apache.poi.ss.formula.functions;
|
||||
|
||||
import java.io.PrintStream;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import junit.framework.AssertionFailedError;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.apache.poi.hssf.HSSFTestDataSamples;
|
||||
import org.apache.poi.ss.formula.eval.ErrorEval;
|
||||
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;
|
||||
import org.apache.poi.ss.usermodel.CellValue;
|
||||
|
||||
/**
|
||||
* Tests INDEX() as loaded from a test data spreadsheet.<p/>
|
||||
*
|
||||
* @author Josh Micich
|
||||
* @author Cedric Walter at innoveo.com
|
||||
*/
|
||||
public final class TestIndexFunctionFromSpreadsheet extends TestCase {
|
||||
public final class TestIndexFunctionFromSpreadsheet extends BaseTestFunctionsFromSpreadsheet {
|
||||
|
||||
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 = "IndexFunctionTestCaseData.xls";
|
||||
|
||||
public static final int COLUMN_INDEX_EVALUATION = 2; // Column 'C'
|
||||
public static final int COLUMN_INDEX_EXPECTED_RESULT = 3; // Column 'D'
|
||||
|
||||
}
|
||||
|
||||
// Note - multiple failures are aggregated before ending.
|
||||
// If one or more functions fail, a single AssertionFailedError is thrown at the end
|
||||
private int _evaluationFailureCount;
|
||||
private int _evaluationSuccessCount;
|
||||
|
||||
|
||||
|
||||
private static void confirmExpectedResult(String msg, HSSFCell expected, 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_ERROR) {
|
||||
confirmErrorResult(msg, expected.getErrorCellValue(), actual);
|
||||
return;
|
||||
}
|
||||
if(actual.getCellType() == HSSFCell.CELL_TYPE_ERROR) {
|
||||
throw unexpectedError(msg, expected, actual.getErrorValue());
|
||||
}
|
||||
if(actual.getCellType() != expected.getCellType()) {
|
||||
throw wrongTypeError(msg, expected, actual);
|
||||
}
|
||||
|
||||
|
||||
switch (expected.getCellType()) {
|
||||
case HSSFCell.CELL_TYPE_BOOLEAN:
|
||||
assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue());
|
||||
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, expected.getNumericCellValue(), actual.getNumberValue(), 0.0);
|
||||
break;
|
||||
case HSSFCell.CELL_TYPE_STRING:
|
||||
assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getStringValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static AssertionFailedError wrongTypeError(String msgPrefix, HSSFCell expectedCell, CellValue actualValue) {
|
||||
return new AssertionFailedError(msgPrefix + " Result type mismatch. Evaluated result was "
|
||||
+ actualValue.formatAsString()
|
||||
+ " but the expected result was "
|
||||
+ formatValue(expectedCell)
|
||||
);
|
||||
}
|
||||
private static AssertionFailedError unexpectedError(String msgPrefix, HSSFCell expected, int actualErrorCode) {
|
||||
return new AssertionFailedError(msgPrefix + " Error code ("
|
||||
+ ErrorEval.getText(actualErrorCode)
|
||||
+ ") was evaluated, but the expected result was "
|
||||
+ formatValue(expected)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private static void confirmErrorResult(String msgPrefix, int expectedErrorCode, CellValue actual) {
|
||||
if(actual.getCellType() != HSSFCell.CELL_TYPE_ERROR) {
|
||||
throw new AssertionFailedError(msgPrefix + " Expected cell error ("
|
||||
+ ErrorEval.getText(expectedErrorCode) + ") but actual value was "
|
||||
+ actual.formatAsString());
|
||||
}
|
||||
if(expectedErrorCode != actual.getErrorValue()) {
|
||||
throw new AssertionFailedError(msgPrefix + " Expected cell error code ("
|
||||
+ ErrorEval.getText(expectedErrorCode)
|
||||
+ ") but actual error code was ("
|
||||
+ ErrorEval.getText(actual.getErrorValue())
|
||||
+ ")");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static String formatValue(HSSFCell expecedCell) {
|
||||
switch (expecedCell.getCellType()) {
|
||||
case HSSFCell.CELL_TYPE_BLANK: return "<blank>";
|
||||
case HSSFCell.CELL_TYPE_BOOLEAN: return String.valueOf(expecedCell.getBooleanCellValue());
|
||||
case HSSFCell.CELL_TYPE_NUMERIC: return String.valueOf(expecedCell.getNumericCellValue());
|
||||
case HSSFCell.CELL_TYPE_STRING: return expecedCell.getRichStringCellValue().getString();
|
||||
}
|
||||
throw new RuntimeException("Unexpected cell type of expected value (" + expecedCell.getCellType() + ")");
|
||||
}
|
||||
|
||||
|
||||
protected void setUp() {
|
||||
_evaluationFailureCount = 0;
|
||||
_evaluationSuccessCount = 0;
|
||||
}
|
||||
|
||||
public void testFunctionsFromTestSpreadsheet() {
|
||||
HSSFWorkbook workbook = HSSFTestDataSamples.openSampleWorkbook(SS.FILENAME);
|
||||
|
||||
processTestSheet(workbook, workbook.getSheetName(0));
|
||||
|
||||
// confirm results
|
||||
String successMsg = "There were "
|
||||
+ _evaluationSuccessCount + " function(s) without error";
|
||||
if(_evaluationFailureCount > 0) {
|
||||
String msg = _evaluationFailureCount + " evaluation(s) failed. " + successMsg;
|
||||
throw new AssertionFailedError(msg);
|
||||
}
|
||||
if(false) { // normally no output for successful tests
|
||||
System.out.println(getClass().getName() + ": " + successMsg);
|
||||
}
|
||||
}
|
||||
|
||||
private void processTestSheet(HSSFWorkbook workbook, String sheetName) {
|
||||
HSSFSheet sheet = workbook.getSheetAt(0);
|
||||
HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(workbook);
|
||||
int maxRows = sheet.getLastRowNum()+1;
|
||||
int result = Result.NO_EVALUATIONS_FOUND; // so far
|
||||
|
||||
for(int rowIndex=0; rowIndex<maxRows; rowIndex++) {
|
||||
HSSFRow r = sheet.getRow(rowIndex);
|
||||
if(r == null) {
|
||||
continue;
|
||||
}
|
||||
HSSFCell c = r.getCell(SS.COLUMN_INDEX_EVALUATION);
|
||||
if (c == null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {
|
||||
continue;
|
||||
}
|
||||
HSSFCell expectedValueCell = r.getCell(SS.COLUMN_INDEX_EXPECTED_RESULT);
|
||||
|
||||
String msgPrefix = formatTestCaseDetails(sheetName, r.getRowNum(), c);
|
||||
try {
|
||||
CellValue actualValue = evaluator.evaluate(c);
|
||||
confirmExpectedResult(msgPrefix, expectedValueCell, actualValue);
|
||||
_evaluationSuccessCount ++;
|
||||
if(result != Result.SOME_EVALUATIONS_FAILED) {
|
||||
result = Result.ALL_EVALUATIONS_SUCCEEDED;
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
_evaluationFailureCount ++;
|
||||
printShortStackTrace(System.err, e, msgPrefix);
|
||||
result = Result.SOME_EVALUATIONS_FAILED;
|
||||
} catch (AssertionFailedError e) {
|
||||
_evaluationFailureCount ++;
|
||||
printShortStackTrace(System.err, e, msgPrefix);
|
||||
result = Result.SOME_EVALUATIONS_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static String formatTestCaseDetails(String sheetName, int rowIndex, HSSFCell c) {
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
CellReference cr = new CellReference(sheetName, rowIndex, c.getColumnIndex(), false, false);
|
||||
sb.append(cr.formatAsString());
|
||||
sb.append(" [formula: ").append(c.getCellFormula()).append(" ]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Useful to keep output concise when expecting many failures to be reported by this test case
|
||||
*/
|
||||
private static void printShortStackTrace(PrintStream ps, Throwable e, String msgPrefix) {
|
||||
System.err.println("Problem with " + msgPrefix);
|
||||
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());
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected String getFilename() {
|
||||
return "IndexFunctionTestCaseData.xls";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,21 +17,7 @@
|
|||
|
||||
package org.apache.poi.ss.formula.functions;
|
||||
|
||||
import java.io.PrintStream;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import junit.framework.AssertionFailedError;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.apache.poi.hssf.HSSFTestDataSamples;
|
||||
import org.apache.poi.ss.formula.eval.ErrorEval;
|
||||
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;
|
||||
import org.apache.poi.ss.usermodel.CellValue;
|
||||
|
||||
/**
|
||||
* Tests lookup functions (VLOOKUP, HLOOKUP, LOOKUP, MATCH) as loaded from a test data spreadsheet.<p/>
|
||||
|
@ -43,323 +29,12 @@ import org.apache.poi.ss.usermodel.CellValue;
|
|||
* more easily.
|
||||
*
|
||||
* @author Josh Micich
|
||||
* @author Cedric Walter at innoveo.com
|
||||
*/
|
||||
public final class TestLookupFunctionsFromSpreadsheet extends TestCase {
|
||||
public final class TestLookupFunctionsFromSpreadsheet extends BaseTestFunctionsFromSpreadsheet {
|
||||
|
||||
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 = "LookupFunctionsTestCaseData.xls";
|
||||
|
||||
/** Name of the first sheet in the spreadsheet (contains comments) */
|
||||
public final static String README_SHEET_NAME = "Read Me";
|
||||
|
||||
|
||||
/** Row (zero-based) in each sheet where the evaluation cases start. */
|
||||
public static final int START_TEST_CASES_ROW_INDEX = 4; // Row '5'
|
||||
/** Index of the column that contains the function names */
|
||||
public static final int COLUMN_INDEX_MARKER = 0; // Column 'A'
|
||||
public static final int COLUMN_INDEX_EVALUATION = 1; // Column 'B'
|
||||
public static final int COLUMN_INDEX_EXPECTED_RESULT = 2; // Column 'C'
|
||||
public static final int COLUMN_ROW_COMMENT = 3; // Column 'D'
|
||||
|
||||
/** Used to indicate when there are no more test cases on the current sheet */
|
||||
public static final String TEST_CASES_END_MARKER = "<end>";
|
||||
/** Used to indicate that the test on the current row should be ignored */
|
||||
public static final String SKIP_CURRENT_TEST_CASE_MARKER = "<skip>";
|
||||
|
||||
}
|
||||
|
||||
// Note - multiple failures are aggregated before ending.
|
||||
// If one or more functions fail, a single AssertionFailedError is thrown at the end
|
||||
private int _sheetFailureCount;
|
||||
private int _sheetSuccessCount;
|
||||
private int _evaluationFailureCount;
|
||||
private int _evaluationSuccessCount;
|
||||
|
||||
|
||||
|
||||
private static void confirmExpectedResult(String msg, HSSFCell expected, 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_ERROR) {
|
||||
confirmErrorResult(msg, expected.getErrorCellValue(), actual);
|
||||
return;
|
||||
}
|
||||
if(actual.getCellType() == HSSFCell.CELL_TYPE_ERROR) {
|
||||
throw unexpectedError(msg, expected, actual.getErrorValue());
|
||||
}
|
||||
if(actual.getCellType() != expected.getCellType()) {
|
||||
throw wrongTypeError(msg, expected, actual);
|
||||
}
|
||||
|
||||
|
||||
switch (expected.getCellType()) {
|
||||
case HSSFCell.CELL_TYPE_BOOLEAN:
|
||||
assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue());
|
||||
break;
|
||||
case HSSFCell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation
|
||||
throw new IllegalStateException("Cannot expect formula as result of formula evaluation: " + msg);
|
||||
case HSSFCell.CELL_TYPE_NUMERIC:
|
||||
assertEquals(expected.getNumericCellValue(), actual.getNumberValue(), 0.0);
|
||||
break;
|
||||
case HSSFCell.CELL_TYPE_STRING:
|
||||
assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getStringValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static AssertionFailedError wrongTypeError(String msgPrefix, HSSFCell expectedCell, CellValue actualValue) {
|
||||
return new AssertionFailedError(msgPrefix + " Result type mismatch. Evaluated result was "
|
||||
+ actualValue.formatAsString()
|
||||
+ " but the expected result was "
|
||||
+ formatValue(expectedCell)
|
||||
);
|
||||
}
|
||||
private static AssertionFailedError unexpectedError(String msgPrefix, HSSFCell expected, int actualErrorCode) {
|
||||
return new AssertionFailedError(msgPrefix + " Error code ("
|
||||
+ ErrorEval.getText(actualErrorCode)
|
||||
+ ") was evaluated, but the expected result was "
|
||||
+ formatValue(expected)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private static void confirmErrorResult(String msgPrefix, int expectedErrorCode, CellValue actual) {
|
||||
if(actual.getCellType() != HSSFCell.CELL_TYPE_ERROR) {
|
||||
throw new AssertionFailedError(msgPrefix + " Expected cell error ("
|
||||
+ ErrorEval.getText(expectedErrorCode) + ") but actual value was "
|
||||
+ actual.formatAsString());
|
||||
}
|
||||
if(expectedErrorCode != actual.getErrorValue()) {
|
||||
throw new AssertionFailedError(msgPrefix + " Expected cell error code ("
|
||||
+ ErrorEval.getText(expectedErrorCode)
|
||||
+ ") but actual error code was ("
|
||||
+ ErrorEval.getText(actual.getErrorValue())
|
||||
+ ")");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static String formatValue(HSSFCell expecedCell) {
|
||||
switch (expecedCell.getCellType()) {
|
||||
case HSSFCell.CELL_TYPE_BLANK: return "<blank>";
|
||||
case HSSFCell.CELL_TYPE_BOOLEAN: return String.valueOf(expecedCell.getBooleanCellValue());
|
||||
case HSSFCell.CELL_TYPE_NUMERIC: return String.valueOf(expecedCell.getNumericCellValue());
|
||||
case HSSFCell.CELL_TYPE_STRING: return expecedCell.getRichStringCellValue().getString();
|
||||
}
|
||||
throw new RuntimeException("Unexpected cell type of expected value (" + expecedCell.getCellType() + ")");
|
||||
}
|
||||
|
||||
|
||||
protected void setUp() {
|
||||
_sheetFailureCount = 0;
|
||||
_sheetSuccessCount = 0;
|
||||
_evaluationFailureCount = 0;
|
||||
_evaluationSuccessCount = 0;
|
||||
}
|
||||
|
||||
public void testFunctionsFromTestSpreadsheet() {
|
||||
HSSFWorkbook workbook = HSSFTestDataSamples.openSampleWorkbook(SS.FILENAME);
|
||||
|
||||
confirmReadMeSheet(workbook);
|
||||
int nSheets = workbook.getNumberOfSheets();
|
||||
for(int i=1; i< nSheets; i++) {
|
||||
int sheetResult = processTestSheet(workbook, i, workbook.getSheetName(i));
|
||||
switch(sheetResult) {
|
||||
case Result.ALL_EVALUATIONS_SUCCEEDED: _sheetSuccessCount ++; break;
|
||||
case Result.SOME_EVALUATIONS_FAILED: _sheetFailureCount ++; break;
|
||||
}
|
||||
}
|
||||
|
||||
// confirm results
|
||||
String successMsg = "There were "
|
||||
+ _sheetSuccessCount + " successful sheets(s) and "
|
||||
+ _evaluationSuccessCount + " function(s) without error";
|
||||
if(_sheetFailureCount > 0) {
|
||||
String msg = _sheetFailureCount + " sheets(s) failed with "
|
||||
+ _evaluationFailureCount + " evaluation(s). " + successMsg;
|
||||
throw new AssertionFailedError(msg);
|
||||
}
|
||||
if(false) { // normally no output for successful tests
|
||||
System.out.println(getClass().getName() + ": " + successMsg);
|
||||
}
|
||||
}
|
||||
|
||||
private int processTestSheet(HSSFWorkbook workbook, int sheetIndex, String sheetName) {
|
||||
HSSFSheet sheet = workbook.getSheetAt(sheetIndex);
|
||||
HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(workbook);
|
||||
int maxRows = sheet.getLastRowNum()+1;
|
||||
int result = Result.NO_EVALUATIONS_FOUND; // so far
|
||||
|
||||
String currentGroupComment = null;
|
||||
for(int rowIndex=SS.START_TEST_CASES_ROW_INDEX; rowIndex<maxRows; rowIndex++) {
|
||||
HSSFRow r = sheet.getRow(rowIndex);
|
||||
String newMarkerValue = getMarkerColumnValue(r);
|
||||
if(r == null) {
|
||||
continue;
|
||||
}
|
||||
if(SS.TEST_CASES_END_MARKER.equalsIgnoreCase(newMarkerValue)) {
|
||||
// normal exit point
|
||||
return result;
|
||||
}
|
||||
if(SS.SKIP_CURRENT_TEST_CASE_MARKER.equalsIgnoreCase(newMarkerValue)) {
|
||||
// currently disabled test case row
|
||||
continue;
|
||||
}
|
||||
if(newMarkerValue != null) {
|
||||
currentGroupComment = newMarkerValue;
|
||||
}
|
||||
HSSFCell c = r.getCell(SS.COLUMN_INDEX_EVALUATION);
|
||||
if (c == null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {
|
||||
continue;
|
||||
}
|
||||
CellValue actualValue = evaluator.evaluate(c);
|
||||
HSSFCell expectedValueCell = r.getCell(SS.COLUMN_INDEX_EXPECTED_RESULT);
|
||||
String rowComment = getRowCommentColumnValue(r);
|
||||
|
||||
String msgPrefix = formatTestCaseDetails(sheetName, r.getRowNum(), c, currentGroupComment, rowComment);
|
||||
try {
|
||||
confirmExpectedResult(msgPrefix, expectedValueCell, actualValue);
|
||||
_evaluationSuccessCount ++;
|
||||
if(result != Result.SOME_EVALUATIONS_FAILED) {
|
||||
result = Result.ALL_EVALUATIONS_SUCCEEDED;
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
_evaluationFailureCount ++;
|
||||
printShortStackTrace(System.err, e);
|
||||
result = Result.SOME_EVALUATIONS_FAILED;
|
||||
} catch (AssertionFailedError e) {
|
||||
_evaluationFailureCount ++;
|
||||
printShortStackTrace(System.err, e);
|
||||
result = Result.SOME_EVALUATIONS_FAILED;
|
||||
}
|
||||
|
||||
}
|
||||
throw new RuntimeException("Missing end marker '" + SS.TEST_CASES_END_MARKER
|
||||
+ "' on sheet '" + sheetName + "'");
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static String formatTestCaseDetails(String sheetName, int rowIndex, HSSFCell c, String currentGroupComment,
|
||||
String rowComment) {
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
CellReference cr = new CellReference(sheetName, rowIndex, c.getColumnIndex(), false, false);
|
||||
sb.append(cr.formatAsString());
|
||||
sb.append(" {=").append(c.getCellFormula()).append("}");
|
||||
|
||||
if(currentGroupComment != null) {
|
||||
sb.append(" '");
|
||||
sb.append(currentGroupComment);
|
||||
if(rowComment != null) {
|
||||
sb.append(" - ");
|
||||
sb.append(rowComment);
|
||||
}
|
||||
sb.append("' ");
|
||||
} else {
|
||||
if(rowComment != null) {
|
||||
sb.append(" '");
|
||||
sb.append(rowComment);
|
||||
sb.append("' ");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the 'read me' comment page exists, and has this class' name in one of the
|
||||
* cells. This back-link is to make it easy to find this class if a reader encounters the
|
||||
* spreadsheet first.
|
||||
*/
|
||||
private void confirmReadMeSheet(HSSFWorkbook workbook) {
|
||||
String firstSheetName = workbook.getSheetName(0);
|
||||
if(!firstSheetName.equalsIgnoreCase(SS.README_SHEET_NAME)) {
|
||||
throw new RuntimeException("First sheet's name was '" + firstSheetName + "' but expected '" + SS.README_SHEET_NAME + "'");
|
||||
}
|
||||
HSSFSheet sheet = workbook.getSheetAt(0);
|
||||
String specifiedClassName = sheet.getRow(2).getCell(0).getRichStringCellValue().getString();
|
||||
assertEquals("Test class name in spreadsheet comment", getClass().getName(), specifiedClassName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Useful to keep output concise when expecting many failures to be reported by this test case
|
||||
*/
|
||||
private static void printShortStackTrace(PrintStream ps, Throwable 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String getRowCommentColumnValue(HSSFRow r) {
|
||||
return getCellTextValue(r, SS.COLUMN_ROW_COMMENT, "row comment");
|
||||
}
|
||||
|
||||
private static String getMarkerColumnValue(HSSFRow r) {
|
||||
return getCellTextValue(r, SS.COLUMN_INDEX_MARKER, "marker");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>null</code> if cell is missing, empty or blank
|
||||
*/
|
||||
private static String getCellTextValue(HSSFRow r, int colIndex, String columnName) {
|
||||
if(r == null) {
|
||||
return null;
|
||||
}
|
||||
HSSFCell cell = r.getCell(colIndex);
|
||||
if(cell == null) {
|
||||
return null;
|
||||
}
|
||||
if(cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) {
|
||||
return null;
|
||||
}
|
||||
if(cell.getCellType() == HSSFCell.CELL_TYPE_STRING) {
|
||||
return cell.getRichStringCellValue().getString();
|
||||
}
|
||||
|
||||
throw new RuntimeException("Bad cell type for '" + columnName + "' column: ("
|
||||
+ cell.getCellType() + ") row (" + (r.getRowNum() +1) + ")");
|
||||
}
|
||||
@Override
|
||||
protected String getFilename() {
|
||||
return "LookupFunctionsTestCaseData.xls";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ package org.apache.poi.ss.formula.functions;
|
|||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.apache.poi.hssf.HSSFTestDataSamples;
|
||||
import org.apache.poi.hssf.usermodel.*;
|
||||
import org.apache.poi.ss.formula.eval.AreaEval;
|
||||
import org.apache.poi.ss.formula.eval.BoolEval;
|
||||
import org.apache.poi.ss.formula.eval.ErrorEval;
|
||||
|
@ -26,11 +28,13 @@ import org.apache.poi.ss.formula.eval.NumberEval;
|
|||
import org.apache.poi.ss.formula.eval.NumericValueEval;
|
||||
import org.apache.poi.ss.formula.eval.StringEval;
|
||||
import org.apache.poi.ss.formula.eval.ValueEval;
|
||||
import org.apache.poi.ss.usermodel.CellValue;
|
||||
|
||||
/**
|
||||
* Test cases for MATCH()
|
||||
*
|
||||
* @author Josh Micich
|
||||
* @author Cedric Walter at innoveo.com
|
||||
*/
|
||||
public final class TestMatch extends TestCase {
|
||||
/** less than or equal to */
|
||||
|
@ -93,7 +97,7 @@ public final class TestMatch extends TestCase {
|
|||
}
|
||||
|
||||
public void testSimpleString() {
|
||||
|
||||
// Arrange
|
||||
ValueEval[] values = {
|
||||
new StringEval("Albert"),
|
||||
new StringEval("Charles"),
|
||||
|
@ -109,10 +113,52 @@ public final class TestMatch extends TestCase {
|
|||
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 testSimpleWildcardValuesString() {
|
||||
// Arrange
|
||||
ValueEval[] values = {
|
||||
new StringEval("Albert"),
|
||||
new StringEval("Charles"),
|
||||
new StringEval("Ed"),
|
||||
new StringEval("Greg"),
|
||||
new StringEval("Ian"),
|
||||
};
|
||||
|
||||
AreaEval ae = EvalFactory.createAreaEval("A1:A5", values);
|
||||
|
||||
// Note String comparisons are case insensitive
|
||||
confirmInt(3, invokeMatch(new StringEval("e*"), ae, MATCH_EXACT));
|
||||
confirmInt(3, invokeMatch(new StringEval("*d"), ae, MATCH_EXACT));
|
||||
|
||||
confirmInt(1, invokeMatch(new StringEval("Al*"), ae, MATCH_EXACT));
|
||||
confirmInt(2, invokeMatch(new StringEval("Char*"), ae, MATCH_EXACT));
|
||||
|
||||
confirmInt(4, invokeMatch(new StringEval("*eg"), ae, MATCH_EXACT));
|
||||
confirmInt(4, invokeMatch(new StringEval("G?eg"), ae, MATCH_EXACT));
|
||||
confirmInt(4, invokeMatch(new StringEval("??eg"), ae, MATCH_EXACT));
|
||||
confirmInt(4, invokeMatch(new StringEval("G*?eg"), ae, MATCH_EXACT));
|
||||
confirmInt(4, invokeMatch(new StringEval("Hugh"), ae, MATCH_LARGEST_LTE));
|
||||
|
||||
confirmInt(5, invokeMatch(new StringEval("*Ian*"), ae, MATCH_EXACT));
|
||||
confirmInt(5, invokeMatch(new StringEval("*Ian*"), ae, MATCH_LARGEST_LTE));
|
||||
}
|
||||
|
||||
public void testTildeString() {
|
||||
|
||||
ValueEval[] values = {
|
||||
new StringEval("what?"),
|
||||
new StringEval("all*")
|
||||
};
|
||||
|
||||
AreaEval ae = EvalFactory.createAreaEval("A1:A2", values);
|
||||
|
||||
confirmInt(1, invokeMatch(new StringEval("what~?"), ae, MATCH_EXACT));
|
||||
confirmInt(2, invokeMatch(new StringEval("all~*"), ae, MATCH_EXACT));
|
||||
}
|
||||
|
||||
public void testSimpleBoolean() {
|
||||
|
||||
ValueEval[] values = {
|
||||
|
@ -159,11 +205,17 @@ public final class TestMatch extends TestCase {
|
|||
confirmInt(3, invokeMatch(new NumberEval(5), ae, MATCH_EXACT));
|
||||
|
||||
confirmInt(8, invokeMatch(new StringEval("CHARLES"), ae, MATCH_EXACT));
|
||||
//wildcard values
|
||||
confirmInt(8, invokeMatch(new StringEval("CHAR*"), 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(13, invokeMatch(new StringEval("ED*"), ae, MATCH_LARGEST_LTE));
|
||||
confirmInt(13, invokeMatch(new StringEval("*ED"), ae, MATCH_LARGEST_LTE));
|
||||
confirmInt(9, invokeMatch(new StringEval("ED"), ae, MATCH_EXACT));
|
||||
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));
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.ss.formula.functions;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Tests lookup functions (VLOOKUP, HLOOKUP, LOOKUP, MATCH) as loaded from a test data spreadsheet.<p/>
|
||||
* These tests have been separated from the common function and operator tests because the lookup
|
||||
* functions have more complex test cases and test data setup.
|
||||
*
|
||||
* 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 Josh Micich
|
||||
* @author Cedric Walter at innoveo.com
|
||||
*/
|
||||
public final class TestMatchFunctionsFromSpreadsheet extends BaseTestFunctionsFromSpreadsheet {
|
||||
|
||||
@Override
|
||||
protected String getFilename() {
|
||||
return "MatchFunctionTestCaseData.xls";
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue