Patch from Josh from bug #44403 - Further support for unusual, but valid, arguments to the Mid function

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@629552 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2008-02-20 18:14:30 +00:00
parent ca243602bb
commit 5f2db43b4d
8 changed files with 581 additions and 80 deletions

View File

@ -36,6 +36,7 @@
<!-- Don't forget to update status.xml too! -->
<release version="3.1-beta1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">44403 - Further support for unusual, but valid, arguments to the Mid function</action>
<action dev="POI-DEVELOPERS" type="fix">44410 - Support for whole-column ranges, such as C:C, in formula strings and the formula evaluator</action>
<action dev="POI-DEVELOPERS" type="fix">44421 - Update Match function to properly support Area references</action>
<action dev="POI-DEVELOPERS" type="fix">44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name</action>

View File

@ -45,8 +45,8 @@
<link href="http://svn.apache.org/viewcvs.cgi/poi/trunk/src/scratchpad/">scratchpad area</link>
of the POI SVN repository.
Ensure that you have the scratchpad jar or the scratchpad
build area in your
classpath before experimenting with this code.
build area in your classpath before experimenting with
this code - the main POI jar is not enough.
</note>
<p>The <link href="./quick-guide.html">quick guide</link> documentation provides
information on using this API. Comments and fixes gratefully accepted on the POI

View File

@ -33,6 +33,7 @@
<!-- Don't forget to update changes.xml too! -->
<changes>
<release version="3.1-beta1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">44403 - Further support for unusual, but valid, arguments to the Mid function</action>
<action dev="POI-DEVELOPERS" type="fix">44410 - Support for whole-column ranges, such as C:C, in formula strings and the formula evaluator</action>
<action dev="POI-DEVELOPERS" type="fix">44421 - Update Match function to properly support Area references</action>
<action dev="POI-DEVELOPERS" type="fix">44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name</action>

View File

@ -0,0 +1,134 @@
/* ====================================================================
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.hssf.record.formula.eval;
/**
* This class is used to simplify error handling logic <i>within</i> operator and function
* implementations. Note - <tt>OperationEval.evaluate()</tt> and <tt>Function.evaluate()</tt>
* method signatures do not throw this exception so it cannot propagate outside.<p/>
*
* Here is an example coded without <tt>EvaluationException</tt>, to show how it can help:
* <pre>
* public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
* // ...
* Eval arg0 = args[0];
* if(arg0 instanceof ErrorEval) {
* return arg0;
* }
* if(!(arg0 instanceof AreaEval)) {
* return ErrorEval.VALUE_INVALID;
* }
* double temp = 0;
* AreaEval area = (AreaEval)arg0;
* ValueEval[] values = area.getValues();
* for (int i = 0; i < values.length; i++) {
* ValueEval ve = values[i];
* if(ve instanceof ErrorEval) {
* return ve;
* }
* if(!(ve instanceof NumericValueEval)) {
* return ErrorEval.VALUE_INVALID;
* }
* temp += ((NumericValueEval)ve).getNumberValue();
* }
* // ...
* }
* </pre>
* In this example, if any error is encountered while processing the arguments, an error is
* returned immediately. This code is difficult to refactor due to all the points where errors
* are returned.<br/>
* Using <tt>EvaluationException</tt> allows the error returning code to be consolidated to one
* place.<p/>
* <pre>
* public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
* try {
* // ...
* AreaEval area = getAreaArg(args[0]);
* double temp = sumValues(area.getValues());
* // ...
* } catch (EvaluationException e) {
* return e.getErrorEval();
* }
*}
*
*private static AreaEval getAreaArg(Eval arg0) throws EvaluationException {
* if (arg0 instanceof ErrorEval) {
* throw new EvaluationException((ErrorEval) arg0);
* }
* if (arg0 instanceof AreaEval) {
* return (AreaEval) arg0;
* }
* throw EvaluationException.invalidValue();
*}
*
*private double sumValues(ValueEval[] values) throws EvaluationException {
* double temp = 0;
* for (int i = 0; i < values.length; i++) {
* ValueEval ve = values[i];
* if (ve instanceof ErrorEval) {
* throw new EvaluationException((ErrorEval) ve);
* }
* if (!(ve instanceof NumericValueEval)) {
* throw EvaluationException.invalidValue();
* }
* temp += ((NumericValueEval) ve).getNumberValue();
* }
* return temp;
*}
* </pre>
* It is not mandatory to use EvaluationException, doing so might give the following advantages:<br/>
* - Methods can more easily be extracted, allowing for re-use.<br/>
* - Type management (typecasting etc) is simpler because error conditions have been separated from
* intermediate calculation values.<br/>
* - Fewer local variables are required. Local variables can have stronger types.<br/>
* - It is easier to mimic common Excel error handling behaviour (exit upon encountering first
* error), because exceptions conveniently propagate up the call stack regardless of execution
* points or the number of levels of nested calls.<p/>
*
* <b>Note</b> - Only standard evaluation errors are represented by <tt>EvaluationException</tt> (
* i.e. conditions expected to be encountered when evaluating arbitrary Excel formulas). Conditions
* that could never occur in an Excel spreadsheet should result in runtime exceptions. Care should
* be taken to not translate any POI internal error into an Excel evaluation error code.
*
* @author Josh Micich
*/
public final class EvaluationException extends Exception {
private final ErrorEval _errorEval;
public EvaluationException(ErrorEval errorEval) {
_errorEval = errorEval;
}
// some convenience factory methods
/** <b>#VALUE!</b> - Wrong type of operand */
public static EvaluationException invalidValue() {
return new EvaluationException(ErrorEval.VALUE_INVALID);
}
/** <b>#REF!</b> - Illegal or deleted cell reference */
public static EvaluationException invalidRef() {
return new EvaluationException(ErrorEval.REF_INVALID);
}
/** <b>#NUM!</b> - Value range overflow */
public static EvaluationException numberError() {
return new EvaluationException(ErrorEval.NUM_ERROR);
}
public ErrorEval getErrorEval() {
return _errorEval;
}
}

