]> granicus.if.org Git - icu/commitdiff
ICU-10633 Implement context-sensitive number formatting (RBNF for J), handle serializ...
authorPeter Edberg <pedberg@unicode.org>
Mon, 3 Mar 2014 19:48:34 +0000 (19:48 +0000)
committerPeter Edberg <pedberg@unicode.org>
Mon, 3 Mar 2014 19:48:34 +0000 (19:48 +0000)
X-SVN-Rev: 35301

icu4j/main/classes/core/src/com/ibm/icu/text/NumberFormat.java
icu4j/main/classes/core/src/com/ibm/icu/text/RuleBasedNumberFormat.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/RbnfTest.java

index 5d4134970e1ad4afec13eedd007b72027cb0fb59..1c22e2048deecf5309ce90f65b5d19cf26eb60c7 100644 (file)
@@ -1047,7 +1047,8 @@ public abstract class NumberFormat extends UFormat {
             && minimumFractionDigits == other.minimumFractionDigits
             && groupingUsed == other.groupingUsed
             && parseIntegerOnly == other.parseIntegerOnly
-            && parseStrict == other.parseStrict;
+            && parseStrict == other.parseStrict
+            && capitalizationSetting == other.capitalizationSetting;
     }
 
     /**
@@ -1528,6 +1529,10 @@ public abstract class NumberFormat extends UFormat {
             maximumFractionDigits = maxFractionDigits;
             minimumFractionDigits = minFractionDigits;
         }
+        if (serialVersionOnStream < 2) {
+            // Didn't have capitalizationSetting, set it to default
+            capitalizationSetting = DisplayContext.CAPITALIZATION_NONE;
+        }
         ///CLOVER:ON
         /*Bug 4185761
           Validate the min and max fields [Richard/GCL]
@@ -1714,7 +1719,7 @@ public abstract class NumberFormat extends UFormat {
      */
     private Currency currency;
 
-    static final int currentSerialVersion = 1;
+    static final int currentSerialVersion = 2;
 
     /**
      * Describes the version of <code>NumberFormat</code> present on the stream.
@@ -1729,6 +1734,8 @@ public abstract class NumberFormat extends UFormat {
      *     <code>byte</code> fields such as <code>maxIntegerDigits</code> are ignored,
      *     and the <code>int</code> fields such as <code>maximumIntegerDigits</code>
      *     are used instead.
+     *
+     * <li><b>2</b>: adds capitalizationSetting.
      * </ul>
      * When streaming out a <code>NumberFormat</code>, the most recent format
      * (corresponding to the highest allowable <code>serialVersionOnStream</code>)
@@ -1755,9 +1762,10 @@ public abstract class NumberFormat extends UFormat {
     private boolean parseStrict;
 
     /*
-     *  Capitalization setting, new in ICU 53
+     * Capitalization context setting, new in ICU 53
+     * @serial
      */
-    private transient DisplayContext capitalizationSetting = DisplayContext.CAPITALIZATION_NONE;
+    private DisplayContext capitalizationSetting = DisplayContext.CAPITALIZATION_NONE;
 
     /**
      * The instances of this inner class are used as attribute keys and values
index b6b638d5b387f00dd7e3512babd855b4c2a571c8..5932fcbe47b48edfaff84cca08a086369684dc1a 100644 (file)
@@ -20,6 +20,9 @@ import java.util.Set;
 import com.ibm.icu.impl.ICUDebug;
 import com.ibm.icu.impl.ICUResourceBundle;
 import com.ibm.icu.impl.PatternProps;
+import com.ibm.icu.lang.UCharacter;
+import com.ibm.icu.text.BreakIterator;
+import com.ibm.icu.text.DisplayContext;
 import com.ibm.icu.util.ULocale;
 import com.ibm.icu.util.ULocale.Category;
 import com.ibm.icu.util.UResourceBundle;
@@ -600,6 +603,15 @@ public class RuleBasedNumberFormat extends NumberFormat {
      */
     private String[] publicRuleSetNames;
 
+    /**
+     * Data for handling context-based capitalization
+     */
+    private boolean capitalizationInfoIsSet = false;
+    private boolean capitalizationForListOrMenu = false;
+    private boolean capitalizationForStandAlone = false;
+    private BreakIterator capitalizationBrkIter = null;
+
+
     private static final boolean DEBUG  =  ICUDebug.enabled("rbnf");
 
     //-----------------------------------------------------------------------
