Inspired by bug #52349 - Merge the logic between the TEXT function and DataFormatter

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1221126 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2011-12-20 07:20:44 +00:00
parent b3709bbd36
commit ca28206971
4 changed files with 113 additions and 71 deletions

View File

@ -34,6 +34,7 @@
<changes>
<release version="3.8-beta6" date="2012-??-??">
<action dev="poi-developers" type="add">52349 - Merge the logic between the TEXT function and DataFormatter</action>
<action dev="poi-developers" type="fix">52349 - Correctly support excel style date format strings in the TEXT function</action>
<action dev="poi-developers" type="fix">52369 - XSSFExcelExtractor should format numeric cells based on the format strings applied to them</action>
<action dev="poi-developers" type="fix">52369 - Event based XSSF parsing should handle formatting of formula values in XSSFSheetXMLHandler</action>

View File

@ -17,12 +17,6 @@
package org.apache.poi.ss.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.ss.formula.eval.BoolEval;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException;
@ -279,17 +273,13 @@ 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
* {@link DataFormatter}, which attempts to replicate the Excel date
* format string.
*
* TODO Merge much of this logic with {@link DataFormatter}
* TEXT returns a number value formatted with the given number formatting string.
* This function is not a complete implementation of the Excel function, but
* handles most of the common cases. All work is passed down to
* {@link DataFormatter} to be done, as this works much the same as the
* display focused work that that does.
*
* <b>Syntax<b>:<br/> <b>TEXT</b>(<b>value</b>, <b>format_text</b>)<br/>
*
*/
public static final Function TEXT = new Fixed2ArgFunction() {
@ -302,57 +292,13 @@ public abstract class TextFunction implements Function {
} 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 {
// Ask DataFormatter to handle the Date string for us
String formattedDate = formatter.formatRawCellContents(s0, -1, s1);
return new StringEval(formattedDate);
} catch (Exception e) {
return ErrorEval.VALUE_INVALID;
}
try {
// Ask DataFormatter to handle the String for us
String formattedStr = formatter.formatRawCellContents(s0, -1, s1);
return new StringEval(formattedStr);
} catch (Exception e) {
return ErrorEval.VALUE_INVALID;
}
}
};

View File

@ -16,13 +16,28 @@
==================================================================== */
package org.apache.poi.ss.usermodel;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.RoundingMode;
import java.text.*;
import java.text.DateFormatSymbols;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.FieldPosition;
import java.text.Format;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.poi.ss.formula.eval.NotImplementedException;
/**
* DataFormatter contains methods for formatting the value stored in an
@ -257,7 +272,7 @@ public class DataFormatter {
if (emulateCsv && cellValue == 0.0 && formatStr.contains("#") && !formatStr.contains("0")) {
formatStr = formatStr.replaceAll("#", "");
}
// See if we already have it cached
Format format = formats.get(formatStr);
if (format != null) {
@ -332,6 +347,13 @@ public class DataFormatter {
DateUtil.isValidExcelDate(cellValue)) {
return createDateFormat(formatStr, cellValue);
}
// Excel supports fractions in format strings, which Java doesn't
if (formatStr.indexOf("/") == formatStr.lastIndexOf("/") &&
formatStr.indexOf("/") >= 0 && !formatStr.contains("-")) {
return new FractionFormat(formatStr);
}
if (numPattern.matcher(formatStr).find()) {
return createNumberFormat(formatStr, cellValue);
}
@ -946,6 +968,67 @@ public class DataFormatter {
return df.parseObject(source, pos);
}
}
/**
* Format class that handles Excel style fractions, such as "# #/#" and "#/###"
*/
@SuppressWarnings("serial")
private static final class FractionFormat extends Format {
private final String str;
public FractionFormat(String s) {
str = s;
}
public String format(Number num) {
double wholePart = Math.floor(num.doubleValue());
double decPart = num.doubleValue() - wholePart;
if (wholePart * decPart == 0) {
return "0";
}
String[] parts = str.split(" ");
String[] fractParts;
if (parts.length == 2) {
fractParts = parts[1].split("/");
} else {
fractParts = str.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 result;
} else {
String result = neumFormatter.format(currNeum + (currDenom * wholePart)) + "/" + denomFormatter.format(currDenom);
return result;
}
} else {
throw new IllegalArgumentException("Fraction must have 2 parts, found " + fractParts.length + " for fraction format " + str);
}
}
public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
return toAppendTo.append(format((Number)obj));
}
public Object parseObject(String source, ParsePosition pos) {
throw new NotImplementedException("Reverse parsing not supported");
}
}
/**
* Format class that does nothing and always returns a constant string.

View File

@ -161,6 +161,18 @@ public class TestDataFormatter extends TestCase {
// assertEquals("(12.3)", dfUS.formatRawCellContents(-12.343, -1, p2dp_n1dpTSP));
}
/**
* Test that we correctly handle fractions in the
* format string, eg # #/#
*/
public void testFractions() {
DataFormatter dfUS = new DataFormatter(Locale.US);
assertEquals("321 1/3", dfUS.formatRawCellContents(321.321, -1, "# #/#"));
assertEquals("321 26/81", dfUS.formatRawCellContents(321.321, -1, "# #/##"));
assertEquals("26027/81", dfUS.formatRawCellContents(321.321, -1, "#/##"));
}
/**
* Test that _x (blank with the space taken by "x")
* and *x (fill to the column width with "x"s) are