]> granicus.if.org Git - icu/commitdiff
ICU-13634 In accordance with ICU-TC meeting, changing percent parsing behavior to...
authorShane Carr <shane@unicode.org>
Thu, 12 Apr 2018 06:49:24 +0000 (06:49 +0000)
committerShane Carr <shane@unicode.org>
Thu, 12 Apr 2018 06:49:24 +0000 (06:49 +0000)
X-SVN-Rev: 41222

icu4c/source/i18n/numparse_impl.cpp
icu4c/source/i18n/numparse_impl.h
icu4c/source/i18n/numparse_validators.cpp
icu4c/source/i18n/numparse_validators.h
icu4c/source/test/intltest/numfmtst.cpp
icu4c/source/test/intltest/numfmtst.h
icu4c/source/test/testdata/numberformattestspecification.txt
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/FlagHandler.java [new file with mode: 0644]
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java
icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java

index db1586bca48de697c4da3f68203aa10f4c953c2d..acc68394a1cecf17d0b2cd3cb93637a6c8842adf 100644 (file)
@@ -150,6 +150,23 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr
         parser->addMatcher(parser->fLocalMatchers.currency = {currencySymbols, symbols, status});
     }
 
+    ///////////////
+    /// PERCENT ///
+    ///////////////
+
+    // ICU-TC meeting, April 11, 2018: accept percent/permille only if it is in the pattern,
+    // and to maintain regressive behavior, divide by 100 even if no percent sign is present.
+    if (affixProvider->containsSymbolType(AffixPatternType::TYPE_PERCENT, status)) {
+        parser->addMatcher(parser->fLocalMatchers.percent = {symbols});
+        // causes number to be always scaled by 100:
+        parser->addMatcher(parser->fLocalValidators.percentFlags = {ResultFlags::FLAG_PERCENT});
+    }
+    if (affixProvider->containsSymbolType(AffixPatternType::TYPE_PERMILLE, status)) {
+        parser->addMatcher(parser->fLocalMatchers.permille = {symbols});
+        // causes number to be always scaled by 1000:
+        parser->addMatcher(parser->fLocalValidators.permilleFlags = {ResultFlags::FLAG_PERMILLE});
+    }
+
     ///////////////////////////////
     /// OTHER STANDARD MATCHERS ///
     ///////////////////////////////
@@ -157,8 +174,6 @@ NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatPr
     if (!isStrict) {
         parser->addMatcher(parser->fLocalMatchers.plusSign = {symbols, false});
         parser->addMatcher(parser->fLocalMatchers.minusSign = {symbols, false});
-        parser->addMatcher(parser->fLocalMatchers.percent = {symbols});
-        parser->addMatcher(parser->fLocalMatchers.permille = {symbols});
     }
     parser->addMatcher(parser->fLocalMatchers.nan = {symbols});
     parser->addMatcher(parser->fLocalMatchers.infinity = {symbols});
index 748b9415ecb80398146fd03484c4ede3c64f7472..4677ae14d8872cdb5f38a84acc1e2c95b3f90c19 100644 (file)
@@ -80,6 +80,8 @@ class NumberParserImpl : public MutableMatcherCollection {
         RequireExponentValidator exponent;
         RequireNumberValidator number;
         MultiplierParseHandler multiplier;
+        FlagHandler percentFlags;
+        FlagHandler permilleFlags;
     } fLocalValidators;
 
     explicit NumberParserImpl(parse_flags_t parseFlags);
index 724b0cf0313066127fbad92ae2855db079abdc46..a36a15f5120c7b3943d9405a5af52537face8a8a 100644 (file)
@@ -80,4 +80,16 @@ UnicodeString RequireNumberValidator::toString() const {
 }
 
 
+FlagHandler::FlagHandler(result_flags_t flags)
+        : fFlags(flags) {}
+
+void FlagHandler::postProcess(ParsedNumber& result) const {
+    result.flags |= fFlags;
+}
+
+UnicodeString FlagHandler::toString() const {
+    return u"<Flags>";
+}
+
+
 #endif /* #if !UCONFIG_NO_FORMATTING */
index d3bc63aceb308be00d0ae531d605cd6c30ac8ef4..c27890ae11dc986162f9666f7f2b8bcd232cf87b 100644 (file)
@@ -97,6 +97,24 @@ class MultiplierParseHandler : public ValidationMatcher, public UMemory {
 };
 
 
