ICU-13634 In accordance with ICU-TC meeting, changing percent parsing behavior to be closer to that of ICU 60.

X-SVN-Rev: 41222
This commit is contained in:
Shane Carr 2018-04-12 06:49:24 +00:00
parent 6c1714870f
commit af0f8e62e4
11 changed files with 161 additions and 9 deletions

View 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});

View 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);

View 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 */

View 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

View 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 */

View 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);

View 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

View file

@ -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) + ">";
}
}

View 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));

View 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

View 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());
}
}
}