From 927e153c94947c3c237552eadf4658d7b1190f22 Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Sat, 24 Apr 2010 17:17:20 +0000 Subject: [PATCH] Added implementation for TEXT() and TRUNC(), see Bugzilla 49025 and 49026 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@937652 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/status.xml | 2 + .../record/formula/eval/FunctionEval.java | 3 +- .../formula/functions/NumericFunction.java | 22 ++++ .../formula/functions/TextFunction.java | 92 +++++++++++++++ .../record/formula/functions/TestText.java | 105 ++++++++++++++++++ .../record/formula/functions/TestTrunc.java | 59 ++++++++++ 6 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 src/testcases/org/apache/poi/hssf/record/formula/functions/TestText.java create mode 100644 src/testcases/org/apache/poi/hssf/record/formula/functions/TestTrunc.java diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index f3805fb6b..bc9fb9da0 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,8 @@ + 49026 - Added implementation for TEXT() + 49025 - Added implementation for TRUNC() 49147 - Properly close internal InputStream in ExtractorFactory#createExtractor(File) 49138 - Fixed locale-sensitive formatters in PackagePropertiesPart 49153 - Ensure that CTVectorVariant is included in poi-ooxml-schemas.jar diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java b/src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java index 7b17996dc..a50ac5997 100644 --- a/src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java @@ -94,6 +94,7 @@ public final class FunctionEval { retval[37] = BooleanFunction.OR; retval[38] = BooleanFunction.NOT; retval[39] = NumericFunction.MOD; + retval[48] = TextFunction.TEXT; retval[56] = FinanceFunction.PV; retval[57] = FinanceFunction.FV; @@ -152,7 +153,7 @@ public final class FunctionEval { retval[184] = NumericFunction.FACT; retval[190] = LogicalFunction.ISNONTEXT; - + retval[197] = NumericFunction.TRUNC; retval[198] = LogicalFunction.ISLOGICAL; retval[212] = NumericFunction.ROUNDUP; diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/NumericFunction.java b/src/java/org/apache/poi/hssf/record/formula/functions/NumericFunction.java index 28aed5cc2..bf69e6181 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/NumericFunction.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/NumericFunction.java @@ -26,6 +26,7 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval; /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * @author Josh Micich + * @author Stephen Wolke (smwolke at geistig.com) */ public abstract class NumericFunction implements Function { @@ -321,6 +322,27 @@ public abstract class NumericFunction implements Function { return MathX.roundUp(d0, (int)d1); } }; + static final NumberEval TRUNC_ARG2_DEFAULT = new NumberEval(0); + public static final Function TRUNC = new Var1or2ArgFunction() { + + public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { + return evaluate(srcRowIndex, srcColumnIndex, arg0, TRUNC_ARG2_DEFAULT); + } + + public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { + double result; + try { + double d0 = singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); + double d1 = singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); + double multi = Math.pow(10d,d1); + result = Math.floor(d0 * multi) / multi; + checkValue(result); + }catch (EvaluationException e) { + return e.getErrorEval(); + } + return new NumberEval(result); + } + }; /* -------------------------------------------------------------------------- */ diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/TextFunction.java b/src/java/org/apache/poi/hssf/record/formula/functions/TextFunction.java index 6b108f2f6..3e43ac28f 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/TextFunction.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/TextFunction.java @@ -17,6 +17,12 @@ package org.apache.poi.hssf.record.formula.functions; +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.GregorianCalendar; 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.EvaluationException; @@ -28,6 +34,7 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval; /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * @author Josh Micich + * @author Stephen Wolke (smwolke at geistig.com) */ public abstract class TextFunction implements Function { @@ -41,6 +48,11 @@ public abstract class TextFunction implements Function { ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); return OperandResolver.coerceValueToInt(ve); } + + protected static final double evaluateDoubleArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException { + ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); + return OperandResolver.coerceValueToDouble(ve); + } public final ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) { try { @@ -206,6 +218,86 @@ public abstract class TextFunction implements Function { } }; + /** + * An implementation of the TEXT function
+ * TEXT returns a number value formatted with the given + * number formatting string. This function is not a complete implementation of + * the Excel function. This function implements decimal formatting + * with the Java class DecimalFormat. For date formatting this function uses + * the SimpleDateFormat class.

