mirror of https://github.com/apache/poi.git
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:
parent
b3709bbd36
commit
ca28206971
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue