ICU-13634 Fixing various issues in order to make currencies round-trip in strict mode.

X-SVN-Rev: 41212
This commit is contained in:
Shane Carr 2018-04-07 11:10:08 +00:00
parent 06485f3b6b
commit b9925e084e
8 changed files with 67 additions and 15 deletions

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

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

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

View 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

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

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

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

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