<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>
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;
/**
* 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() {
} 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;
}
}
};
==================================================================== */
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
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) {
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);
}
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.
// 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