ICU-13513 A few more minor fixes before merge.

X-SVN-Rev: 40822
This commit is contained in:
Shane Carr 2018-01-30 01:21:56 +00:00
parent 8d366b982d
commit da65d09dd5
4 changed files with 52 additions and 127 deletions

View File

@ -34,37 +34,36 @@ public class NumberParserImpl {
// TODO: Find a better place for this enum.
/** Controls the set of rules for parsing a string. */
public static enum ParseMode {
/**
* Lenient mode should be used if you want to accept malformed user input. It will use
* heuristics to attempt to parse through typographical errors in the string.
*/
LENIENT,
/**
* Lenient mode should be used if you want to accept malformed user input. It will use heuristics
* to attempt to parse through typographical errors in the string.
*/
LENIENT,
/**
* Strict mode should be used if you want to require that the input is well-formed. More
* specifically, it differs from lenient mode in the following ways:
*
* <ul>
* <li>Grouping widths must match the grouping settings. For example, "12,3,45" will fail if
* the grouping width is 3, as in the pattern "#,##0".
* <li>The string must contain a complete prefix and suffix. For example, if the pattern is
* "{#};(#)", then "{123}" or "(123)" would match, but "{123", "123}", and "123" would all
* fail. (The latter strings would be accepted in lenient mode.)
* <li>Whitespace may not appear at arbitrary places in the string. In lenient mode,
* whitespace is allowed to occur arbitrarily before and after prefixes and exponent
* separators.
* <li>Leading grouping separators are not allowed, as in ",123".
* <li>Minus and plus signs can only appear if specified in the pattern. In lenient mode, a
* plus or minus sign can always precede a number.
* <li>The set of characters that can be interpreted as a decimal or grouping separator is
* smaller.
* <li><strong>If currency parsing is enabled,</strong> currencies must only appear where
* specified in either the current pattern string or in a valid pattern string for the
* current locale. For example, if the pattern is "¤0.00", then "$1.23" would match, but
* "1.23$" would fail to match.
* </ul>
*/
STRICT,
/**
* Strict mode should be used if you want to require that the input is well-formed. More
* specifically, it differs from lenient mode in the following ways:
*
* <ul>
* <li>Grouping widths must match the grouping settings. For example, "12,3,45" will fail if the
* grouping width is 3, as in the pattern "#,##0".
* <li>The string must contain a complete prefix and suffix. For example, if the pattern is
* "{#};(#)", then "{123}" or "(123)" would match, but "{123", "123}", and "123" would all fail.
* (The latter strings would be accepted in lenient mode.)
* <li>Whitespace may not appear at arbitrary places in the string. In lenient mode, whitespace
* is allowed to occur arbitrarily before and after prefixes and exponent separators.
* <li>Leading grouping separators are not allowed, as in ",123".
* <li>Minus and plus signs can only appear if specified in the pattern. In lenient mode, a plus
* or minus sign can always precede a number.
* <li>The set of characters that can be interpreted as a decimal or grouping separator is
* smaller.
* <li><strong>If currency parsing is enabled,</strong> currencies must only appear where
* specified in either the current pattern string or in a valid pattern string for the current
* locale. For example, if the pattern is "¤0.00", then "$1.23" would match, but "1.23$" would
* fail to match.
* </ul>
*/
STRICT,
}
@Deprecated
@ -167,20 +166,12 @@ public class NumberParserImpl {
AffixPatternProvider patternInfo = new PropertiesAffixPatternProvider(properties);
Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols);
boolean isStrict = properties.getParseMode() == ParseMode.STRICT;
boolean decimalSeparatorRequired = properties.getDecimalPatternMatchRequired()
? (properties.getDecimalSeparatorAlwaysShown()
|| properties.getMaximumFractionDigits() != 0)
: false;
boolean decimalSeparatorForbidden = properties.getDecimalPatternMatchRequired()
? (!properties.getDecimalSeparatorAlwaysShown()
&& properties.getMaximumFractionDigits() == 0)
: false;
Grouper grouper = Grouper.defaults().withProperties(properties);
int parseFlags = 0;
if (!properties.getParseCaseSensitive()) {
parseFlags |= ParsingUtils.PARSE_FLAG_IGNORE_CASE;
}
if (properties.getParseIntegerOnly() || decimalSeparatorForbidden) {
if (properties.getParseIntegerOnly()) {
parseFlags |= ParsingUtils.PARSE_FLAG_INTEGER_ONLY;
}
if (isStrict) {
@ -260,8 +251,10 @@ public class NumberParserImpl {
if (parseCurrency) {
parser.addMatcher(new RequireCurrencyMatcher());
}
if (decimalSeparatorRequired) {
parser.addMatcher(new RequireDecimalSeparatorMatcher());
if (properties.getDecimalPatternMatchRequired()) {
boolean patternHasDecimalSeparator = properties.getDecimalSeparatorAlwaysShown()
|| properties.getMaximumFractionDigits() != 0;
parser.addMatcher(RequireDecimalSeparatorMatcher.getInstance(patternHasDecimalSeparator));
}
if (properties.getMultiplier() != null) {
// We need to use a math context in order to prevent non-terminating decimal expansions.

View File

@ -8,9 +8,23 @@ package com.ibm.icu.impl.number.parse;
*/
public class RequireDecimalSeparatorMatcher extends ValidationMatcher {
private static final RequireDecimalSeparatorMatcher A = new RequireDecimalSeparatorMatcher(true);
private static final RequireDecimalSeparatorMatcher B = new RequireDecimalSeparatorMatcher(false);
private final boolean patternHasDecimalSeparator;
public static RequireDecimalSeparatorMatcher getInstance(boolean patternHasDecimalSeparator) {
return patternHasDecimalSeparator ? A : B;
}
private RequireDecimalSeparatorMatcher(boolean patternHasDecimalSeparator) {
this.patternHasDecimalSeparator = patternHasDecimalSeparator;
}
@Override
public void postProcess(ParsedNumber result) {
if (0 == (result.flags & ParsedNumber.FLAG_HAS_DECIMAL_SEPARATOR)) {
boolean parseHasDecimalSeparator = 0 != (result.flags & ParsedNumber.FLAG_HAS_DECIMAL_SEPARATOR);
if (parseHasDecimalSeparator != patternHasDecimalSeparator) {
result.flags |= ParsedNumber.FLAG_FAIL;
}
}

View File

@ -8,6 +8,8 @@ import java.math.RoundingMode;
import java.text.FieldPosition;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.RoundingUtils;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.Operand;
import com.ibm.icu.text.UFieldPosition;

View File

@ -4250,7 +4250,6 @@ public class NumberFormatTest extends TestFmwk {
}
@Test
@Ignore
public void TestParseRequiredDecimalPoint() {
String[] testPattern = { "00.####", "00.0", "00" };
@ -5466,7 +5465,6 @@ public class NumberFormatTest extends TestFmwk {
}
@Test
@Ignore
public void testParseSubtraction() {
// TODO: Is this a case we need to support? It prevents us from automatically parsing
// minus signs that appear after the number, like in "12-" vs "-12".
@ -5499,6 +5497,7 @@ public class NumberFormatTest extends TestFmwk {
assertEquals("Quote should be escapable in padding syntax", "a''12b", result);
}
// TODO: Investigate this test and re-enable if appropriate.
@Test
@Ignore
public void testParseAmbiguousAffixes() {
@ -5648,89 +5647,6 @@ public class NumberFormatTest extends TestFmwk {
}
}
@Test
@Ignore
public void testParseGroupingMode() {
ULocale[] locales = { // GROUPING DECIMAL
new ULocale("en-US"), // comma period
new ULocale("fr-FR"), // space comma
new ULocale("de-CH"), // apostrophe period
new ULocale("es-PY") // period comma
};
String[] inputs = {
"12,345.67",
"12 345,67",
"12'345.67",
"12.345,67",
"12,345",
"12 345",
"12'345",
"12.345"
};
BigDecimal[] outputs = {
new BigDecimal("12345.67"),
new BigDecimal("12345.67"),
new BigDecimal("12345.67"),
new BigDecimal("12345.67"),
new BigDecimal("12345"),
new BigDecimal("12345"),
new BigDecimal("12345"),
new BigDecimal("12345")
};
int[][] expecteds = {
// 0 => works in neither default nor restricted
// 1 => works in default but not restricted
// 2 => works in restricted but not default (should not happen)
// 3 => works in both default and restricted
//
// C=comma, P=period, S=space, A=apostrophe
// C+P S+C A+P P+C C-only S-only A-only P-only
{ 3, 0, 1, 0, 3, 1, 1, 0 }, // => en-US
{ 0, 3, 0, 1, 0, 3, 3, 1 }, // => fr-FR
{ 1, 0, 3, 0, 1, 3, 3, 0 }, // => de-CH
{ 0, 1, 0, 3, 0, 1, 1, 3 } // => es-PY
};
for (int i=0; i<locales.length; i++) {
ULocale loc = locales[i];
DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(loc);
df.setParseBigDecimal(true);
for (int j=0; j<inputs.length; j++) {
String input = inputs[j];
BigDecimal output = outputs[j];
int expected = expecteds[i][j];
// TODO(sffc): Uncomment after ICU 60 API proposal
//df.setParseGroupingMode(null);
//assertEquals("Getter should return null", null, df.getParseGroupingMode());
ParsePosition ppos = new ParsePosition(0);
Number result = df.parse(input, ppos);
boolean actualNull = output.equals(result) && (ppos.getIndex() == input.length());
assertEquals("Locale " + loc + ", string \"" + input + "\", DEFAULT, "
+ "actual result: " + result + " (ppos: " + ppos.getIndex() + ")",
(expected & 1) != 0, actualNull);
// TODO(sffc): Uncomment after ICU 60 API proposal
//df.setParseGroupingMode(GroupingMode.DEFAULT);
//assertEquals("Getter should return new value", GroupingMode.DEFAULT, df.getParseGroupingMode());
//ppos = new ParsePosition(0);
//result = df.parse(input, ppos);
//boolean actualDefault = output.equals(result) && (ppos.getIndex() == input.length());
//assertEquals("Result from null should be the same as DEFAULT", actualNull, actualDefault);
// TODO(sffc): Uncomment after ICU 60 API proposal
//df.setParseGroupingMode(GroupingMode.RESTRICTED);
//assertEquals("Getter should return new value", GroupingMode.RESTRICTED, df.getParseGroupingMode());
//ppos = new ParsePosition(0);
//result = df.parse(input, ppos);
//boolean actualRestricted = output.equals(result) && (ppos.getIndex() == input.length());
//assertEquals("Locale " + loc + ", string \"" + input + "\", RESTRICTED, "
// + "actual result: " + result + " (ppos: " + ppos.getIndex() + ")",
// (expected & 2) != 0, actualRestricted);
}
}
}
@Test
public void testParseNoExponent() throws ParseException {
DecimalFormat df = new DecimalFormat();