]> source.dussan.org Git - poi.git/commitdiff
Bugzilla 46065 - added implementation for VALUE function
authorJosh Micich <josh@apache.org>
Mon, 27 Oct 2008 18:16:44 +0000 (18:16 +0000)
committerJosh Micich <josh@apache.org>
Mon, 27 Oct 2008 18:16:44 +0000 (18:16 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@708262 13f79535-47bb-0310-9956-ffa450edef68

src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/record/formula/functions/Value.java
src/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java
src/testcases/org/apache/poi/hssf/record/formula/functions/TestValue.java [new file with mode: 0644]

index 6a37e74f9ad0caeb0bf1a893fa291d780fb55424..0bd615c15b1aad7004fa893aab02b4aa01799b21 100644 (file)
@@ -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>
index 5dc90fdea42d02a303383056b37919928083b531..c704553b5ba275765ebd34c14fe93f24cf7b79f8 100644 (file)
@@ -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>
index 6772dcf2836e2e4af5ae38829b1388689914a64b..84f61db98e7b79539015fc52a15acefda7739a10 100644 (file)
-/*
-* 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);
+       }
 }
index df16d208ccb0aab46e9934fdc318277bfced714e..834b5281d893dc38b2d92dcc8dfccae6fa3b4cc1 100755 (executable)
@@ -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 (file)
index 0000000..5f74fbf
--- /dev/null
@@ -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");
+       }
+}