]> source.dussan.org Git - poi.git/commitdiff
Inspired by bug #52349 - Merge the logic between the TEXT function and DataFormatter
authorNick Burch <nick@apache.org>
Tue, 20 Dec 2011 07:20:44 +0000 (07:20 +0000)
committerNick Burch <nick@apache.org>
Tue, 20 Dec 2011 07:20:44 +0000 (07:20 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1221126 13f79535-47bb-0310-9956-ffa450edef68

src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/ss/formula/functions/TextFunction.java
src/java/org/apache/poi/ss/usermodel/DataFormatter.java
src/testcases/org/apache/poi/ss/usermodel/TestDataFormatter.java

index eead9ffc07ea9cb6056a5635adbc0e20b983a09b..caedc59802faf5ce058f2818c86eb63bd95441c2 100644 (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>
index dc75b03401993bd55542509c22fccd312b205dca..1125ae55a81f04136e410aa8f728f77e06d6778c 100644 (file)
 
 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;
                        }
                }
        };
index 9ae8b6bb5f86b0dcbb800e941d62ad0d8e49c32e..9c56adaa47228ca1b6ee9a2d21babc061458c4f4 100644 (file)
 ==================================================================== */
 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.
index c54829ffa621a34494d79d77ff609c5b49fab512..e11ab2d60f0184b776b42348c8b1f5c571d41ea8 100644 (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