+/**
+ * Unconditionally applies a given set of flags to the ParsedNumber in the post-processing step.
+ */
+class FlagHandler : public ValidationMatcher, public UMemory {
+  public:
+    FlagHandler() = default;
+
+    FlagHandler(result_flags_t flags);
+
+    void postProcess(ParsedNumber& result) const U_OVERRIDE;
+
+    UnicodeString toString() const U_OVERRIDE;
+
+  private:
+    result_flags_t fFlags;
+};
+
+
 } // namespace impl
 } // namespace numparse
 U_NAMESPACE_END
index 3d26e1a6b6c47890e7fe8510c8e907f2ad94ba34..0d07750a4eaa7cabae82a47602c115ba1eb7b1a5 100644 (file)
@@ -655,6 +655,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n
   TESTCASE_AUTO(Test11735_ExceptionIssue);
   TESTCASE_AUTO(Test11035_FormatCurrencyAmount);
   TESTCASE_AUTO(Test11318_DoubleConversion);
+  TESTCASE_AUTO(TestParsePercentRegression);
   TESTCASE_AUTO_END;
 }
 
@@ -9029,4 +9030,35 @@ void NumberFormatTest::Test11318_DoubleConversion() {
     assertEquals("Should render all digits", u"999,999,999,999,999.9", appendTo);
 }
 
+void NumberFormatTest::TestParsePercentRegression() {
+    IcuTestErrorCode status(*this, "TestParsePercentRegression");
+    LocalPointer<DecimalFormat> df1((DecimalFormat*) NumberFormat::createInstance("en", status));
+    LocalPointer<DecimalFormat> df2((DecimalFormat*) NumberFormat::createPercentInstance("en", status));
+    df1->setLenient(TRUE);
+    df2->setLenient(TRUE);
+
+    {
+        ParsePosition ppos;
+        Formattable result;
+        df1->parse("50%", result, ppos);
+        assertEquals("df1 should accept a number but not the percent sign", 2, ppos.getIndex());
+        assertEquals("df1 should return the number as 50", 50.0, result.getDouble(status));
+    }
+    {
+        ParsePosition ppos;
+        Formattable result;
+        df2->parse("50%", result, ppos);
+        assertEquals("df2 should accept the percent sign", 3, ppos.getIndex());
+        assertEquals("df2 should return the number as 0.5", 0.5, result.getDouble(status));
+    }
+    {
+        ParsePosition ppos;
+        Formattable result;
+        df2->parse("50", result, ppos);
+        assertEquals("df2 should return the number as 0.5 even though the percent sign is missing",
+                0.5,
+                result.getDouble(status));
+    }
+}
+
 #endif /* #if !UCONFIG_NO_FORMATTING */