View File

@ -0,0 +1,249 @@
/* ====================================================================
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.hssf.record.formula.eval;
/**
* Provides functionality for evaluating arguments to functions and operators.
*
* @author Josh Micich
*/
public final class OperandResolver {
private OperandResolver() {
// no instances of this class
}
/**
* Retrieves a single value from a variety of different argument types according to standard
* Excel rules. Does not perform any type conversion.
* @param arg the evaluated argument as passed to the function or operator.
* @param srcCellRow used when arg is a single column 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>.
* Never <code>null</code> or <tt>ErrorEval</tt>.
* @throws EvaluationException(#VALUE!) if srcCellRow or srcCellCol do not properly index into
* an AreaEval. If the actual value retrieved is an ErrorEval, a corresponding
* EvaluationException is thrown.
*/
public static ValueEval getSingleValue(Eval arg, int srcCellRow, short srcCellCol)
throws EvaluationException {
if (arg instanceof RefEval) {
RefEval re = (RefEval) arg;
return re.getInnerValueEval();
}
Eval result;
if (arg instanceof AreaEval) {
result = chooseSingleElementFromArea((AreaEval) arg, srcCellRow, srcCellCol);
} else {
result = arg;
}
if (result instanceof ErrorEval) {
throw new EvaluationException((ErrorEval) result);
}
if (result instanceof ValueEval) {
return (ValueEval) result;
}
throw new RuntimeException("Unexpected eval type (" + result.getClass().getName() + ")");
}
/**
* 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
* 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">
* <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>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>400</td></tr>
* </table>
*
* If the formula "=1000+A1:B1+D2:D3" is put into the 9 cells from A2 to C4, the spreadsheet
* will look like this:
*
* <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>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>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>
* </table>
*
* 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
* 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,
* multi-column areas can be useful.<p/>
*
* 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
* 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
* <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
* is assumed logic exists elsewhere to produce this behaviour.
*
* @return whatever the selected cell's evaluated value is. Never <code>null</code>. Never
* <tt>ErrorEval</tt>.
* @throws EvaluationException if there is a problem with indexing into the area, or if the
* evaluated cell has an error.
*/
public static ValueEval chooseSingleElementFromArea(AreaEval ae,
int srcCellRow, short srcCellCol) throws EvaluationException {
ValueEval result = chooseSingleElementFromAreaInternal(ae, srcCellRow, srcCellCol);
if(result == null) {
// This seems to be required because AreaEval.values() array may contain nulls.
// perhaps that should not be allowed.
result = BlankEval.INSTANCE;
}
if (result instanceof ErrorEval) {
throw new EvaluationException((ErrorEval) result);
}
return result;
}
/**
* @return possibly <tt>ErrorEval</tt>, and <code>null</code>
*/
private static ValueEval chooseSingleElementFromAreaInternal(AreaEval ae,
int srcCellRow, short srcCellCol) throws EvaluationException {
if(false) {
// this is too simplistic
if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) {
throw new EvaluationException(ErrorEval.CIRCULAR_REF_ERROR);
}
/*
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
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
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.)
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
cells this method could not detect it.
Logic to detect evaluation cycles of all kinds has been coded in EvaluationCycleDetector
(and HSSFFormulaEvaluator).
*/
}
if (ae.isColumn()) {
if(ae.isRow()) {
return ae.getValues()[0];
}
if(!ae.containsRow(srcCellRow)) {
throw EvaluationException.invalidValue();
}
return ae.getValueAt(srcCellRow, ae.getFirstColumn());
}
if(!ae.isRow()) {
// multi-column, multi-row area
if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) {
return ae.getValueAt(ae.getFirstRow(), ae.getFirstColumn());
}
throw EvaluationException.invalidValue();
}
if(!ae.containsColumn(srcCellCol)) {
throw EvaluationException.invalidValue();
}
return ae.getValueAt(ae.getFirstRow(), srcCellCol);
}
/**
* 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> ).<p/>
*
* Excel typically converts doubles to integers by truncating toward negative infinity.<br/>
* The equivalent java code is:<br/>
* &nbsp;&nbsp;<code>return (int)Math.floor(d);</code><br/>
* <b>not</b>:<br/>
* &nbsp;&nbsp;<code>return (int)d; // wrong - rounds toward zero</code>
*
*/
public static int coerceValueToInt(ValueEval ev) throws EvaluationException {
double d = coerceValueToDouble(ev);
// Note - the standard java type conversion from double to int truncates toward zero.
// but Math.floor() truncates toward negative infinity
return (int)Math.floor(d);
}
/**
* Applies some conversion rules if the supplied value is not already a number.
* Note - <tt>BlankEval</tt> is not supported and must be handled by the caller.
* @param ev must be a <tt>NumberEval</tt>, <tt>StringEval</tt> or <tt>BoolEval</tt>
* @return actual, parsed or interpreted double value (respectively).
* @throws EvaluationException(#VALUE!) only if a StringEval is supplied and cannot be parsed
* as a double (See <tt>parseDouble()</tt> for allowable formats).
* @throws RuntimeException if the supplied parameter is not <tt>NumberEval</tt>,
* <tt>StringEval</tt> or <tt>BoolEval</tt>
*/
public static double coerceValueToDouble(ValueEval ev) throws EvaluationException {
if (ev instanceof NumericValueEval) {
// this also handles booleans
return ((NumericValueEval)ev).getNumberValue();
}
if (ev instanceof StringEval) {
Double dd = parseDouble(((StringEval) ev).getStringValue());
if (dd == null) {
throw EvaluationException.invalidValue();
}
return dd.doubleValue();
}
throw new RuntimeException("Unexpected arg eval type (" + ev.getClass().getName() + ")");
}
/**
* Converts a string to a double using standard rules that Excel would use.<br/>
* Tolerates currency prefixes, commas, leading and trailing spaces.<p/>
*
* Some examples:<br/>
* " 123 " -&gt; 123.0<br/>
* ".123" -&gt; 0.123<br/>
* " $ 1,000.00 " -&gt; 1000.0<br/>
* "$1.25E4" -&gt; 12500.0<br/>
*
* @param text
* @return <code>null</code> if the specified text cannot be parsed as a number
*/
public static Double parseDouble(String text) {
if(!Character.isDigit(text.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(text);
} catch (NumberFormatException e) {
return null;
}
return new Double(val);
}
}

