From 2af707980e73d751c973769362ed3bbc6e832400 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Tue, 20 Dec 2011 07:20:44 +0000 Subject: [PATCH] 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 --- src/documentation/content/xdocs/status.xml | 1 + .../ss/formula/functions/TextFunction.java | 78 +++------------- .../poi/ss/usermodel/DataFormatter.java | 93 ++++++++++++++++++- .../poi/ss/usermodel/TestDataFormatter.java | 12 +++ 4 files changed, 113 insertions(+), 71 deletions(-) diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index eead9ffc0..caedc5980 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 52349 - Merge the logic between the TEXT function and DataFormatter 52349 - Correctly support excel style date format strings in the TEXT function 52369 - XSSFExcelExtractor should format numeric cells based on the format strings applied to them 52369 - Event based XSSF parsing should handle formatting of formula values in XSSFSheetXMLHandler diff --git a/src/java/org/apache/poi/ss/formula/functions/TextFunction.java b/src/java/org/apache/poi/ss/formula/functions/TextFunction.java index dc75b0340..1125ae55a 100644 --- a/src/java/org/apache/poi/ss/formula/functions/TextFunction.java +++ b/src/java/org/apache/poi/ss/formula/functions/TextFunction.java @@ -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
- * 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. * * Syntax:
TEXT(value, format_text)
- * */ 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; } } }; diff --git a/src/java/org/apache/poi/ss/usermodel/DataFormatter.java b/src/java/org/apache/poi/ss/usermodel/DataFormatter.java index 9ae8b6bb5..9c56adaa4 100644 --- a/src/java/org/apache/poi/ss/usermodel/DataFormatter.java +++ b/src/java/org/apache/poi/ss/usermodel/DataFormatter.java @@ -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. diff --git a/src/testcases/org/apache/poi/ss/usermodel/TestDataFormatter.java b/src/testcases/org/apache/poi/ss/usermodel/TestDataFormatter.java index c54829ffa..e11ab2d60 100644 --- a/src/testcases/org/apache/poi/ss/usermodel/TestDataFormatter.java +++ b/src/testcases/org/apache/poi/ss/usermodel/TestDataFormatter.java @@ -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