Patches from Patrick Zimmermann from bugs #60130 and #60131 - DGET fix for empty cells and D* coding improvements

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1760717 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2016-09-14 14:59:00 +00:00
parent 1a210230dc
commit 2f0230acd1
3 changed files with 67 additions and 94 deletions

View File

@ -17,7 +17,10 @@
package org.apache.poi.ss.formula.functions; package org.apache.poi.ss.formula.functions;
import org.apache.poi.ss.formula.eval.BlankEval;
import org.apache.poi.ss.formula.eval.ErrorEval; import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException;
import org.apache.poi.ss.formula.eval.OperandResolver;
import org.apache.poi.ss.formula.eval.ValueEval; import org.apache.poi.ss.formula.eval.ValueEval;
/** /**
@ -46,8 +49,18 @@ public final class DGet implements IDStarAlgorithm {
public ValueEval getResult() { public ValueEval getResult() {
if(result == null) { if(result == null) {
return ErrorEval.VALUE_INVALID; return ErrorEval.VALUE_INVALID;
} else { } else if(result instanceof BlankEval) {
return result; return ErrorEval.VALUE_INVALID;
} } else
try {
if(OperandResolver.coerceValueToString(OperandResolver.getSingleValue(result, 0, 0)).equals("")) {
return ErrorEval.VALUE_INVALID;
}
else {
return result;
}
} catch (EvaluationException e) {
return e.getErrorEval();
}
} }
} }

View File

@ -17,13 +17,13 @@
package org.apache.poi.ss.formula.functions; package org.apache.poi.ss.formula.functions;
import org.apache.poi.ss.formula.TwoDEval; import org.apache.poi.ss.formula.eval.AreaEval;
import org.apache.poi.ss.formula.eval.BlankEval; import org.apache.poi.ss.formula.eval.BlankEval;
import org.apache.poi.ss.formula.eval.ErrorEval; import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException; import org.apache.poi.ss.formula.eval.EvaluationException;
import org.apache.poi.ss.formula.eval.NotImplementedException; import org.apache.poi.ss.formula.eval.NotImplementedException;
import org.apache.poi.ss.formula.eval.NumericValueEval; import org.apache.poi.ss.formula.eval.NumericValueEval;
import org.apache.poi.ss.formula.eval.RefEval; import org.apache.poi.ss.formula.eval.OperandResolver;
import org.apache.poi.ss.formula.eval.StringEval; import org.apache.poi.ss.formula.eval.StringEval;
import org.apache.poi.ss.formula.eval.StringValueEval; import org.apache.poi.ss.formula.eval.StringValueEval;
import org.apache.poi.ss.formula.eval.ValueEval; import org.apache.poi.ss.formula.eval.ValueEval;
@ -62,11 +62,17 @@ public final class DStarRunner implements Function3Arg {
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, public ValueEval evaluate(int srcRowIndex, int srcColumnIndex,
ValueEval database, ValueEval filterColumn, ValueEval conditionDatabase) { ValueEval database, ValueEval filterColumn, ValueEval conditionDatabase) {
// Input processing and error checks. // Input processing and error checks.
if(!(database instanceof TwoDEval) || !(conditionDatabase instanceof TwoDEval)) { if(!(database instanceof AreaEval) || !(conditionDatabase instanceof AreaEval)) {
return ErrorEval.VALUE_INVALID; return ErrorEval.VALUE_INVALID;
} }
TwoDEval db = (TwoDEval)database; AreaEval db = (AreaEval)database;
TwoDEval cdb = (TwoDEval)conditionDatabase; AreaEval cdb = (AreaEval)conditionDatabase;
try {
filterColumn = OperandResolver.getSingleValue(filterColumn, srcRowIndex, srcColumnIndex);
} catch (EvaluationException e) {
return e.getErrorEval();
}
int fc; int fc;
try { try {
@ -100,15 +106,11 @@ public final class DStarRunner implements Function3Arg {
} }
// Filter each entry. // Filter each entry.
if(matches) { if(matches) {
try { ValueEval currentValueEval = resolveReference(db, row, fc);
ValueEval currentValueEval = solveReference(db.getValue(row, fc)); // Pass the match to the algorithm and conditionally abort the search.
// Pass the match to the algorithm and conditionally abort the search. boolean shouldContinue = algorithm.processMatch(currentValueEval);
boolean shouldContinue = algorithm.processMatch(currentValueEval); if(! shouldContinue) {
if(! shouldContinue) { break;
break;
}
} catch (EvaluationException e) {
return e.getErrorEval();
} }
} }
} }
@ -126,56 +128,16 @@ public final class DStarRunner implements Function3Arg {
} }
/** /**
* Resolve reference(-chains) until we have a normal value.
* *
* @param field a ValueEval which can be a RefEval. *
* @return a ValueEval which is guaranteed not to be a RefEval * @param nameValueEval Must not be a RefEval or AreaEval. Thus make sure resolveReference() is called on the value first!
* @throws EvaluationException If a multi-sheet reference was found along the way.
*/
private static ValueEval solveReference(ValueEval field) throws EvaluationException {
if (field instanceof RefEval) {
RefEval refEval = (RefEval)field;
if (refEval.getNumberOfSheets() > 1) {
throw new EvaluationException(ErrorEval.VALUE_INVALID);
}
return solveReference(refEval.getInnerValueEval(refEval.getFirstSheetIndex()));
}
else {
return field;
}
}
/**
* Returns the first column index that matches the given name. The name can either be
* a string or an integer, when it's an integer, then the respective column
* (1 based index) is returned.
* @param nameValueEval
* @param db * @param db
* @return the first column index that matches the given name (or int) * @return
* @throws EvaluationException * @throws EvaluationException
*/ */
@SuppressWarnings("unused") private static int getColumnForName(ValueEval nameValueEval, AreaEval db)
private static int getColumnForTag(ValueEval nameValueEval, TwoDEval db)
throws EvaluationException { throws EvaluationException {
int resultColumn = -1; String name = OperandResolver.coerceValueToString(nameValueEval);
// Numbers as column indicator are allowed, check that.
if(nameValueEval instanceof NumericValueEval) {
double doubleResultColumn = ((NumericValueEval)nameValueEval).getNumberValue();
resultColumn = (int)doubleResultColumn;
// Floating comparisions are usually not possible, but should work for 0.0.
if(doubleResultColumn - resultColumn != 0.0)
throw new EvaluationException(ErrorEval.VALUE_INVALID);
resultColumn -= 1; // Numbers are 1-based not 0-based.
} else {
resultColumn = getColumnForName(nameValueEval, db);
}
return resultColumn;
}
private static int getColumnForName(ValueEval nameValueEval, TwoDEval db)
throws EvaluationException {
String name = getStringFromValueEval(nameValueEval);
return getColumnForString(db, name); return getColumnForString(db, name);
} }
@ -187,16 +149,19 @@ public final class DStarRunner implements Function3Arg {
* @return Corresponding column number. * @return Corresponding column number.
* @throws EvaluationException If it's not possible to turn all headings into strings. * @throws EvaluationException If it's not possible to turn all headings into strings.
*/ */
private static int getColumnForString(TwoDEval db,String name) private static int getColumnForString(AreaEval db,String name)
throws EvaluationException { throws EvaluationException {
int resultColumn = -1; int resultColumn = -1;
final int width = db.getWidth(); final int width = db.getWidth();
for(int column = 0; column < width; ++column) { for(int column = 0; column < width; ++column) {
ValueEval columnNameValueEval = db.getValue(0, column); ValueEval columnNameValueEval = resolveReference(db, 0, column);
if(solveReference(columnNameValueEval) instanceof BlankEval) { if(columnNameValueEval instanceof BlankEval) {
continue; continue;
} }
String columnName = getStringFromValueEval(columnNameValueEval); if(columnNameValueEval instanceof ErrorEval) {
continue;
}
String columnName = OperandResolver.coerceValueToString(columnNameValueEval);
if(name.equals(columnName)) { if(name.equals(columnName)) {
resultColumn = column; resultColumn = column;
break; break;
@ -215,7 +180,7 @@ public final class DStarRunner implements Function3Arg {
* @throws EvaluationException If references could not be resolved or comparison * @throws EvaluationException If references could not be resolved or comparison
* operators and operands didn't match. * operators and operands didn't match.
*/ */
private static boolean fullfillsConditions(TwoDEval db, int row, TwoDEval cdb) private static boolean fullfillsConditions(AreaEval db, int row, AreaEval cdb)
throws EvaluationException { throws EvaluationException {
// Only one row must match to accept the input, so rows are ORed. // Only one row must match to accept the input, so rows are ORed.
// Each row is made up of cells where each cell is a condition, // Each row is made up of cells where each cell is a condition,
@ -229,20 +194,15 @@ public final class DStarRunner implements Function3Arg {
// special column that accepts formulas. // special column that accepts formulas.
boolean columnCondition = true; boolean columnCondition = true;
ValueEval condition = null; ValueEval condition = null;
try {
// The condition to apply. // The condition to apply.
condition = solveReference(cdb.getValue(conditionRow, column)); condition = resolveReference(cdb, conditionRow, column);
} catch (java.lang.RuntimeException e) {
// It might be a special formula, then it is ok if it fails.
columnCondition = false;
}
// If the condition is empty it matches. // If the condition is empty it matches.
if(condition instanceof BlankEval) if(condition instanceof BlankEval)
continue; continue;
// The column in the DB to apply the condition to. // The column in the DB to apply the condition to.
ValueEval targetHeader = solveReference(cdb.getValue(0, column)); ValueEval targetHeader = resolveReference(cdb, 0, column);
targetHeader = solveReference(targetHeader);
if(!(targetHeader instanceof StringValueEval)) { if(!(targetHeader instanceof StringValueEval)) {
throw new EvaluationException(ErrorEval.VALUE_INVALID); throw new EvaluationException(ErrorEval.VALUE_INVALID);
@ -254,14 +214,14 @@ public final class DStarRunner implements Function3Arg {
if(columnCondition == true) { // normal column condition if(columnCondition == true) { // normal column condition
// Should not throw, checked above. // Should not throw, checked above.
ValueEval value = db.getValue( ValueEval value = resolveReference(db, row, getColumnForName(targetHeader, db));
row, getColumnForName(targetHeader, db));
if(!testNormalCondition(value, condition)) { if(!testNormalCondition(value, condition)) {
matches = false; matches = false;
break; break;
} }
} else { // It's a special formula condition. } else { // It's a special formula condition.
if(getStringFromValueEval(condition).isEmpty()) { // TODO: Check whether the condition cell contains a formula and return #VALUE! if it doesn't.
if(OperandResolver.coerceValueToString(condition).isEmpty()) {
throw new EvaluationException(ErrorEval.VALUE_INVALID); throw new EvaluationException(ErrorEval.VALUE_INVALID);
} }
throw new NotImplementedException( throw new NotImplementedException(
@ -328,7 +288,7 @@ public final class DStarRunner implements Function3Arg {
if(itsANumber) { if(itsANumber) {
return testNumericCondition(value, operator.equal, stringOrNumber); return testNumericCondition(value, operator.equal, stringOrNumber);
} else { // It's a string. } else { // It's a string.
String valueString = value instanceof BlankEval ? "" : getStringFromValueEval(value); String valueString = value instanceof BlankEval ? "" : OperandResolver.coerceValueToString(value);
return stringOrNumber.equals(valueString); return stringOrNumber.equals(valueString);
} }
} else { // It's a text starts-with condition. } else { // It's a text starts-with condition.
@ -336,7 +296,7 @@ public final class DStarRunner implements Function3Arg {
return value instanceof StringEval; return value instanceof StringEval;
} }
else { else {
String valueString = value instanceof BlankEval ? "" : getStringFromValueEval(value); String valueString = value instanceof BlankEval ? "" : OperandResolver.coerceValueToString(value);
return valueString.startsWith(conditionString); return valueString.startsWith(conditionString);
} }
} }
@ -426,18 +386,18 @@ public final class DStarRunner implements Function3Arg {
} }
/** /**
* Takes a ValueEval and tries to retrieve a String value from it. * Resolve a ValueEval that's in an AreaEval.
* It tries to resolve references if there are any.
* *
* @param value ValueEval to retrieve the string from. * @param db AreaEval from which the cell to resolve is retrieved.
* @return String corresponding to the given ValueEval. * @param dbRow Relative row in the AreaEval.
* @throws EvaluationException If it's not possible to retrieve a String value. * @param dbCol Relative column in the AreaEval.
* @return A ValueEval that is a NumberEval, StringEval, BoolEval, BlankEval or ErrorEval.
*/ */
private static String getStringFromValueEval(ValueEval value) private static ValueEval resolveReference(AreaEval db, int dbRow, int dbCol) {
throws EvaluationException { try {
value = solveReference(value); return OperandResolver.getSingleValue(db.getValue(dbRow, dbCol), db.getFirstRow()+dbRow, db.getFirstColumn()+dbCol);
if(!(value instanceof StringValueEval)) } catch (EvaluationException e) {
throw new EvaluationException(ErrorEval.VALUE_INVALID); return e.getErrorEval();
return ((StringValueEval)value).getStringValue(); }
} }
} }

Binary file not shown.