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
This commit is contained in:
Yegor Kozlov 2010-04-24 17:17:20 +00:00
parent 50cde157ec
commit 5a80808a73
6 changed files with 282 additions and 1 deletions

View File

@ -34,6 +34,8 @@
<changes>
<release version="3.7-SNAPSHOT" date="2010-??-??">
<action dev="POI-DEVELOPERS" type="add">49026 - Added implementation for TEXT() </action>
<action dev="POI-DEVELOPERS" type="add">49025 - Added implementation for TRUNC() </action>
<action dev="POI-DEVELOPERS" type="fix">49147 - Properly close internal InputStream in ExtractorFactory#createExtractor(File)</action>
<action dev="POI-DEVELOPERS" type="fix">49138 - Fixed locale-sensitive formatters in PackagePropertiesPart</action>
<action dev="POI-DEVELOPERS" type="fix">49153 - Ensure that CTVectorVariant is included in poi-ooxml-schemas.jar</action>

View File

@ -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;

View File

@ -26,6 +26,7 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
* @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);
}
};
/* -------------------------------------------------------------------------- */

View File

@ -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 &lt; amolweb at ya hoo dot com &gt;
* @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<br/>
* 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.<p/>
*
* <b>Syntax<b>:<br/> <b>TEXT</b>(<b>value</b>, <b>format_text</b>)<br/>
*
*/
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;

View File

@ -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());
}
}

View File

@ -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());
}
}