]> granicus.if.org Git - icu/commitdiff
ICU-13634 Fixing various issues in order to make currencies round-trip in strict...
authorShane Carr <shane@unicode.org>
Sat, 7 Apr 2018 11:10:08 +0000 (11:10 +0000)
committerShane Carr <shane@unicode.org>
Sat, 7 Apr 2018 11:10:08 +0000 (11:10 +0000)
X-SVN-Rev: 41212

icu4c/source/i18n/numparse_affixes.cpp
icu4c/source/i18n/numparse_compositions.cpp
icu4c/source/i18n/numparse_impl.cpp
icu4c/source/test/intltest/numfmtst.cpp
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SeriesMatcher.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java

index 013cc01ff8709c2400fe58ce17dd577beb554ca9..0ac1fe7c0e9ff0ee0ca98a454060628dd0408fd6 100644 (file)
@@ -304,7 +304,7 @@ void AffixMatcherWarehouse::createAffixMatchers(const AffixPatternProvider& patt
     UnicodeString sb;
     bool includeUnpaired = 0 != (parseFlags & PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES);
     UNumberSignDisplay signDisplay = (0 != (parseFlags & PARSE_FLAG_PLUS_SIGN_ALLOWED)) ? UNUM_SIGN_ALWAYS
-                                                                                        : UNUM_SIGN_NEVER;
+                                                                                        : UNUM_SIGN_AUTO;
 
     int32_t numAffixMatchers = 0;
     int32_t numAffixPatternMatchers = 0;
index 06aa476a29b914c5873c1fb5ecfa644336aeef9b..8c7b7d251cdce7ade755dace3d2d95944c1de1af 100644 (file)
@@ -40,6 +40,11 @@ bool SeriesMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCo
         } else if (success) {
             // Match succeeded, and this is NOT a flexible matcher. Proceed to the next matcher.
             it++;
+            // Small hack: if there is another matcher coming, do not accept trailing weak chars.
+            // Needed for proper handling of currency spacing.
+            if (it < end() && segment.getOffset() != result.charEnd && result.charEnd > matcherOffset) {
+                segment.setOffset(result.charEnd);
+            }
         } else if (isFlexible) {
             // Match failed, and this is a flexible matcher. Try again with the next matcher.
             it++;
index b8240e7f401a76c5f9000b6c6306103ad4b087d3..54609151bcd8a042fa0df798713643a2ca33deac 100644 (file)
@@ -81,12 +81,23 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr
                                              const DecimalFormatSymbols& symbols, bool parseCurrency,
                                              UErrorCode& status) {
     Locale locale = symbols.getLocale();
-    PropertiesAffixPatternProvider patternInfo(properties, status);
+    PropertiesAffixPatternProvider localPAPP;
+    CurrencyPluralInfoAffixProvider localCPIAP;
+    AffixPatternProvider* affixProvider;
+    if (properties.currencyPluralInfo.fPtr.isNull()) {
+        localPAPP.setTo(properties, status);
+        affixProvider = &localPAPP;
+    } else {
+        localCPIAP.setTo(*properties.currencyPluralInfo.fPtr, status);
+        affixProvider = &localCPIAP;
+    }
+    if (affixProvider == nullptr || U_FAILURE(status)) { return nullptr; }
     CurrencyUnit currency = resolveCurrency(properties, locale, status);
     CurrencySymbols currencySymbols(currency, locale, symbols, status);
     bool isStrict = properties.parseMode.getOrDefault(PARSE_MODE_STRICT) == PARSE_MODE_STRICT;
     Grouper grouper = Grouper::forProperties(properties);
     int parseFlags = 0;
+    if (affixProvider == nullptr || U_FAILURE(status)) { return nullptr; }
     // Fraction grouping is disabled by default because it has never been supported in DecimalFormat
     parseFlags |= PARSE_FLAG_FRACTION_GROUPING_DISABLED;
     if (!properties.parseCaseSensitive) {
@@ -109,7 +120,7 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr
     if (grouper.getPrimary() <= 0) {
         parseFlags |= PARSE_FLAG_GROUPING_DISABLED;
     }
-    if (parseCurrency || patternInfo.hasCurrencySign()) {
+    if (parseCurrency || affixProvider->hasCurrencySign()) {
         parseFlags |= PARSE_FLAG_MONETARY_SEPARATORS;
     }
 
@@ -129,13 +140,13 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr
     parser->fLocalMatchers.affixTokenMatcherWarehouse = {&affixSetupData};
     parser->fLocalMatchers.affixMatcherWarehouse = {&parser->fLocalMatchers.affixTokenMatcherWarehouse};
     parser->fLocalMatchers.affixMatcherWarehouse.createAffixMatchers(
-            patternInfo, *parser, ignorables, parseFlags, status);
+            *affixProvider, *parser, ignorables, parseFlags, status);
 
     ////////////////////////
     /// CURRENCY MATCHER ///
     ////////////////////////
 
-    if (parseCurrency || patternInfo.hasCurrencySign()) {
+    if (parseCurrency || affixProvider->hasCurrencySign()) {
         parser->addMatcher(parser->fLocalMatchers.currency = {currencySymbols, symbols, status});
     }
 
index 54817637b4ebce0130bfc3b21e9621aa3ae8a402..bf070fa75ac3750bbd4c76a6d26ab9cd75eecba2 100644 (file)
@@ -3987,10 +3987,10 @@ NumberFormatTest::TestCurrencyIsoPluralFormat() {
         {"en_US", "1", "USD", "$1.00", "USD\\u00A01.00", "1.00 US dollars"},
         {"en_US", "1234.56", "USD", "$1,234.56", "USD\\u00A01,234.56", "1,234.56 US dollars"},
         {"en_US", "-1234.56", "USD", "-$1,234.56", "-USD\\u00A01,234.56", "-1,234.56 US dollars"},
-        {"zh_CN", "1", "USD", "US$1.00", "USD\\u00A01.00", "1.00\\u7F8E\\u5143"},
-        {"zh_CN", "1234.56", "USD", "US$1,234.56", "USD\\u00A01,234.56", "1,234.56\\u7F8E\\u5143"},
-        {"zh_CN", "1", "CNY", "\\uFFE51.00", "CNY\\u00A01.00", "1.00\\u4EBA\\u6C11\\u5E01"},
-        {"zh_CN", "1234.56", "CNY", "\\uFFE51,234.56", "CNY\\u00A01,234.56", "1,234.56\\u4EBA\\u6C11\\u5E01"},
+        {"zh_CN", "1", "USD", "US$1.00", "USD\\u00A01.00", "1.00\\u00A0\\u7F8E\\u5143"},
+        {"zh_CN", "1234.56", "USD", "US$1,234.56", "USD\\u00A01,234.56", "1,234.56\\u00A0\\u7F8E\\u5143"},
+        {"zh_CN", "1", "CNY", "\\uFFE51.00", "CNY\\u00A01.00", "1.00\\u00A0\\u4EBA\\u6C11\\u5E01"},
+        {"zh_CN", "1234.56", "CNY", "\\uFFE51,234.56", "CNY\\u00A01,234.56", "1,234.56\\u00A0\\u4EBA\\u6C11\\u5E01"},
         {"ru_RU", "1", "RUB", "1,00\\u00A0\\u20BD", "1,00\\u00A0RUB", "1,00 \\u0440\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u043E\\u0433\\u043E \\u0440\\u0443\\u0431\\u043B\\u044F"},
         {"ru_RU", "2", "RUB", "2,00\\u00A0\\u20BD", "2,00\\u00A0RUB", "2,00 \\u0440\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u043E\\u0433\\u043E \\u0440\\u0443\\u0431\\u043B\\u044F"},
         {"ru_RU", "5", "RUB", "5,00\\u00A0\\u20BD", "5,00\\u00A0RUB", "5,00 \\u0440\\u043E\\u0441\\u0441\\u0438\\u0439\\u0441\\u043A\\u043E\\u0433\\u043E \\u0440\\u0443\\u0431\\u043B\\u044F"},
@@ -4040,7 +4040,9 @@ NumberFormatTest::TestCurrencyIsoPluralFormat() {
             errln("FAIL: Expected " + formatResult + " actual: " + strBuf);
         }
         // test parsing, and test parsing for all currency formats.
-        for (int j = 3; j < 6; ++j) {
+        // NOTE: ICU 62 requires that the currency format match the pattern in strict mode.
+        //for (int j = 3; j < 6; ++j) {
+        for (int j = 3 + kIndex; j <= 3 + kIndex; j++) {
             // DATA[i][3] is the currency format result using
             // CURRENCYSTYLE formatter.
             // DATA[i][4] is the currency format result using
index 04bbadbc22823319f302340c9f54717c0358a1ff..f3c1f9410a1fe42458c2919e7223efe0aa809931 100644 (file)
@@ -92,7 +92,7 @@ public class AffixMatcher implements NumberParseMatcher {
         boolean includeUnpaired = 0 != (parseFlags & ParsingUtils.PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES);
         SignDisplay signDisplay = (0 != (parseFlags & ParsingUtils.PARSE_FLAG_PLUS_SIGN_ALLOWED))
                 ? SignDisplay.ALWAYS
-                : SignDisplay.NEVER;
+                : SignDisplay.AUTO;
 
         AffixPatternMatcher posPrefix = null;
         AffixPatternMatcher posSuffix = null;
index 72ab98e4e31f041acae214a384d2153ba10d2665..957a593345209c5350ee8c26492d9f947b419f09 100644 (file)
@@ -9,6 +9,7 @@ import java.util.List;
 
 import com.ibm.icu.impl.StringSegment;
 import com.ibm.icu.impl.number.AffixPatternProvider;
+import com.ibm.icu.impl.number.CurrencyPluralInfoAffixProvider;
 import com.ibm.icu.impl.number.CustomSymbolCurrency;
 import com.ibm.icu.impl.number.DecimalFormatProperties;
 import com.ibm.icu.impl.number.DecimalFormatProperties.ParseMode;
@@ -134,7 +135,12 @@ public class NumberParserImpl {
             boolean parseCurrency) {
 
         ULocale locale = symbols.getULocale();
-        AffixPatternProvider patternInfo = new PropertiesAffixPatternProvider(properties);
+        AffixPatternProvider affixProvider;
+        if (properties.getCurrencyPluralInfo() == null) {
+            affixProvider = new PropertiesAffixPatternProvider(properties);
+        } else {
+            affixProvider = new CurrencyPluralInfoAffixProvider(properties.getCurrencyPluralInfo());
+        }
         Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols);
         boolean isStrict = properties.getParseMode() == ParseMode.STRICT;
         Grouper grouper = Grouper.forProperties(properties);
@@ -161,7 +167,7 @@ public class NumberParserImpl {
         if (grouper.getPrimary() <= 0) {
             parseFlags |= ParsingUtils.PARSE_FLAG_GROUPING_DISABLED;
         }
-        if (parseCurrency || patternInfo.hasCurrencySign()) {
+        if (parseCurrency || affixProvider.hasCurrencySign()) {
             parseFlags |= ParsingUtils.PARSE_FLAG_MONETARY_SEPARATORS;
         }
         IgnorablesMatcher ignorables = isStrict ? IgnorablesMatcher.STRICT : IgnorablesMatcher.DEFAULT;
@@ -179,13 +185,13 @@ public class NumberParserImpl {
         //////////////////////
 
         // Set up a pattern modifier with mostly defaults to generate AffixMatchers.
-        AffixMatcher.createMatchers(patternInfo, parser, factory, ignorables, parseFlags);
+        AffixMatcher.createMatchers(affixProvider, parser, factory, ignorables, parseFlags);
 
         ////////////////////////
         /// CURRENCY MATCHER ///
         ////////////////////////
 
-        if (parseCurrency || patternInfo.hasCurrencySign()) {
+        if (parseCurrency || affixProvider.hasCurrencySign()) {
             parser.addMatcher(CombinedCurrencyMatcher.getInstance(currency, symbols));
         }
 
index f12a81fd351f771004e306266df94ef76abba94c..466be0e648c18aa23f8c5e4bfd1480ce7681cb8f 100644 (file)
@@ -65,6 +65,11 @@ public class SeriesMatcher implements NumberParseMatcher {
             } else if (success) {
                 // Match succeeded, and this is NOT a flexible matcher. Proceed to the next matcher.
                 i++;
+                // Small hack: if there is another matcher coming, do not accept trailing weak chars.
+                // Needed for proper handling of currency spacing.
+                if (i < matchers.size() && segment.getOffset() != result.charEnd && result.charEnd > matcherOffset) {
+                    segment.setOffset(result.charEnd);
+                }
             } else if (isFlexible) {
                 // Match failed, and this is a flexible matcher. Try again with the next matcher.
                 i++;
index 679031eea236424052c259abdcabaa27dfff7ee2..639744aed436a966701aa210cb426fb107de5e32 100644 (file)
@@ -5994,4 +5994,27 @@ public class NumberFormatTest extends TestFmwk {
         assertEquals("Should parse successfully", 0.08, percentage.doubleValue());
         assertEquals("Should consume whole string", 3, ppos.getIndex());
     }
+
+    @Test
+    public void testStrictParseCurrencyLongNames() {
+        DecimalFormat df = (DecimalFormat) DecimalFormat.getInstance(ULocale.ENGLISH, DecimalFormat.PLURALCURRENCYSTYLE);
+        df.setParseStrict(true);
+        df.setCurrency(Currency.getInstance("USD"));
+        double input = 514.23;
+        String formatted = df.format(input);
+        assertEquals("Should format as expected", "514.23 US dollars", formatted);
+        ParsePosition ppos = new ParsePosition(0);
+        CurrencyAmount ca = df.parseCurrency(formatted, ppos);
+        assertEquals("Should consume whole number", ppos.getIndex(), 17);
+        assertEquals("Number should round-trip", ca.getNumber().doubleValue(), input);
+        assertEquals("Should get correct currency", ca.getCurrency().getCurrencyCode(), "USD");
+    }
+
+    @Test
+    public void testStrictParseCurrencySpacing() {
+        DecimalFormat df = new DecimalFormat("¤ 0", DecimalFormatSymbols.getInstance(ULocale.ROOT));
+        df.setCurrency(Currency.getInstance("USD"));
+        df.setParseStrict(true);
+        expect2(df, -51.42, "-US$ 51.42");
+    }
 }