whitespace

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1716057 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Javen O'Neal 2015-11-24 08:13:10 +00:00
parent 58718f7245
commit d58342dd9f

View File

@ -27,302 +27,302 @@ import java.util.regex.Pattern;
*/ */
public final class OperandResolver { public final class OperandResolver {
// Based on regular expression defined in JavaDoc at {@link java.lang.Double#valueOf} // Based on regular expression defined in JavaDoc at {@link java.lang.Double#valueOf}
// modified to remove support for NaN, Infinity, Hexadecimal support and floating type suffixes // modified to remove support for NaN, Infinity, Hexadecimal support and floating type suffixes
private static final String Digits = "(\\p{Digit}+)"; private static final String Digits = "(\\p{Digit}+)";
private static final String Exp = "[eE][+-]?"+Digits; private static final String Exp = "[eE][+-]?"+Digits;
private static final String fpRegex = private static final String fpRegex =
("[\\x00-\\x20]*" + ("[\\x00-\\x20]*" +
"[+-]?(" + "[+-]?(" +
"((("+Digits+"(\\.)?("+Digits+"?)("+Exp+")?)|"+ "((("+Digits+"(\\.)?("+Digits+"?)("+Exp+")?)|"+
"(\\.("+Digits+")("+Exp+")?))))"+ "(\\.("+Digits+")("+Exp+")?))))"+
"[\\x00-\\x20]*"); "[\\x00-\\x20]*");
private OperandResolver() { private OperandResolver() {
// no instances of this class // no instances of this class
} }
/** /**
* Retrieves a single value from a variety of different argument types according to standard * Retrieves a single value from a variety of different argument types according to standard
* Excel rules. Does not perform any type conversion. * Excel rules. Does not perform any type conversion.
* @param arg the evaluated argument as passed to the function or operator. * @param arg the evaluated argument as passed to the function or operator.
* @param srcCellRow used when arg is a single column AreaRef * @param srcCellRow used when arg is a single column AreaRef
* @param srcCellCol used when arg is a single row AreaRef * @param srcCellCol used when arg is a single row AreaRef
* @return a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt> or <tt>BlankEval</tt>. * @return a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt> or <tt>BlankEval</tt>.
* Never <code>null</code> or <tt>ErrorEval</tt>. * Never <code>null</code> or <tt>ErrorEval</tt>.
* @throws EvaluationException(#VALUE!) if srcCellRow or srcCellCol do not properly index into * @throws EvaluationException(#VALUE!) if srcCellRow or srcCellCol do not properly index into
* an AreaEval. If the actual value retrieved is an ErrorEval, a corresponding * an AreaEval. If the actual value retrieved is an ErrorEval, a corresponding
* EvaluationException is thrown. * EvaluationException is thrown.
*/ */
public static ValueEval getSingleValue(ValueEval arg, int srcCellRow, int srcCellCol) public static ValueEval getSingleValue(ValueEval arg, int srcCellRow, int srcCellCol)
throws EvaluationException { throws EvaluationException {
final ValueEval result; final ValueEval result;
if (arg instanceof RefEval) { if (arg instanceof RefEval) {
result = chooseSingleElementFromRef((RefEval) arg); result = chooseSingleElementFromRef((RefEval) arg);
} else if (arg instanceof AreaEval) { } else if (arg instanceof AreaEval) {
result = chooseSingleElementFromArea((AreaEval) arg, srcCellRow, srcCellCol); result = chooseSingleElementFromArea((AreaEval) arg, srcCellRow, srcCellCol);
} else { } else {
result = arg; result = arg;
} }
if (result instanceof ErrorEval) { if (result instanceof ErrorEval) {
throw new EvaluationException((ErrorEval) result); throw new EvaluationException((ErrorEval) result);
} }
return result; return result;
} }
/** /**
* Implements (some perhaps not well known) Excel functionality to select a single cell from an * Implements (some perhaps not well known) Excel functionality to select a single cell from an
* area depending on the coordinates of the calling cell. Here is an example demonstrating * area depending on the coordinates of the calling cell. Here is an example demonstrating
* both selection from a single row area and a single column area in the same formula. * both selection from a single row area and a single column area in the same formula.
* *
* <table border="1" cellpadding="1" cellspacing="1" summary="sample spreadsheet"> * <table border="1" cellpadding="1" cellspacing="1" summary="sample spreadsheet">
* <tr><th>&nbsp;</th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th><th>&nbsp;C&nbsp;</th><th>&nbsp;D&nbsp;</th></tr> * <tr><th>&nbsp;</th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th><th>&nbsp;C&nbsp;</th><th>&nbsp;D&nbsp;</th></tr>
* <tr><th>1</th><td>15</td><td>20</td><td>25</td><td>&nbsp;</td></tr> * <tr><th>1</th><td>15</td><td>20</td><td>25</td><td>&nbsp;</td></tr>
* <tr><th>2</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>200</td></tr> * <tr><th>2</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>200</td></tr>
* <tr><th>3</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>300</td></tr> * <tr><th>3</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>300</td></tr>
* <tr><th>3</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>400</td></tr> * <tr><th>3</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>400</td></tr>
* </table> * </table>
* *
* If the formula "=1000+A1:B1+D2:D3" is put into the 9 cells from A2 to C4, the spreadsheet * If the formula "=1000+A1:B1+D2:D3" is put into the 9 cells from A2 to C4, the spreadsheet
* will look like this: * will look like this:
* *
* <table border="1" cellpadding="1" cellspacing="1" summary="sample spreadsheet"> * <table border="1" cellpadding="1" cellspacing="1" summary="sample spreadsheet">
* <tr><th>&nbsp;</th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th><th>&nbsp;C&nbsp;</th><th>&nbsp;D&nbsp;</th></tr> * <tr><th>&nbsp;</th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th><th>&nbsp;C&nbsp;</th><th>&nbsp;D&nbsp;</th></tr>
* <tr><th>1</th><td>15</td><td>20</td><td>25</td><td>&nbsp;</td></tr> * <tr><th>1</th><td>15</td><td>20</td><td>25</td><td>&nbsp;</td></tr>
* <tr><th>2</th><td>1215</td><td>1220</td><td>#VALUE!</td><td>200</td></tr> * <tr><th>2</th><td>1215</td><td>1220</td><td>#VALUE!</td><td>200</td></tr>
* <tr><th>3</th><td>1315</td><td>1320</td><td>#VALUE!</td><td>300</td></tr> * <tr><th>3</th><td>1315</td><td>1320</td><td>#VALUE!</td><td>300</td></tr>
* <tr><th>4</th><td>#VALUE!</td><td>#VALUE!</td><td>#VALUE!</td><td>400</td></tr> * <tr><th>4</th><td>#VALUE!</td><td>#VALUE!</td><td>#VALUE!</td><td>400</td></tr>
* </table> * </table>
* *
* Note that the row area (A1:B1) does not include column C and the column area (D2:D3) does * Note that the row area (A1:B1) does not include column C and the column area (D2:D3) does
* not include row 4, so the values in C1(=25) and D4(=400) are not accessible to the formula * not include row 4, so the values in C1(=25) and D4(=400) are not accessible to the formula
* as written, but in the 4 cells A2:B3, the row and column selection works ok.<p/> * as written, but in the 4 cells A2:B3, the row and column selection works ok.<p/>
* *
* The same concept is extended to references across sheets, such that even multi-row, * The same concept is extended to references across sheets, such that even multi-row,
* multi-column areas can be useful.<p/> * multi-column areas can be useful.<p/>
* *
* Of course with carefully (or carelessly) chosen parameters, cyclic references can occur and * Of course with carefully (or carelessly) chosen parameters, cyclic references can occur and
* hence this method <b>can</b> throw a 'circular reference' EvaluationException. Note that * hence this method <b>can</b> throw a 'circular reference' EvaluationException. Note that
* this method does not attempt to detect cycles. Every cell in the specified Area <tt>ae</tt> * this method does not attempt to detect cycles. Every cell in the specified Area <tt>ae</tt>
* has already been evaluated prior to this method call. Any cell (or cell<b>s</b>) part of * has already been evaluated prior to this method call. Any cell (or cell<b>s</b>) part of
* <tt>ae</tt> that would incur a cyclic reference error if selected by this method, will * <tt>ae</tt> that would incur a cyclic reference error if selected by this method, will
* already have the value <t>ErrorEval.CIRCULAR_REF_ERROR</tt> upon entry to this method. It * already have the value <t>ErrorEval.CIRCULAR_REF_ERROR</tt> upon entry to this method. It
* is assumed logic exists elsewhere to produce this behaviour. * is assumed logic exists elsewhere to produce this behaviour.
* *
* @return whatever the selected cell's evaluated value is. Never <code>null</code>. Never * @return whatever the selected cell's evaluated value is. Never <code>null</code>. Never
* <tt>ErrorEval</tt>. * <tt>ErrorEval</tt>.
* @throws EvaluationException if there is a problem with indexing into the area, or if the * @throws EvaluationException if there is a problem with indexing into the area, or if the
* evaluated cell has an error. * evaluated cell has an error.
*/ */
public static ValueEval chooseSingleElementFromArea(AreaEval ae, public static ValueEval chooseSingleElementFromArea(AreaEval ae,
int srcCellRow, int srcCellCol) throws EvaluationException { int srcCellRow, int srcCellCol) throws EvaluationException {
ValueEval result = chooseSingleElementFromAreaInternal(ae, srcCellRow, srcCellCol); ValueEval result = chooseSingleElementFromAreaInternal(ae, srcCellRow, srcCellCol);
if (result instanceof ErrorEval) { if (result instanceof ErrorEval) {
throw new EvaluationException((ErrorEval) result); throw new EvaluationException((ErrorEval) result);
} }
return result; return result;
} }
/** /**
* @return possibly <tt>ErrorEval</tt>, and <code>null</code> * @return possibly <tt>ErrorEval</tt>, and <code>null</code>
*/ */
private static ValueEval chooseSingleElementFromAreaInternal(AreaEval ae, private static ValueEval chooseSingleElementFromAreaInternal(AreaEval ae,
int srcCellRow, int srcCellCol) throws EvaluationException { int srcCellRow, int srcCellCol) throws EvaluationException {
// if(false) { // if(false) {
// // this is too simplistic // // this is too simplistic
// if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) { // if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) {
// throw new EvaluationException(ErrorEval.CIRCULAR_REF_ERROR); // throw new EvaluationException(ErrorEval.CIRCULAR_REF_ERROR);
// } // }
// /* // /*
// Circular references are not dealt with directly here, but it is worth noting some issues. // Circular references are not dealt with directly here, but it is worth noting some issues.
// //
// ANY one of the return statements in this method could return a cell that is identical // ANY one of the return statements in this method could return a cell that is identical
// to the one immediately being evaluated. The evaluating cell is identified by srcCellRow, // to the one immediately being evaluated. The evaluating cell is identified by srcCellRow,
// srcCellRow AND sheet. The sheet is not available in any nearby calling method, so that's // srcCellRow AND sheet. The sheet is not available in any nearby calling method, so that's
// one reason why circular references are not easy to detect here. (The sheet of the returned // one reason why circular references are not easy to detect here. (The sheet of the returned
// cell can be obtained from ae if it is an Area3DEval.) // cell can be obtained from ae if it is an Area3DEval.)
// //
// Another reason there's little value in attempting to detect circular references here is // Another reason there's little value in attempting to detect circular references here is
// that only direct circular references could be detected. If the cycle involved two or more // that only direct circular references could be detected. If the cycle involved two or more
// cells this method could not detect it. // cells this method could not detect it.
// //
// Logic to detect evaluation cycles of all kinds has been coded in EvaluationCycleDetector // Logic to detect evaluation cycles of all kinds has been coded in EvaluationCycleDetector
// (and FormulaEvaluator). // (and FormulaEvaluator).
// */ // */
// } // }
if (ae.isColumn()) { if (ae.isColumn()) {
if(ae.isRow()) { if(ae.isRow()) {
return ae.getRelativeValue(0, 0); return ae.getRelativeValue(0, 0);
} }
if(!ae.containsRow(srcCellRow)) { if(!ae.containsRow(srcCellRow)) {
throw EvaluationException.invalidValue(); throw EvaluationException.invalidValue();
} }
return ae.getAbsoluteValue(srcCellRow, ae.getFirstColumn()); return ae.getAbsoluteValue(srcCellRow, ae.getFirstColumn());
} }
if(!ae.isRow()) { if(!ae.isRow()) {
// multi-column, multi-row area // multi-column, multi-row area
if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) { if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) {
return ae.getAbsoluteValue(ae.getFirstRow(), ae.getFirstColumn()); return ae.getAbsoluteValue(ae.getFirstRow(), ae.getFirstColumn());
} }
throw EvaluationException.invalidValue(); throw EvaluationException.invalidValue();
} }
if(!ae.containsColumn(srcCellCol)) { if(!ae.containsColumn(srcCellCol)) {
throw EvaluationException.invalidValue(); throw EvaluationException.invalidValue();
} }
return ae.getAbsoluteValue(ae.getFirstRow(), srcCellCol); return ae.getAbsoluteValue(ae.getFirstRow(), srcCellCol);
} }
private static ValueEval chooseSingleElementFromRef(RefEval ref) { private static ValueEval chooseSingleElementFromRef(RefEval ref) {
return ref.getInnerValueEval( ref.getFirstSheetIndex() ); return ref.getInnerValueEval( ref.getFirstSheetIndex() );
} }
/** /**
* Applies some conversion rules if the supplied value is not already an integer.<br/> * Applies some conversion rules if the supplied value is not already an integer.<br/>
* Value is first coerced to a <tt>double</tt> ( See <tt>coerceValueToDouble()</tt> ). * Value is first coerced to a <tt>double</tt> ( See <tt>coerceValueToDouble()</tt> ).
* Note - <tt>BlankEval</tt> is converted to <code>0</code>.<p/> * Note - <tt>BlankEval</tt> is converted to <code>0</code>.<p/>
* *
* Excel typically converts doubles to integers by truncating toward negative infinity.<br/> * Excel typically converts doubles to integers by truncating toward negative infinity.<br/>
* The equivalent java code is:<br/> * The equivalent java code is:<br/>
* &nbsp;&nbsp;<code>return (int)Math.floor(d);</code><br/> * &nbsp;&nbsp;<code>return (int)Math.floor(d);</code><br/>
* <b>not</b>:<br/> * <b>not</b>:<br/>
* &nbsp;&nbsp;<code>return (int)d; // wrong - rounds toward zero</code> * &nbsp;&nbsp;<code>return (int)d; // wrong - rounds toward zero</code>
* *
*/ */
public static int coerceValueToInt(ValueEval ev) throws EvaluationException { public static int coerceValueToInt(ValueEval ev) throws EvaluationException {
if (ev == BlankEval.instance) { if (ev == BlankEval.instance) {
return 0; return 0;
} }
double d = coerceValueToDouble(ev); double d = coerceValueToDouble(ev);
// Note - the standard java type conversion from double to int truncates toward zero. // Note - the standard java type conversion from double to int truncates toward zero.
// but Math.floor() truncates toward negative infinity // but Math.floor() truncates toward negative infinity
return (int)Math.floor(d); return (int)Math.floor(d);
} }
/** /**
* Applies some conversion rules if the supplied value is not already a number. * Applies some conversion rules if the supplied value is not already a number.
* Note - <tt>BlankEval</tt> is converted to {@link NumberEval#ZERO}. * Note - <tt>BlankEval</tt> is converted to {@link NumberEval#ZERO}.
* @param ev must be a {@link NumberEval}, {@link StringEval}, {@link BoolEval} or * @param ev must be a {@link NumberEval}, {@link StringEval}, {@link BoolEval} or
* {@link BlankEval} * {@link BlankEval}
* @return actual, parsed or interpreted double value (respectively). * @return actual, parsed or interpreted double value (respectively).
* @throws EvaluationException(#VALUE!) only if a StringEval is supplied and cannot be parsed * @throws EvaluationException(#VALUE!) only if a StringEval is supplied and cannot be parsed
* as a double (See <tt>parseDouble()</tt> for allowable formats). * as a double (See <tt>parseDouble()</tt> for allowable formats).
* @throws RuntimeException if the supplied parameter is not {@link NumberEval}, * @throws RuntimeException if the supplied parameter is not {@link NumberEval},
* {@link StringEval}, {@link BoolEval} or {@link BlankEval} * {@link StringEval}, {@link BoolEval} or {@link BlankEval}
*/ */
public static double coerceValueToDouble(ValueEval ev) throws EvaluationException { public static double coerceValueToDouble(ValueEval ev) throws EvaluationException {
if (ev == BlankEval.instance) { if (ev == BlankEval.instance) {
return 0.0; return 0.0;
} }
if (ev instanceof NumericValueEval) { if (ev instanceof NumericValueEval) {
// this also handles booleans // this also handles booleans
return ((NumericValueEval)ev).getNumberValue(); return ((NumericValueEval)ev).getNumberValue();
} }
if (ev instanceof StringEval) { if (ev instanceof StringEval) {
Double dd = parseDouble(((StringEval) ev).getStringValue()); Double dd = parseDouble(((StringEval) ev).getStringValue());
if (dd == null) { if (dd == null) {
throw EvaluationException.invalidValue(); throw EvaluationException.invalidValue();
} }
return dd.doubleValue(); return dd.doubleValue();
} }
throw new RuntimeException("Unexpected arg eval type (" + ev.getClass().getName() + ")"); throw new RuntimeException("Unexpected arg eval type (" + ev.getClass().getName() + ")");
} }
/** /**
* Converts a string to a double using standard rules that Excel would use.<br/> * Converts a string to a double using standard rules that Excel would use.<br/>
* Tolerates leading and trailing spaces, <p/> * Tolerates leading and trailing spaces, <p/>
* *
* Doesn't support currency prefixes, commas, percentage signs or arithmetic operations strings. * Doesn't support currency prefixes, commas, percentage signs or arithmetic operations strings.
* *
* Some examples:<br/> * Some examples:<br/>
* " 123 " -&gt; 123.0<br/> * " 123 " -&gt; 123.0<br/>
* ".123" -&gt; 0.123<br/> * ".123" -&gt; 0.123<br/>
* "1E4" -&gt; 1000<br/> * "1E4" -&gt; 1000<br/>
* "-123" -&gt; -123.0<br/> * "-123" -&gt; -123.0<br/>
* These not supported yet:<br/> * These not supported yet:<br/>
* " $ 1,000.00 " -&gt; 1000.0<br/> * " $ 1,000.00 " -&gt; 1000.0<br/>
* "$1.25E4" -&gt; 12500.0<br/> * "$1.25E4" -&gt; 12500.0<br/>
* "5**2" -&gt; 500<br/> * "5**2" -&gt; 500<br/>
* "250%" -&gt; 2.5<br/> * "250%" -&gt; 2.5<br/>
* *
* @return <code>null</code> if the specified text cannot be parsed as a number * @return <code>null</code> if the specified text cannot be parsed as a number
*/ */
public static Double parseDouble(String pText) { public static Double parseDouble(String pText) {
if (Pattern.matches(fpRegex, pText)) if (Pattern.matches(fpRegex, pText))
try { try {
return Double.parseDouble(pText); return Double.parseDouble(pText);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
return null; return null;
} }
else { else {
return null; return null;
} }
} }
/** /**
* @param ve must be a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>, or <tt>BlankEval</tt> * @param ve must be a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>, or <tt>BlankEval</tt>
* @return the converted string value. never <code>null</code> * @return the converted string value. never <code>null</code>
*/ */
public static String coerceValueToString(ValueEval ve) { public static String coerceValueToString(ValueEval ve) {
if (ve instanceof StringValueEval) { if (ve instanceof StringValueEval) {
StringValueEval sve = (StringValueEval) ve; StringValueEval sve = (StringValueEval) ve;
return sve.getStringValue(); return sve.getStringValue();
} }
if (ve == BlankEval.instance) { if (ve == BlankEval.instance) {
return ""; return "";
} }
throw new IllegalArgumentException("Unexpected eval class (" + ve.getClass().getName() + ")"); throw new IllegalArgumentException("Unexpected eval class (" + ve.getClass().getName() + ")");
} }
/** /**
* @return <code>null</code> to represent blank values * @return <code>null</code> to represent blank values
* @throws EvaluationException if ve is an ErrorEval, or if a string value cannot be converted * @throws EvaluationException if ve is an ErrorEval, or if a string value cannot be converted
*/ */
public static Boolean coerceValueToBoolean(ValueEval ve, boolean stringsAreBlanks) throws EvaluationException { public static Boolean coerceValueToBoolean(ValueEval ve, boolean stringsAreBlanks) throws EvaluationException {
if (ve == null || ve == BlankEval.instance) { if (ve == null || ve == BlankEval.instance) {
// TODO - remove 've == null' condition once AreaEval is fixed // TODO - remove 've == null' condition once AreaEval is fixed
return null; return null;
} }
if (ve instanceof BoolEval) { if (ve instanceof BoolEval) {
return Boolean.valueOf(((BoolEval) ve).getBooleanValue()); return Boolean.valueOf(((BoolEval) ve).getBooleanValue());
} }
if (ve == BlankEval.instance) { if (ve == BlankEval.instance) {
return null; return null;
} }
if (ve instanceof StringEval) { if (ve instanceof StringEval) {
if (stringsAreBlanks) { if (stringsAreBlanks) {
return null; return null;
} }
String str = ((StringEval) ve).getStringValue(); String str = ((StringEval) ve).getStringValue();
if (str.equalsIgnoreCase("true")) { if (str.equalsIgnoreCase("true")) {
return Boolean.TRUE; return Boolean.TRUE;
} }
if (str.equalsIgnoreCase("false")) { if (str.equalsIgnoreCase("false")) {
return Boolean.FALSE; return Boolean.FALSE;
} }
// else - string cannot be converted to boolean // else - string cannot be converted to boolean
throw new EvaluationException(ErrorEval.VALUE_INVALID); throw new EvaluationException(ErrorEval.VALUE_INVALID);
} }
if (ve instanceof NumericValueEval) { if (ve instanceof NumericValueEval) {
NumericValueEval ne = (NumericValueEval) ve; NumericValueEval ne = (NumericValueEval) ve;
double d = ne.getNumberValue(); double d = ne.getNumberValue();
if (Double.isNaN(d)) { if (Double.isNaN(d)) {
throw new EvaluationException(ErrorEval.VALUE_INVALID); throw new EvaluationException(ErrorEval.VALUE_INVALID);
} }
return Boolean.valueOf(d != 0); return Boolean.valueOf(d != 0);
} }
if (ve instanceof ErrorEval) { if (ve instanceof ErrorEval) {
throw new EvaluationException((ErrorEval) ve); throw new EvaluationException((ErrorEval) ve);
} }
throw new RuntimeException("Unexpected eval (" + ve.getClass().getName() + ")"); throw new RuntimeException("Unexpected eval (" + ve.getClass().getName() + ")");
} }
} }