index d71b8c4880f1faeb98de76f3e257033f0c4f06fd..e17b825d03f90d323b4e44502b37ea6d33a23b80 100644 (file)
@@ -222,6 +222,7 @@ class NumberFormatTest: public CalendarTimeZoneTest {
     void Test11735_ExceptionIssue();
     void Test11035_FormatCurrencyAmount();
     void Test11318_DoubleConversion();
+    void TestParsePercentRegression();
 
  private:
     UBool testFormattableAsUFormattable(const char *file, int line, Formattable &f);
index 629073890c36b719d88c9e1e73f2fcd962b1ed37..7b375222ca8f800fd24e069da48b2fb320563f8c 100644 (file)
@@ -1543,8 +1543,8 @@ begin
 parse  output  breaks
 55%    0.55
 // J and K get null
-// P requires the symbol to be present and gets 55
-55     0.55    CJKP
+// C and P scale by 100 even if the percent sign is not present
+55     0.55    JK
 
 test trailing grouping separators in pattern
 // This test is for #13115
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/FlagHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/FlagHandler.java
new file mode 100644 (file)
index 0000000..37d3911
--- /dev/null
@@ -0,0 +1,28 @@
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package com.ibm.icu.impl.number.parse;
+
+/**
+ * Unconditionally applies a given set of flags to the ParsedNumber in the post-processing step.
+ */
+public class FlagHandler extends ValidationMatcher {
+
+    public static final FlagHandler PERCENT = new FlagHandler(ParsedNumber.FLAG_PERCENT);
+    public static final FlagHandler PERMILLE = new FlagHandler(ParsedNumber.FLAG_PERMILLE);
+
+    private final int flags;
+
+    private FlagHandler(int flags) {
+        this.flags = flags;
+    }
+
+    @Override
+    public void postProcess(ParsedNumber result) {
+        result.flags |= flags;
+    }
+
+    @Override
+    public String toString() {
+        return "<FlagsHandler " + Integer.toHexString(flags) + ">";
+    }
+}
index bff15dce37e3d3a9e88e5b592e7931cb8c0d25ab..3d41c37ce78bff76ef509c118c9df56293af0196 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.AffixUtils;
 import com.ibm.icu.impl.number.CurrencyPluralInfoAffixProvider;
 import com.ibm.icu.impl.number.CustomSymbolCurrency;
 import com.ibm.icu.impl.number.DecimalFormatProperties;
@@ -195,6 +196,23 @@ public class NumberParserImpl {
             parser.addMatcher(CombinedCurrencyMatcher.getInstance(currency, symbols));
         }
 
+        ///////////////
+        /// PERCENT ///
+        ///////////////
+
+        // ICU-TC meeting, April 11, 2018: accept percent/permille only if it is in the pattern,
+        // and to maintain regressive behavior, divide by 100 even if no percent sign is present.
+        if (affixProvider.containsSymbolType(AffixUtils.TYPE_PERCENT)) {
+            parser.addMatcher(PercentMatcher.getInstance(symbols));
+            // causes number to be always scaled by 100:
+            parser.addMatcher(FlagHandler.PERCENT);
+        }
+        if (affixProvider.containsSymbolType(AffixUtils.TYPE_PERMILLE)) {
+            parser.addMatcher(PermilleMatcher.getInstance(symbols));
+            // causes number to be always scaled by 1000:
+            parser.addMatcher(FlagHandler.PERMILLE);
+        }
+
         ///////////////////////////////
         /// OTHER STANDARD MATCHERS ///
         ///////////////////////////////
@@ -202,8 +220,6 @@ public class NumberParserImpl {
         if (!isStrict) {
             parser.addMatcher(PlusSignMatcher.getInstance(symbols, false));
             parser.addMatcher(MinusSignMatcher.getInstance(symbols, false));
-            parser.addMatcher(PercentMatcher.getInstance(symbols));
-            parser.addMatcher(PermilleMatcher.getInstance(symbols));
         }
         parser.addMatcher(NanMatcher.getInstance(symbols, parseFlags));
         parser.addMatcher(InfinityMatcher.getInstance(symbols));
index 35731ff8b7b3194173d18d548ba4c90455bdf8c4..d1e71388d3e1e1bb02112827a7c100634e4eeb56 100644 (file)
@@ -1545,8 +1545,8 @@ begin
 parse  output  breaks
 55%    0.55
 // J and K get null
-// P requires the symbol to be present and gets 55
-55     0.55    JKP
+// C and P scale by 100 even if the percent sign is not present
+55     0.55    JK
 
 test trailing grouping separators in pattern
 // This test is for #13115
index dbbdc13210ffa6d30595a790376f20f3a2c5bdda..b442d3b3029405cea6ec3576f27e949c668984ef 100644 (file)
@@ -5637,7 +5637,7 @@ public class NumberFormatTest extends TestFmwk {
         assertEquals("Should consume the trailing bidi since it is in the symbol", 5, ppos.getIndex());
         ppos.setIndex(0);
         result = df.parse("-42a\u200E ", ppos);
-        assertEquals("Should not parse as percent", new Long(-42), result);
+        assertEquals("Should parse as percent", -0.42, result.doubleValue());
         assertEquals("Should not consume the trailing bidi or whitespace", 4, ppos.getIndex());
 
         // A few more cases based on the docstring:
@@ -6075,4 +6075,32 @@ public class NumberFormatTest extends TestFmwk {
         DecimalFormat df = new DecimalFormat("-0", DecimalFormatSymbols.getInstance(ULocale.ENGLISH));
         expect2(df, -5, "--5");
     }
+
+    @Test
+    public void testParsePercentRegression() {
+        DecimalFormat df1 = (DecimalFormat) NumberFormat.getInstance(ULocale.ENGLISH);
+        DecimalFormat df2 = (DecimalFormat) NumberFormat.getPercentInstance(ULocale.ENGLISH);
+        df1.setParseStrict(false);
+        df2.setParseStrict(false);
+
+        {
+            ParsePosition ppos = new ParsePosition(0);
+            Number result = df1.parse("50%", ppos);
+            assertEquals("df1 should accept a number but not the percent sign", 2, ppos.getIndex());
+            assertEquals("df1 should return the number as 50", 50.0, result.doubleValue());
+        }
+        {
+            ParsePosition ppos = new ParsePosition(0);
+            Number result = df2.parse("50%", ppos);
+            assertEquals("df2 should accept the percent sign", 3, ppos.getIndex());
+            assertEquals("df2 should return the number as 0.5", 0.5, result.doubleValue());
+        }
+        {
+            ParsePosition ppos = new ParsePosition(0);
+            Number result = df2.parse("50", ppos);
+            assertEquals("df2 should return the number as 0.5 even though the percent sign is missing",
+                    0.5,
+                    result.doubleValue());
+        }
+    }
 }