View File

@ -1,99 +1,100 @@
/*
* 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.
*/
/*
* Created on May 15, 2005
* 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.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
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.NumericValueEval;
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.StringValueEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* An implementation of the MID function:
* Returns a specific number of characters from a text string,
* starting at the position you specify, based on the number
* of characters you specify.
* An implementation of the MID function<br/> MID returns a specific number of
* characters from a text string, starting at the specified position.<p/>
*
* <b>Syntax<b>:<br/> <b>MID</b>(<b>text</b>, <b>start_num</b>,
* <b>num_chars</b>)<br/>
*
* @author Manda Wilson &lt; wilson at c bio dot msk cc dot org &gt;
*/
public class Mid extends TextFunction {
/**
* Returns a specific number of characters from a text string,
* starting at the position you specify, based on the number
* of characters you specify.
* Returns a specific number of characters from a text string, starting at
* the position you specify, based on the number of characters you specify.
*
* @see org.apache.poi.hssf.record.formula.eval.Eval
*/
public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
Eval retval = null;
String str = null;
int startNum = 0;
int numChars = 0;
switch (operands.length) {
default:
retval = ErrorEval.VALUE_INVALID;
case 3:
// first operand is text string containing characters to extract
// second operand is position of first character to extract
// third operand is the number of characters to return
ValueEval firstveval = singleOperandEvaluate(operands[0], srcCellRow, srcCellCol);
ValueEval secondveval = singleOperandEvaluate(operands[1], srcCellRow, srcCellCol);
ValueEval thirdveval = singleOperandEvaluate(operands[2], srcCellRow, srcCellCol);
if (firstveval instanceof StringValueEval
&& secondveval instanceof NumericValueEval
&& thirdveval instanceof NumericValueEval) {
StringValueEval strEval = (StringValueEval) firstveval;
str = strEval.getStringValue();
NumericValueEval startNumEval = (NumericValueEval) secondveval;
// NOTE: it is safe to cast to int here
// because in Excel =MID("test", 1, 1.7) returns t
// so 1.7 must be truncated to 1
// and =MID("test", 1.9, 2) returns te
// so 1.9 must be truncated to 1
startNum = (int) startNumEval.getNumberValue();
NumericValueEval numCharsEval = (NumericValueEval) thirdveval;
numChars = (int) numCharsEval.getNumberValue();
} else {
retval = ErrorEval.VALUE_INVALID;
}
}
if (retval == null) {
if (startNum < 1 || numChars < 0) {
retval = ErrorEval.VALUE_INVALID;
} else if (startNum > str.length() || numChars == 0) {
retval = BlankEval.INSTANCE;
} else if (startNum + numChars > str.length()) {
retval = new StringEval(str.substring(startNum - 1));
} else {
retval = new StringEval(str.substring(startNum - 1, (numChars + startNum - 1)));
}
}
return retval;
}
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
if (args.length != 3) {
return ErrorEval.VALUE_INVALID;
}
}
String text;
int startIx; // zero based
int numChars;
try {
text = evaluateTextArg(args[0], srcCellRow, srcCellCol);
int startCharNum = evaluateNumberArg(args[1], srcCellRow, srcCellCol);
numChars = evaluateNumberArg(args[2], srcCellRow, srcCellCol);
startIx = startCharNum - 1; // convert to zero-based
} catch (EvaluationException e) {
return e.getErrorEval();
}
int len = text.length();
if (startIx < 0) {
return ErrorEval.VALUE_INVALID;
}
if (numChars < 0) {
return ErrorEval.VALUE_INVALID;
}
if (numChars < 0 || startIx > len) {
return new StringEval("");
}
int endIx = startIx + numChars;
if (endIx > len) {
endIx = len;
}
String result = text.substring(startIx, endIx);
return new StringEval(result);
}
public static int evaluateNumberArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException {
ValueEval ev = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
if (ev instanceof BlankEval) {
// Note - for start_num arg, blank causes error(#VALUE!),
// but for num_chars causes empty string to be returned.
return 0;
}
return OperandResolver.coerceValueToInt(ev);
}
private static String evaluateTextArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException {
ValueEval ev = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
if (ev instanceof StringValueEval) {
return ((StringValueEval) ev).getStringValue();
}
throw EvaluationException.invalidValue();
}
}