@@ -833,6 +845,9 @@ public class RuleBasedNumberFormat extends NumberFormat {
     public boolean equals(Object that) {
         // if the other object isn't a RuleBasedNumberFormat, that's
         // all we need to know
+        // Test for capitalization info equality is adequately handled
+        // by the NumberFormat test for capitalizationSetting equality;
+        // the info here is just derived from that.
         if (!(that instanceof RuleBasedNumberFormat)) {
             return false;
         } else {
@@ -1061,7 +1076,7 @@ public class RuleBasedNumberFormat extends NumberFormat {
         if (ruleSet.startsWith("%%")) {
             throw new IllegalArgumentException("Can't use internal rule set");
         }
-        return format(number, findRuleSet(ruleSet));
+        return adjustForContext(format(number, findRuleSet(ruleSet)));
     }
 
     /**
@@ -1080,7 +1095,7 @@ public class RuleBasedNumberFormat extends NumberFormat {
         if (ruleSet.startsWith("%%")) {
             throw new IllegalArgumentException("Can't use internal rule set");
         }
-        return format(number, findRuleSet(ruleSet));
+        return adjustForContext(format(number, findRuleSet(ruleSet)));
     }
 
     /**
@@ -1098,7 +1113,13 @@ public class RuleBasedNumberFormat extends NumberFormat {
         // this is one of the inherited format() methods.  Since it doesn't
         // have a way to select the rule set to use, it just uses the
         // default one
-        toAppendTo.append(format(number, defaultRuleSet));
+        // Note, the BigInteger/BigDecimal methods below currently go through this.
+        if (toAppendTo.length() == 0) {
+            toAppendTo.append(adjustForContext(format(number, defaultRuleSet)));
+        } else {
+            // appending to other text, don't capitalize
+            toAppendTo.append(format(number, defaultRuleSet));
+        }
         return toAppendTo;
     }
 
@@ -1121,7 +1142,12 @@ public class RuleBasedNumberFormat extends NumberFormat {
         // this is one of the inherited format() methods.  Since it doesn't
         // have a way to select the rule set to use, it just uses the
         // default one
-        toAppendTo.append(format(number, defaultRuleSet));
+        if (toAppendTo.length() == 0) {
+            toAppendTo.append(adjustForContext(format(number, defaultRuleSet)));
+        } else {
+            // appending to other text, don't capitalize
+            toAppendTo.append(format(number, defaultRuleSet));
+        }
         return toAppendTo;
     }
 
@@ -1381,6 +1407,31 @@ public class RuleBasedNumberFormat extends NumberFormat {
         }
     }
 
+    /**
+     * {@icu} Set a particular DisplayContext value in the formatter,
+     * such as CAPITALIZATION_FOR_STANDALONE. Note: For getContext, see 
+     * NumberFormat.
+     * 
+     * @param context The DisplayContext value to set. 
+     * @draft ICU 53
+     * @provisional This API might change or be removed in a future release.
+     */
+    // Here we override the NumberFormat implementation in order to
+    // lazily initialize relevant items 
+    public void setContext(DisplayContext context) {
+        super.setContext(context);
+        if (!capitalizationInfoIsSet &&
+              (context==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU || context==DisplayContext.CAPITALIZATION_FOR_STANDALONE)) {
+            initCapitalizationContextInfo(locale);
+            capitalizationInfoIsSet = true;
+        }
+        if (capitalizationBrkIter == null && (context==DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE ||
+              (context==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU && capitalizationForListOrMenu) ||
+              (context==DisplayContext.CAPITALIZATION_FOR_STANDALONE && capitalizationForStandAlone) )) {
+            capitalizationBrkIter = BreakIterator.getSentenceInstance(locale);
+        }
+    }
+
     //-----------------------------------------------------------------------
     // package-internal API
     //-----------------------------------------------------------------------
@@ -1653,6 +1704,23 @@ public class RuleBasedNumberFormat extends NumberFormat {
         }
     }
 
+    /**
+     * Set capitalizationForListOrMenu, capitalizationForStandAlone 
+     */
+    private void initCapitalizationContextInfo(ULocale theLocale) {
+        ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, theLocale);
+        try {
+            ICUResourceBundle rdb = rb.getWithFallback("contextTransforms/number-spellout");
+            int[] intVector = rdb.getIntVector();
+            if (intVector.length >= 2) {
+                capitalizationForListOrMenu = (intVector[0] != 0);
+                capitalizationForStandAlone = (intVector[1] != 0);
+            }
+        } catch (MissingResourceException e) {
+            // use default
+        }
+    }
+
     /**
      * This function is used by init() to strip whitespace between rules (i.e.,
      * after semicolons).
@@ -1805,6 +1873,23 @@ public class RuleBasedNumberFormat extends NumberFormat {
         }
     }
 
+    /**
+     * Adjust capitalization of formatted result for display context
+     */
+    private String adjustForContext(String result) {
+        if (result != null && result.length() > 0 && UCharacter.isLowerCase(result.codePointAt(0)) &&
+              capitalizationBrkIter != null) {
+            DisplayContext capitalization = getContext(DisplayContext.Type.CAPITALIZATION);
+            if (  capitalization==DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE ||
+                  (capitalization == DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU && capitalizationForListOrMenu) ||
+                  (capitalization == DisplayContext.CAPITALIZATION_FOR_STANDALONE && capitalizationForStandAlone) ) {
+                return UCharacter.toTitleCase(locale, result, capitalizationBrkIter,
+                                UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT);
+            }
+        }
+        return result;
+    }
+
     /**
      * Returns the named rule set.  Throws an IllegalArgumentException
      * if this formatter doesn't have a rule set with that name.
index 0a6618add9a7ab2a58ab1f140c0bec0ec789ae5a..5b7094adc18feb87d5a25ac1cfc79ca0da0407b8 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *******************************************************************************
- * Copyright (C) 1996-2013, International Business Machines Corporation and    *
+ * Copyright (C) 1996-2014, International Business Machines Corporation and    *
  * others. All Rights Reserved.                                                *
  *******************************************************************************
  */
@@ -15,6 +15,7 @@ import java.util.Random;
 
 import com.ibm.icu.dev.test.TestFmwk;
 import com.ibm.icu.text.DecimalFormatSymbols;
+import com.ibm.icu.text.DisplayContext;
 import com.ibm.icu.text.RuleBasedNumberFormat;
 import com.ibm.icu.util.ULocale;
 
@@ -1302,4 +1303,51 @@ public class RbnfTest extends TestFmwk {
             errln("Format Error - Got: " + result + " Expected: " + expected[1]);
         }
     }
