From: Josh Micich Date: Mon, 27 Oct 2008 18:16:44 +0000 (+0000) Subject: Bugzilla 46065 - added implementation for VALUE function X-Git-Tag: trunk_20081106~26 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=2c1e400bc0deb74fe72b9d1f77b56a40e83ff17e;p=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 --- diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 6a37e74f9a..0bd615c15b 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + 46065 - added implementation for VALUE function 45966 - added implementation for FIND function 45778 - fixed ObjRecord to read ftLbsData properly 46053 - fixed evaluation cache dependency analysis when changing blank cells diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 5dc90fdea4..c704553b5b 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 46065 - added implementation for VALUE function 45966 - added implementation for FIND function 45778 - fixed ObjRecord to read ftLbsData properly 46053 - fixed evaluation cache dependency analysis when changing blank cells diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Value.java b/src/java/org/apache/poi/hssf/record/formula/functions/Value.java index 6772dcf283..84f61db98e 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Value.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Value.java @@ -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.

+ * + * Syntax:
VALUE(text)
+ * + * 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 #VALUE! 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 null 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); + } } diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java index df16d208cc..834b5281d8 100755 --- a/src/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java @@ -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; } diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestValue.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestValue.java new file mode 100644 index 0000000000..5f74fbf1a6 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestValue.java @@ -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"); + } +}