View File

@ -35,6 +35,7 @@ public final class AllIndividualFunctionEvaluationTests {
result.addTestSuite(TestDate.class);
result.addTestSuite(TestFinanceLib.class);
result.addTestSuite(TestIndex.class);
result.addTestSuite(TestMid.class);
result.addTestSuite(TestMathX.class);
result.addTestSuite(TestMatch.class);
result.addTestSuite(TestRowCol.class);

View File

@ -0,0 +1,114 @@
/* ====================================================================
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.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.AreaPtg;
import org.apache.poi.hssf.record.formula.ReferencePtg;
import org.apache.poi.hssf.record.formula.eval.Area2DEval;
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.Ref2DEval;
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;
import junit.framework.TestCase;
/**
* Tests for Excel function MID()
*
* @author Josh Micich
*/
public final class TestMid extends TestCase {
private static Eval invokeMid(Eval text, Eval startPos, Eval numChars) {
Eval[] args = new Eval[] { text, startPos, numChars, };
return new Mid().evaluate(args, -1, (short)-1);
}
private void confirmMid(Eval text, Eval startPos, Eval numChars, String expected) {
Eval result = invokeMid(text, startPos, numChars);
assertEquals(StringEval.class, result.getClass());
assertEquals(expected, ((StringEval)result).getStringValue());
}
private void confirmMid(Eval text, Eval startPos, Eval numChars, ErrorEval expectedError) {
Eval result = invokeMid(text, startPos, numChars);
assertEquals(ErrorEval.class, result.getClass());
assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode());
}
public void testBasic() {
confirmMid(new StringEval("galactic"), new NumberEval(3), new NumberEval(4), "lact");
}
/**
* Valid cases where args are not precisely (string, int, int) but can be resolved OK.
*/
public void testUnusualArgs() {
// startPos with fractional digits
confirmMid(new StringEval("galactic"), new NumberEval(3.1), new NumberEval(4), "lact");
// string startPos
confirmMid(new StringEval("galactic"), new StringEval("3"), new NumberEval(4), "lact");
// text (first) arg type is number, other args are strings with fractional digits
confirmMid(new NumberEval(123456), new StringEval("3.1"), new StringEval("2.9"), "34");
// startPos is 1x1 area ref, numChars is cell ref
AreaEval aeStart = new Area2DEval(new AreaPtg("A1:A1"), new ValueEval[] { new NumberEval(2), } );
RefEval reNumChars = new Ref2DEval(new ReferencePtg("B1"), new NumberEval(3),false);
confirmMid(new StringEval("galactic"), aeStart, reNumChars, "ala");
confirmMid(new StringEval("galactic"), new NumberEval(3.1), BlankEval.INSTANCE, "");
confirmMid(new StringEval("galactic"), new NumberEval(3), BoolEval.FALSE, "");
confirmMid(new StringEval("galactic"), new NumberEval(3), BoolEval.TRUE, "l");
}
/**
* Extreme values for startPos and numChars
*/
public void testExtremes() {
confirmMid(new StringEval("galactic"), new NumberEval(4), new NumberEval(400), "actic");
confirmMid(new StringEval("galactic"), new NumberEval(30), new NumberEval(4), "");
confirmMid(new StringEval("galactic"), new NumberEval(3), new NumberEval(0), "");
}
/**
* All sorts of ways to make MID return defined errors.
*/
public void testErrors() {
confirmMid(ErrorEval.NAME_INVALID, new NumberEval(3), new NumberEval(4), ErrorEval.NAME_INVALID);
confirmMid(new StringEval("galactic"), ErrorEval.NAME_INVALID, new NumberEval(4), ErrorEval.NAME_INVALID);
confirmMid(new StringEval("galactic"), new NumberEval(3), ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID);
confirmMid(new StringEval("galactic"), ErrorEval.DIV_ZERO, ErrorEval.NAME_INVALID, ErrorEval.DIV_ZERO);
confirmMid(new StringEval("galactic"), BlankEval.INSTANCE, new NumberEval(3.1), ErrorEval.VALUE_INVALID);
confirmMid(new StringEval("galactic"), new NumberEval(0), new NumberEval(4), ErrorEval.VALUE_INVALID);
confirmMid(new StringEval("galactic"), new NumberEval(1), new NumberEval(-1), ErrorEval.VALUE_INVALID);
}
}