+ * + * Syntax:
TEXT(value, format_text)
+ * + */ + public static final Function TEXT = new Fixed2ArgFunction() { + + public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { + double s0; + String s1; + try { + s0 = evaluateDoubleArg(arg0, srcRowIndex, srcColumnIndex); + s1 = evaluateStringArg(arg1, srcRowIndex, srcColumnIndex); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + if (s1.matches("[\\d,\\#,\\.,\\$,\\,]+")) { + NumberFormat formatter = new DecimalFormat(s1); + return new StringEval(formatter.format(s0)); + } else if (s1.indexOf("/") == s1.lastIndexOf("/") && s1.indexOf("/") >=0 && !s1.contains("-")) { + double wholePart = Math.floor(s0); + double decPart = s0 - wholePart; + if (wholePart * decPart == 0) { + return new StringEval("0"); + } + String[] parts = s1.split(" "); + String[] fractParts; + if (parts.length == 2) { + fractParts = parts[1].split("/"); + } else { + fractParts = s1.split("/"); + } + + if (fractParts.length == 2) { + double minVal = 1.0; + double currDenom = Math.pow(10 , fractParts[1].length()) - 1d; + double currNeum = 0; + for (int i = (int)(Math.pow(10, fractParts[1].length())- 1d); i > 0; i--) { + for(int i2 = (int)(Math.pow(10, fractParts[1].length())- 1d); i2 > 0; i2--){ + if (minVal >= Math.abs((double)i2/(double)i - decPart)) { + currDenom = i; + currNeum = i2; + minVal = Math.abs((double)i2/(double)i - decPart); + } + } + } + NumberFormat neumFormatter = new DecimalFormat(fractParts[0]); + NumberFormat denomFormatter = new DecimalFormat(fractParts[1]); + if (parts.length == 2) { + NumberFormat wholeFormatter = new DecimalFormat(parts[0]); + String result = wholeFormatter.format(wholePart) + " " + neumFormatter.format(currNeum) + "/" + denomFormatter.format(currDenom); + return new StringEval(result); + } else { + String result = neumFormatter.format(currNeum + (currDenom * wholePart)) + "/" + denomFormatter.format(currDenom); + return new StringEval(result); + } + } else { + return ErrorEval.VALUE_INVALID; + } + } else { + try { + DateFormat dateFormatter = new SimpleDateFormat(s1); + Calendar cal = new GregorianCalendar(1899, 11, 30, 0, 0, 0); + cal.add(Calendar.DATE, (int)Math.floor(s0)); + double dayFraction = s0 - Math.floor(s0); + cal.add(Calendar.MILLISECOND, (int) Math.round(dayFraction * 24 * 60 * 60 * 1000)); + return new StringEval(dateFormatter.format(cal.getTime())); + } catch (Exception e) { + return ErrorEval.VALUE_INVALID; + } + } + } + }; + private static final class SearchFind extends Var2or3ArgFunction { private final boolean _isCaseSensitive; diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestText.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestText.java new file mode 100644 index 000000000..c3e429bc1 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestText.java @@ -0,0 +1,105 @@ +/* ==================================================================== + 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.NumberEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; + +/** + * Test case for TEXT() + * + * @author Stephen Wolke (smwolke at geistig.com) + */ +public final class TestText extends TestCase { + private static final TextFunction T = null; + + public void testTextWithStringFirstArg() { + + ValueEval strArg = new StringEval("abc"); + ValueEval formatArg = new StringEval("abc"); + ValueEval[] args = { strArg, formatArg }; + ValueEval result = T.TEXT.evaluate(args, -1, (short)-1); + assertEquals(ErrorEval.VALUE_INVALID, result); + } + + public void testTextWithDeciamlFormatSecondArg() { + + ValueEval numArg = new NumberEval(321321.321); + ValueEval formatArg = new StringEval("#,###.00000"); + ValueEval[] args = { numArg, formatArg }; + ValueEval result = T.TEXT.evaluate(args, -1, (short)-1); + ValueEval testResult = new StringEval("321,321.32100"); + assertEquals(testResult.toString(), result.toString()); + numArg = new NumberEval(321.321); + formatArg = new StringEval("00000.00000"); + args[0] = numArg; + args[1] = formatArg; + result = T.TEXT.evaluate(args, -1, (short)-1); + testResult = new StringEval("00321.32100"); + assertEquals(testResult.toString(), result.toString()); + + formatArg = new StringEval("$#.#"); + args[1] = formatArg; + result = T.TEXT.evaluate(args, -1, (short)-1); + testResult = new StringEval("$321.3"); + assertEquals(testResult.toString(), result.toString()); + } + + public void testTextWithFractionFormatSecondArg() { + + ValueEval numArg = new NumberEval(321.321); + ValueEval formatArg = new StringEval("# #/#"); + ValueEval[] args = { numArg, formatArg }; + ValueEval result = T.TEXT.evaluate(args, -1, (short)-1); + ValueEval testResult = new StringEval("321 1/3"); + assertEquals(testResult.toString(), result.toString()); + + formatArg = new StringEval("# #/##"); + args[1] = formatArg; + result = T.TEXT.evaluate(args, -1, (short)-1); + testResult = new StringEval("321 26/81"); + assertEquals(testResult.toString(), result.toString()); + + formatArg = new StringEval("#/##"); + args[1] = formatArg; + result = T.TEXT.evaluate(args, -1, (short)-1); + testResult = new StringEval("26027/81"); + assertEquals(testResult.toString(), result.toString()); + } + + public void testTextWithDateFormatSecondArg() { + + ValueEval numArg = new NumberEval(321.321); + ValueEval formatArg = new StringEval("dd:MM:yyyy hh:mm:ss"); + ValueEval[] args = { numArg, formatArg }; + ValueEval result = T.TEXT.evaluate(args, -1, (short)-1); + ValueEval testResult = new StringEval("16:11:1900 07:42:14"); + assertEquals(testResult.toString(), result.toString()); + + formatArg = new StringEval("MMMM dd, yyyy"); + args[1] = formatArg; + result = T.TEXT.evaluate(args, -1, (short)-1); + testResult = new StringEval("November 16, 1900"); + assertEquals(testResult.toString(), result.toString()); + } + + +} diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestTrunc.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestTrunc.java new file mode 100644 index 000000000..ef42d1ce0 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestTrunc.java @@ -0,0 +1,59 @@ +/* ==================================================================== + 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.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; + +/** + * Test case for TRUNC() + * + * @author Stephen Wolke (smwolke at geistig.com) + */ +public final class TestTrunc extends AbstractNumericTestCase { + private static final NumericFunction F = null; + public void testTruncWithStringArg() { + + ValueEval strArg = new StringEval("abc"); + ValueEval[] args = { strArg, new NumberEval(2) }; + ValueEval result = F.TRUNC.evaluate(args, -1, (short)-1); + assertEquals(ErrorEval.VALUE_INVALID, result); + } + + public void testTruncWithWholeNumber() { + ValueEval[] args = { new NumberEval(200), new NumberEval(2) }; + @SuppressWarnings("static-access") + ValueEval result = F.TRUNC.evaluate(args, -1, (short)-1); + assertEquals("TRUNC", (new NumberEval(200d)).getNumberValue(), ((NumberEval)result).getNumberValue()); + } + + public void testTruncWithDecimalNumber() { + ValueEval[] args = { new NumberEval(2.612777), new NumberEval(3) }; + @SuppressWarnings("static-access") + ValueEval result = F.TRUNC.evaluate(args, -1, (short)-1); + assertEquals("TRUNC", (new NumberEval(2.612d)).getNumberValue(), ((NumberEval)result).getNumberValue()); + } + + public void testTruncWithDecimalNumberOneArg() { + ValueEval[] args = { new NumberEval(2.612777) }; + ValueEval result = F.TRUNC.evaluate(args, -1, (short)-1); + assertEquals("TRUNC", (new NumberEval(2d)).getNumberValue(), ((NumberEval)result).getNumberValue()); + } +}