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
09f6464163
commit
2af707980e
@ -34,6 +34,7 @@
|
|||||||
|
|
||||||
<changes>
|
<changes>
|
||||||
<release version="3.8-beta6" date="2012-??-??">
|
<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">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 - 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>
|
<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;
|
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.BoolEval;
|
||||||
import org.apache.poi.ss.formula.eval.ErrorEval;
|
import org.apache.poi.ss.formula.eval.ErrorEval;
|
||||||
import org.apache.poi.ss.formula.eval.EvaluationException;
|
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/>
|
* An implementation of the TEXT function<br/>
|
||||||
* TEXT returns a number value formatted with the given
|
* TEXT returns a number value formatted with the given number formatting string.
|
||||||
* number formatting string. This function is not a complete implementation of
|
* This function is not a complete implementation of the Excel function, but
|
||||||
* the Excel function. This function implements decimal formatting
|
* handles most of the common cases. All work is passed down to
|
||||||
* with the Java class DecimalFormat. For date formatting, this function uses
|
* {@link DataFormatter} to be done, as this works much the same as the
|
||||||
* {@link DataFormatter}, which attempts to replicate the Excel date
|
* display focused work that that does.
|
||||||
* format string.
|
|
||||||
*
|
|
||||||
* TODO Merge much of this logic with {@link DataFormatter}
|
|
||||||
*
|
*
|
||||||
* <b>Syntax<b>:<br/> <b>TEXT</b>(<b>value</b>, <b>format_text</b>)<br/>
|
* <b>Syntax<b>:<br/> <b>TEXT</b>(<b>value</b>, <b>format_text</b>)<br/>
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public static final Function TEXT = new Fixed2ArgFunction() {
|
public static final Function TEXT = new Fixed2ArgFunction() {
|
||||||
|
|
||||||
@ -302,57 +292,13 @@ public abstract class TextFunction implements Function {
|
|||||||
} catch (EvaluationException e) {
|
} catch (EvaluationException e) {
|
||||||
return e.getErrorEval();
|
return e.getErrorEval();
|
||||||
}
|
}
|
||||||
if (s1.matches("[\\d,\\#,\\.,\\$,\\,]+")) {
|
|
||||||
NumberFormat formatter = new DecimalFormat(s1);
|
try {
|
||||||
return new StringEval(formatter.format(s0));
|
// Ask DataFormatter to handle the String for us
|
||||||
} else if (s1.indexOf("/") == s1.lastIndexOf("/") && s1.indexOf("/") >=0 && !s1.contains("-")) {
|
String formattedStr = formatter.formatRawCellContents(s0, -1, s1);
|
||||||
double wholePart = Math.floor(s0);
|
return new StringEval(formattedStr);
|
||||||
double decPart = s0 - wholePart;
|
} catch (Exception e) {
|
||||||
if (wholePart * decPart == 0) {
|
return ErrorEval.VALUE_INVALID;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -16,13 +16,28 @@
|
|||||||
==================================================================== */
|
==================================================================== */
|
||||||
package org.apache.poi.ss.usermodel;
|
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.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.math.RoundingMode;
|
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
|
* 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")) {
|
if (emulateCsv && cellValue == 0.0 && formatStr.contains("#") && !formatStr.contains("0")) {
|
||||||
formatStr = formatStr.replaceAll("#", "");
|
formatStr = formatStr.replaceAll("#", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// See if we already have it cached
|
// See if we already have it cached
|
||||||
Format format = formats.get(formatStr);
|
Format format = formats.get(formatStr);
|
||||||
if (format != null) {
|
if (format != null) {
|
||||||
@ -332,6 +347,13 @@ public class DataFormatter {
|
|||||||
DateUtil.isValidExcelDate(cellValue)) {
|
DateUtil.isValidExcelDate(cellValue)) {
|
||||||
return createDateFormat(formatStr, 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()) {
|
if (numPattern.matcher(formatStr).find()) {
|
||||||
return createNumberFormat(formatStr, cellValue);
|
return createNumberFormat(formatStr, cellValue);
|
||||||
}
|
}
|
||||||
@ -946,6 +968,67 @@ public class DataFormatter {
|
|||||||
return df.parseObject(source, pos);
|
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.
|
* 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));
|
// 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")
|
* Test that _x (blank with the space taken by "x")
|
||||||
* and *x (fill to the column width with "x"s) are
|
* and *x (fill to the column width with "x"s) are
|
||||||
|
Loading…
Reference in New Issue
Block a user