mirror of https://github.com/apache/poi.git
Bugzilla 46065 - added implementation for VALUE function
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@708262 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
200ba997ac
commit
2c1e400bc0
|
@ -37,6 +37,7 @@
|
|||
|
||||
<!-- Don't forget to update status.xml too! -->
|
||||
<release version="3.5-beta4" date="2008-??-??">
|
||||
<action dev="POI-DEVELOPERS" type="add">46065 - added implementation for VALUE function</action>
|
||||
<action dev="POI-DEVELOPERS" type="add">45966 - added implementation for FIND function</action>
|
||||
<action dev="POI-DEVELOPERS" type="fix">45778 - fixed ObjRecord to read ftLbsData properly</action>
|
||||
<action dev="POI-DEVELOPERS" type="fix">46053 - fixed evaluation cache dependency analysis when changing blank cells</action>
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
<!-- Don't forget to update changes.xml too! -->
|
||||
<changes>
|
||||
<release version="3.5-beta4" date="2008-??-??">
|
||||
<action dev="POI-DEVELOPERS" type="add">46065 - added implementation for VALUE function</action>
|
||||
<action dev="POI-DEVELOPERS" type="add">45966 - added implementation for FIND function</action>
|
||||
<action dev="POI-DEVELOPERS" type="fix">45778 - fixed ObjRecord to read ftLbsData properly</action>
|
||||
<action dev="POI-DEVELOPERS" type="fix">46053 - fixed evaluation cache dependency analysis when changing blank cells</action>
|
||||
|
|
|
@ -1,25 +1,188 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
public class Value extends NotImplementedFunction {
|
||||
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.Eval;
|
||||
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
|
||||
import org.apache.poi.hssf.record.formula.eval.NumberEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
|
||||
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
||||
|
||||
/**
|
||||
* Implementation for Excel VALUE() function.<p/>
|
||||
*
|
||||
* <b>Syntax</b>:<br/> <b>VALUE</b>(<b>text</b>)<br/>
|
||||
*
|
||||
* Converts the text argument to a number. Leading and/or trailing whitespace is
|
||||
* ignored. Currency symbols and thousands separators are stripped out.
|
||||
* Scientific notation is also supported. If the supplied text does not convert
|
||||
* properly the result is <b>#VALUE!</b> error. Blank string converts to zero.
|
||||
*
|
||||
* @author Josh Micich
|
||||
*/
|
||||
public final class Value implements Function {
|
||||
|
||||
/** "1,0000" is valid, "1,00" is not */
|
||||
private static final int MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR = 4;
|
||||
private static final Double ZERO = new Double(0.0);
|
||||
|
||||
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
|
||||
if (args.length != 1) {
|
||||
return ErrorEval.VALUE_INVALID;
|
||||
}
|
||||
ValueEval veText;
|
||||
try {
|
||||
veText = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
|
||||
} catch (EvaluationException e) {
|
||||
return e.getErrorEval();
|
||||
}
|
||||
String strText = OperandResolver.coerceValueToString(veText);
|
||||
Double result = convertTextToNumber(strText);
|
||||
if (result == null) {
|
||||
return ErrorEval.VALUE_INVALID;
|
||||
}
|
||||
return new NumberEval(result.doubleValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO see if the same functionality is needed in {@link OperandResolver#parseDouble(String)}
|
||||
*
|
||||
* @return <code>null</code> if there is any problem converting the text
|
||||
*/
|
||||
private static Double convertTextToNumber(String strText) {
|
||||
boolean foundCurrency = false;
|
||||
boolean foundUnaryPlus = false;
|
||||
boolean foundUnaryMinus = false;
|
||||
|
||||
int len = strText.length();
|
||||
int i;
|
||||
for (i = 0; i < len; i++) {
|
||||
char ch = strText.charAt(i);
|
||||
if (Character.isDigit(ch) || ch == '.') {
|
||||
break;
|
||||
}
|
||||
switch (ch) {
|
||||
case ' ':
|
||||
// intervening spaces between '$', '-', '+' are OK
|
||||
continue;
|
||||
case '$':
|
||||
if (foundCurrency) {
|
||||
// only one currency symbols is allowed
|
||||
return null;
|
||||
}
|
||||
foundCurrency = true;
|
||||
continue;
|
||||
case '+':
|
||||
if (foundUnaryMinus || foundUnaryPlus) {
|
||||
return null;
|
||||
}
|
||||
foundUnaryPlus = true;
|
||||
continue;
|
||||
case '-':
|
||||
if (foundUnaryMinus || foundUnaryPlus) {
|
||||
return null;
|
||||
}
|
||||
foundUnaryMinus = true;
|
||||
continue;
|
||||
default:
|
||||
// all other characters are illegal
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (i >= len) {
|
||||
// didn't find digits or '.'
|
||||
if (foundCurrency || foundUnaryMinus || foundUnaryPlus) {
|
||||
return null;
|
||||
}
|
||||
return ZERO;
|
||||
}
|
||||
|
||||
// remove thousands separators
|
||||
|
||||
boolean foundDecimalPoint = false;
|
||||
int lastThousandsSeparatorIndex = Short.MIN_VALUE;
|
||||
|
||||
StringBuffer sb = new StringBuffer(len);
|
||||
for (; i < len; i++) {
|
||||
char ch = strText.charAt(i);
|
||||
if (Character.isDigit(ch)) {
|
||||
sb.append(ch);
|
||||
continue;
|
||||
}
|
||||
switch (ch) {
|
||||
case ' ':
|
||||
String remainingText = strText.substring(i);
|
||||
if (remainingText.trim().length() > 0) {
|
||||
// intervening spaces not allowed once the digits start
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case '.':
|
||||
if (foundDecimalPoint) {
|
||||
return null;
|
||||
}
|
||||
if (i - lastThousandsSeparatorIndex < MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR) {
|
||||
return null;
|
||||
}
|
||||
foundDecimalPoint = true;
|
||||
sb.append('.');
|
||||
continue;
|
||||
case ',':
|
||||
if (foundDecimalPoint) {
|
||||
// thousands separators not allowed after '.' or 'E'
|
||||
return null;
|
||||
}
|
||||
int distanceBetweenThousandsSeparators = i - lastThousandsSeparatorIndex;
|
||||
// as long as there are 3 or more digits between
|
||||
if (distanceBetweenThousandsSeparators < MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR) {
|
||||
return null;
|
||||
}
|
||||
lastThousandsSeparatorIndex = i;
|
||||
// don't append ','
|
||||
continue;
|
||||
|
||||
case 'E':
|
||||
case 'e':
|
||||
if (i - lastThousandsSeparatorIndex < MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR) {
|
||||
return null;
|
||||
}
|
||||
// append rest of strText and skip to end of loop
|
||||
sb.append(strText.substring(i));
|
||||
i = len;
|
||||
break;
|
||||
default:
|
||||
// all other characters are illegal
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (!foundDecimalPoint) {
|
||||
if (i - lastThousandsSeparatorIndex < MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
double d;
|
||||
try {
|
||||
d = Double.parseDouble(sb.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
// still a problem parsing the number - probably out of range
|
||||
return null;
|
||||
}
|
||||
return new Double(foundUnaryMinus ? -d : d);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ public final class AllIndividualFunctionEvaluationTests {
|
|||
result.addTestSuite(TestStatsLib.class);
|
||||
result.addTestSuite(TestTFunc.class);
|
||||
result.addTestSuite(TestTrim.class);
|
||||
result.addTestSuite(TestValue.class);
|
||||
result.addTestSuite(TestXYNumericFunction.class);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/* ====================================================================
|
||||
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 junit.framework.TestCase;
|
||||
|
||||
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.StringEval;
|
||||
|
||||
/**
|
||||
* Tests for {@link Value}
|
||||
*
|
||||
* @author Josh Micich
|
||||
*/
|
||||
public final class TestValue extends TestCase {
|
||||
|
||||
private static Eval invokeValue(String strText) {
|
||||
Eval[] args = new Eval[] { new StringEval(strText), };
|
||||
return new Value().evaluate(args, -1, (short) -1);
|
||||
}
|
||||
|
||||
private static void confirmValue(String strText, double expected) {
|
||||
Eval result = invokeValue(strText);
|
||||
assertEquals(NumberEval.class, result.getClass());
|
||||
assertEquals(expected, ((NumberEval) result).getNumberValue(), 0.0);
|
||||
}
|
||||
|
||||
private static void confirmValueError(String strText) {
|
||||
Eval result = invokeValue(strText);
|
||||
assertEquals(ErrorEval.class, result.getClass());
|
||||
assertEquals(ErrorEval.VALUE_INVALID, result);
|
||||
}
|
||||
|
||||
public void testBasic() {
|
||||
|
||||
confirmValue("100", 100);
|
||||
confirmValue("-2.3", -2.3);
|
||||
confirmValue(".5", 0.5);
|
||||
confirmValue(".5e2", 50);
|
||||
confirmValue(".5e-2", 0.005);
|
||||
confirmValue(".5e+2", 50);
|
||||
confirmValue("+5", 5);
|
||||
confirmValue("$1,000", 1000);
|
||||
confirmValue("100.5e1", 1005);
|
||||
confirmValue("1,0000", 10000);
|
||||
confirmValue("1,000,0000", 10000000);
|
||||
confirmValue("1,000,0000,00000", 1000000000000.0);
|
||||
confirmValue(" 100 ", 100);
|
||||
confirmValue(" + 100", 100);
|
||||
confirmValue("10000", 10000);
|
||||
confirmValue("$-5", -5);
|
||||
confirmValue("$.5", 0.5);
|
||||
confirmValue("123e+5", 12300000);
|
||||
confirmValue("1,000e2", 100000);
|
||||
confirmValue("$10e2", 1000);
|
||||
confirmValue("$1,000e2", 100000);
|
||||
}
|
||||
|
||||
public void testErrors() {
|
||||
confirmValueError("1+1");
|
||||
confirmValueError("1 1");
|
||||
confirmValueError("1,00.0");
|
||||
confirmValueError("1,00");
|
||||
confirmValueError("$1,00.5e1");
|
||||
confirmValueError("1,00.5e1");
|
||||
confirmValueError("1,0,000");
|
||||
confirmValueError("1,00,000");
|
||||
confirmValueError("++100");
|
||||
confirmValueError("$$5");
|
||||
confirmValueError("-");
|
||||
confirmValueError("+");
|
||||
confirmValueError("$");
|
||||
confirmValueError(",300");
|
||||
confirmValueError("0.233,4");
|
||||
confirmValueError("1e2.5");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue