// 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,
-
- /**
- * 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,
+ /**
+ * 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,
}
@Deprecated
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) {
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.
*/
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;
}
}
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;
}
@Test
- @Ignore
public void TestParseRequiredDecimalPoint() {
String[] testPattern = { "00.####", "00.0", "00" };
}
@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".
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() {
}
}
- @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();