+    
+    public void TestContext() {
+        class TextContextItem {
+            public String locale;
+            public int format;
+            public DisplayContext context;
+            public double value;
+            public String expectedResult;
+             // Simple constructor
+            public TextContextItem(String loc, int fmt, DisplayContext ctxt, double val, String expRes) {
+                locale = loc;
+                format = fmt;
+                context = ctxt;
+                value = val;
+                expectedResult = expRes;
+            }
+        };
+        final TextContextItem[] items = {
+            new TextContextItem( "sv", RuleBasedNumberFormat.SPELLOUT, DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE,    123.45, "ett\u00ADhundra\u00ADtjugo\u00ADtre komma fyra fem" ),
+            new TextContextItem( "sv", RuleBasedNumberFormat.SPELLOUT, DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE, 123.45, "Ett\u00ADhundra\u00ADtjugo\u00ADtre komma fyra fem" ),
+            new TextContextItem( "sv", RuleBasedNumberFormat.SPELLOUT, DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU,       123.45, "ett\u00ADhundra\u00ADtjugo\u00ADtre komma fyra fem" ),
+            new TextContextItem( "sv", RuleBasedNumberFormat.SPELLOUT, DisplayContext.CAPITALIZATION_FOR_STANDALONE,            123.45, "ett\u00ADhundra\u00ADtjugo\u00ADtre komma fyra fem" ),
+            new TextContextItem( "en", RuleBasedNumberFormat.SPELLOUT, DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE,    123.45, "one hundred twenty-three point four five" ),
+            new TextContextItem( "en", RuleBasedNumberFormat.SPELLOUT, DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE, 123.45, "One hundred twenty-three point four five" ),
+            new TextContextItem( "en", RuleBasedNumberFormat.SPELLOUT, DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU,       123.45, "One hundred twenty-three point four five" ),
+            new TextContextItem( "en", RuleBasedNumberFormat.SPELLOUT, DisplayContext.CAPITALIZATION_FOR_STANDALONE,            123.45, "One hundred twenty-three point four five" ),
+        };
+        for (TextContextItem item: items) {
+            ULocale locale = new ULocale(item.locale);
+            RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(locale, item.format);
+            rbnf.setContext(item.context);
+            String result = rbnf.format(item.value, rbnf.getDefaultRuleSetName());
+            if (!result.equals(item.expectedResult)) {
+                errln("Error for locale " + item.locale + ", context " + item.context + ", expected " + item.expectedResult + ", got " + result);
+            }
+            RuleBasedNumberFormat rbnfClone = (RuleBasedNumberFormat)rbnf.clone();
+            if (!rbnfClone.equals(rbnf)) {
+                errln("Error for locale " + item.locale + ", context " + item.context + ", rbnf.clone() != rbnf");
+            } else {
+                result = rbnfClone.format(item.value, rbnfClone.getDefaultRuleSetName());
+                if (!result.equals(item.expectedResult)) {
+                    errln("Error with clone for locale " + item.locale + ", context " + item.context + ", expected " + item.expectedResult + ", got " + result);
+                }
+            }
+        }
+    }
+
 }