*/
protected abstract void shiftLeft(int numDigits);
+ /**
+ * Removes digits from the end of the BCD list. This may result in an invalid BCD representation; it is
+ * the caller's responsibility to follow-up with a call to {@link #compact}.
+ *
+ * @param numDigits The number of zeros to add.
+ */
protected abstract void shiftRight(int numDigits);
/**
import com.ibm.icu.number.NumberFormatter.UnitWidth;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.util.Currency;
+import com.ibm.icu.util.CurrencyAmount;
import com.ibm.icu.util.ULocale;
/**
parser.addMatcher(new MinusSignMatcher());
parser.addMatcher(new ScientificMatcher(symbols));
parser.addMatcher(new CurrencyMatcher(locale));
- parser.addMatcher(new RequirementsMatcher());
+ parser.addMatcher(new RequireNumberMatcher());
parser.freeze();
return parser;
public static Number parseStatic(String input,
ParsePosition ppos,
DecimalFormatProperties properties,
- DecimalFormatSymbols symbols,
- boolean parseCurrency) {
- NumberParserImpl parser = createParserFromProperties(properties, symbols, parseCurrency);
+ DecimalFormatSymbols symbols) {
+ NumberParserImpl parser = createParserFromProperties(properties, symbols, false);
ParsedNumber result = new ParsedNumber();
parser.parse(input, true, result);
ppos.setIndex(result.charsConsumed);
}
}
+ public static CurrencyAmount parseStaticCurrency(String input,
+ ParsePosition ppos,
+ DecimalFormatProperties properties,
+ DecimalFormatSymbols symbols) {
+ NumberParserImpl parser = createParserFromProperties(properties, symbols, true);
+ ParsedNumber result = new ParsedNumber();
+ parser.parse(input, true, result);
+ ppos.setIndex(result.charsConsumed);
+ if (result.charsConsumed > 0) {
+ // TODO: Clean this up
+ Currency currency;
+ if (result.currencyCode != null) {
+ currency = Currency.getInstance(result.currencyCode);
+ } else {
+ assert 0 != (result.flags & ParsedNumber.FLAG_HAS_DEFAULT_CURRENCY);
+ currency = CustomSymbolCurrency.resolve(properties.getCurrency(), symbols.getULocale(), symbols);
+ }
+ return new CurrencyAmount(result.getDouble(), currency);
+ } else {
+ return null;
+ }
+ }
+
public static NumberParserImpl createParserFromProperties(
DecimalFormatProperties properties,
DecimalFormatSymbols symbols,
Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols);
boolean isStrict = properties.getParseMode() == ParseMode.STRICT;
+ ////////////////////////
+ /// CURRENCY MATCHER ///
+ ////////////////////////
+
+ if (parseCurrency) {
+ parser.addMatcher(new CurrencyMatcher(locale));
+ }
+
//////////////////////
/// AFFIX MATCHERS ///
//////////////////////
// Set up a pattern modifier with mostly defaults to generate AffixMatchers.
MutablePatternModifier mod = new MutablePatternModifier(false);
- AffixPatternProvider provider = new PropertiesAffixPatternProvider(properties);
- mod.setPatternInfo(provider);
+ AffixPatternProvider patternInfo = new PropertiesAffixPatternProvider(properties);
+ mod.setPatternInfo(patternInfo);
mod.setPatternAttributes(SignDisplay.AUTO, false);
mod.setSymbols(symbols, currency, UnitWidth.SHORT, null);
// Figure out which flags correspond to this pattern modifier. Note: negatives are taken care of in the
// generateFromPatternModifier function.
int flags = 0;
- if (provider.containsSymbolType(AffixUtils.TYPE_PERCENT)) {
+ if (patternInfo.containsSymbolType(AffixUtils.TYPE_PERCENT)) {
flags |= ParsedNumber.FLAG_PERCENT;
}
- if (provider.containsSymbolType(AffixUtils.TYPE_PERMILLE)) {
+ if (patternInfo.containsSymbolType(AffixUtils.TYPE_PERMILLE)) {
flags |= ParsedNumber.FLAG_PERMILLE;
}
+ if (patternInfo.hasCurrencySign()) {
+ flags |= ParsedNumber.FLAG_HAS_DEFAULT_CURRENCY;
+ }
- AffixMatcher.generateFromPatternModifier(mod, flags, !isStrict, parser);
+ parseCurrency = parseCurrency || patternInfo.hasCurrencySign();
+
+ AffixMatcher.generateFromPatternModifier(mod, flags, !isStrict && !parseCurrency, parser);
///////////////////////////////
/// OTHER STANDARD MATCHERS ///
decimalMatcher.grouping2 = properties.getSecondaryGroupingSize();
decimalMatcher.integerOnly = properties.getParseIntegerOnly();
parser.addMatcher(decimalMatcher);
- parser.addMatcher(new ScientificMatcher(symbols));
- parser.addMatcher(new RequirementsMatcher());
+ if (!properties.getParseNoExponent()) {
+ parser.addMatcher(new ScientificMatcher(symbols));
+ }
+
+ //////////////////
+ /// VALIDATORS ///
+ //////////////////
+
+ parser.addMatcher(new RequireNumberMatcher());
if (isStrict) {
parser.addMatcher(new RequireAffixMatcher());
}
if (isStrict && properties.getMinimumExponentDigits() > 0) {
parser.addMatcher(new RequireExponentMatcher());
}
+ if (parseCurrency) {
+ parser.addMatcher(new RequireCurrencyMatcher());
+ }
////////////////////////
- /// CURRENCY MATCHER ///
+ /// OTHER ATTRIBUTES ///
////////////////////////
- if (parseCurrency) {
- parser.addMatcher(new CurrencyMatcher(locale));
- }
+ parser.setIgnoreCase(!properties.getParseCaseSensitive());
parser.freeze();
return parser;
private final List<NumberParseMatcher> matchers;
private Comparator<ParsedNumber> comparator;
+ private boolean ignoreCase;
private boolean frozen;
public NumberParserImpl() {
matchers = new ArrayList<NumberParseMatcher>();
comparator = ParsedNumber.COMPARATOR; // default value
+ ignoreCase = true;
frozen = false;
}
this.comparator = comparator;
}
+ public void setIgnoreCase(boolean ignoreCase) {
+ this.ignoreCase = ignoreCase;
+ }
+
public void freeze() {
frozen = true;
}
public void parse(String input, boolean greedy, ParsedNumber result) {
assert frozen;
- StringSegment segment = new StringSegment(input);
+ StringSegment segment = new StringSegment(input, ignoreCase);
if (greedy) {
parseGreedyRecursive(segment, result);
} else {
public static final int FLAG_PERCENT = 0x0002;
public static final int FLAG_PERMILLE = 0x0004;
public static final int FLAG_HAS_EXPONENT = 0x0008;
+ public static final int FLAG_HAS_DEFAULT_CURRENCY = 0x0010;
/** A Comparator that favors ParsedNumbers with the most chars consumed. */
public static final Comparator<ParsedNumber> COMPARATOR = new Comparator<ParsedNumber>() {
}
}
+ @Override
+ public String toString() {
+ return "<RequireAffix>";
+ }
+
}
--- /dev/null
+// ยฉ 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package com.ibm.icu.impl.number.parse;
+
+/**
+ * @author sffc
+ *
+ */
+public class RequireCurrencyMatcher implements NumberParseMatcher {
+
+ @Override
+ public boolean match(StringSegment segment, ParsedNumber result) {
+ return false;
+ }
+
+ @Override
+ public void postProcess(ParsedNumber result) {
+ if (result.currencyCode == null && 0 == (result.flags & ParsedNumber.FLAG_HAS_DEFAULT_CURRENCY)) {
+ result.clear();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "<RequireCurrency>";
+ }
+
+}
}
}
+ @Override
+ public String toString() {
+ return "<RequireExponent>";
+ }
+
}
* @author sffc
*
*/
-public class RequirementsMatcher implements NumberParseMatcher {
+public class RequireNumberMatcher implements NumberParseMatcher {
@Override
public boolean match(StringSegment segment, ParsedNumber result) {
}
}
+ @Override
+ public String toString() {
+ return "<RequireNumber>";
+ }
+
}
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number.parse;
+import com.ibm.icu.lang.UCharacter;
+
/**
* A mutable class allowing for a String with a variable offset and length. The charAt, length, and subSequence methods
* all operate relative to the fixed offset into the String.
private final String str;
private int start;
private int end;
+ private final boolean ignoreCase;
- public StringSegment(String str) {
+ public StringSegment(String str, boolean ignoreCase) {
this.str = str;
this.start = 0;
this.end = str.length();
+ this.ignoreCase = ignoreCase;
}
public int getOffset() {
*/
public int getCommonPrefixLength(CharSequence other) {
int offset = 0;
- for (; offset < Math.min(length(), other.length()); offset++) {
- if (charAt(offset) != other.charAt(offset)) {
- break;
+ for (; offset < Math.min(length(), other.length());) {
+ if (ignoreCase) {
+ // NOTE: Character.codePointAt() returns the leading surrogate if it is the only char left in the
+ // string. UCharacter.foldCase() will simply return the same integer since it is not a valid code point.
+ int cp1 = Character.codePointAt(this, offset);
+ int cp2 = Character.codePointAt(other, offset);
+ if (cp1 != cp2 && UCharacter.foldCase(cp1, true) != UCharacter.foldCase(cp2, true)) {
+ break;
+ }
+ offset += Character.charCount(cp1);
+ } else {
+ // Case folding is not necessary. Use a slightly faster code path comparing chars with chars.
+ if (charAt(offset) != other.charAt(offset)) {
+ break;
+ }
}
}
return offset;
public static TextTrieMap<CurrencyStringInfo> getParsingTrie(ULocale locale, int type) {
List<TextTrieMap<CurrencyStringInfo>> currencyTrieVec = getCurrencyTrieVec(locale);
if (type == Currency.LONG_NAME) {
- return currencyTrieVec.get(0);
- } else {
return currencyTrieVec.get(1);
+ } else {
+ return currencyTrieVec.get(0);
}
}
ULocale locale, int startingCp, int type) {
List<TextTrieMap<CurrencyStringInfo>> currencyTrieVec = getCurrencyTrieVec(locale);
if (type == Currency.LONG_NAME) {
- return currencyTrieVec.get(0).openParseState(startingCp);
- } else {
return currencyTrieVec.get(1).openParseState(startingCp);
+ } else {
+ return currencyTrieVec.get(0).openParseState(startingCp);
}
}
begin
parse output breaks
// S is the only implementation that passes these cases.
-// C consumes the '9' as a digit and assumes number is negative
+// C and P consume the '9' as a digit and assumes number is negative
// J and JDK bail
-6549K 654 CJK
-// C consumes the '9' as a digit and assumes number is negative
+6549K 654 CJKP
+// C and P consume the '9' as a digit and assumes number is negative
// J and JDK bail
-6549N -654 CJK
+6549N -654 CJKP
test really strange prefix
set locale en
USD 53.45 53.45 USD J
53.45USD 53.45 USD CJ
USD53.45 53.45 USD
-(7.92) USD -7.92 USD
+// Right now, P will not parse the affix unless it contains the exact currency GBP.
+(7.92) USD -7.92 USD P
(7.92) GBP -7.92 GBP
-(7.926) USD -7.926 USD
-(7.926 USD) -7.926 USD CJ
-(USD 7.926) -7.926 USD CJ
-USD (7.926) -7.926 USD CJ
-USD (7.92) -7.92 USD CJ
-(7.92)USD -7.92 USD CJ
-USD(7.92) -7.92 USD CJ
-(8) USD -8 USD
+(7.926) USD -7.926 USD P
+(7.926 USD) -7.926 USD CJP
+(USD 7.926) -7.926 USD CJP
+USD (7.926) -7.926 USD CJP
+USD (7.92) -7.92 USD CJP
+(7.92)USD -7.92 USD CJP
+USD(7.92) -7.92 USD CJP
+(8) USD -8 USD P
-8 USD -8 USD C
67 USD 67 USD
53.45$ fail USD
US Dollar53.45 53.45 USD
US Dollat53.45 fail USD
53.45US Dollar 53.45 USD CJ
-US Dollars (53.45) -53.45 USD CJ
-(53.45) US Dollars -53.45 USD
-(53.45) Euros -53.45 EUR
-US Dollar (53.45) -53.45 USD CJ
-(53.45) US Dollar -53.45 USD
-US Dollars(53.45) -53.45 USD CJ
-(53.45)US Dollars -53.45 USD CJ
-US Dollar(53.45) -53.45 USD CJ
+US Dollars (53.45) -53.45 USD CJP
+(53.45) US Dollars -53.45 USD P
+(53.45) Euros -53.45 EUR P
+US Dollar (53.45) -53.45 USD CJP
+(53.45) US Dollar -53.45 USD P
+US Dollars(53.45) -53.45 USD CJP
+(53.45)US Dollars -53.45 USD CJP
+US Dollar(53.45) -53.45 USD CJP
US Dollat(53.45) fail USD
-(53.45)US Dollar -53.45 USD CJ
+(53.45)US Dollar -53.45 USD CJP
test parse currency ISO negative
USD 53.45 53.45 USD J
53.45USD 53.45 USD CJ
USD53.45 53.45 USD
--7.92 USD -7.92 USD
+// FIXME: Fix this one
+-7.92 USD -7.92 USD P
-7.92 GBP -7.92 GBP
--7.926 USD -7.926 USD
-USD -7.926 -7.926 USD CJ
--7.92USD -7.92 USD CJ
-USD-7.92 -7.92 USD CJ
--8 USD -8 USD
+// FIXME: Fix this one
+-7.926 USD -7.926 USD P
+USD -7.926 -7.926 USD CJP
+-7.92USD -7.92 USD CJP
+USD-7.92 -7.92 USD CJP
+-8 USD -8 USD P
67 USD 67 USD
53.45$ fail USD
US Dollars 53.45 53.45 USD J
USD 53.45 53.45 USD J
53.45USD 53.45 USD CJ
USD53.45 53.45 USD
-(7.92) USD -7.92 USD
-(7.92) GBP -7.92 GBP
-(7.926) USD -7.926 USD
-(7.926 USD) -7.926 USD CJ
-(USD 7.926) -7.926 USD CJ
-USD (7.926) -7.926 USD CJ
-USD (7.92) -7.92 USD CJ
-(7.92)USD -7.92 USD CJ
-USD(7.92) -7.92 USD CJ
-(8) USD -8 USD
+(7.92) USD -7.92 USD P
+(7.92) GBP -7.92 GBP P
+(7.926) USD -7.926 USD P
+(7.926 USD) -7.926 USD CJP
+(USD 7.926) -7.926 USD CJP
+USD (7.926) -7.926 USD CJP
+USD (7.92) -7.92 USD CJP
+(7.92)USD -7.92 USD CJP
+USD(7.92) -7.92 USD CJP
+(8) USD -8 USD P
-8 USD -8 USD C
67 USD 67 USD
// J throws a NullPointerException on the next case
USD 53.45 53.45 USD J
53.45USD 53.45 USD CJ
USD53.45 53.45 USD
-(7.92) USD -7.92 USD
-(7.92) GBP -7.92 GBP
-(7.926) USD -7.926 USD
-(7.926 USD) -7.926 USD CJ
-(USD 7.926) -7.926 USD CJ
-USD (7.926) -7.926 USD CJ
-USD (7.92) -7.92 USD CJ
-(7.92)USD -7.92 USD CJ
-USD(7.92) -7.92 USD CJ
-(8) USD -8 USD
+(7.92) USD -7.92 USD P
+(7.92) GBP -7.92 GBP P
+(7.926) USD -7.926 USD P
+(7.926 USD) -7.926 USD CJP
+(USD 7.926) -7.926 USD CJP
+USD (7.926) -7.926 USD CJP
+USD (7.92) -7.92 USD CJP
+(7.92)USD -7.92 USD CJP
+USD(7.92) -7.92 USD CJP
+(8) USD -8 USD P
-8 USD -8 USD C
67 USD 67 USD
53.45$ fail USD
53.45USD 53.45 USD CJ
USD53.45 53.45 USD
// S fails these because '(' is an incomplete prefix.
-(7.92) USD -7.92 USD CJS
-(7.92) GBP -7.92 GBP CJS
-(7.926) USD -7.926 USD CJS
-(7.926 USD) -7.926 USD CJS
-(USD 7.926) -7.926 USD J
-USD (7.926) -7.926 USD CJS
-USD (7.92) -7.92 USD CJS
-(7.92)USD -7.92 USD CJS
-USD(7.92) -7.92 USD CJS
-(8) USD -8 USD CJS
+(7.92) USD -7.92 USD CJSP
+(7.92) GBP -7.92 GBP CJSP
+(7.926) USD -7.926 USD CJSP
+(7.926 USD) -7.926 USD CJSP
+(USD 7.926) -7.926 USD JP
+USD (7.926) -7.926 USD CJSP
+USD (7.92) -7.92 USD CJSP
+(7.92)USD -7.92 USD CJSP
+USD(7.92) -7.92 USD CJSP
+(8) USD -8 USD CJSP
-8 USD -8 USD C
67 USD 67 USD C
53.45$ fail USD
actual = NumberParserImpl.parseStatic(tuple.parse,
ppos,
properties,
- DecimalFormatSymbols.getInstance(tuple.locale),
- false);
+ DecimalFormatSymbols.getInstance(tuple.locale));
} catch (IllegalArgumentException e) {
return "parse exception: " + e.getMessage();
}
}
}
-// @Override
-// public String parseCurrency(DataDrivenNumberFormatTestData tuple) {
-// String pattern = (tuple.pattern == null) ? "0" : tuple.pattern;
-// DecimalFormatProperties properties;
-// ParsePosition ppos = new ParsePosition(0);
-// CurrencyAmount actual;
-// try {
-// properties = PatternStringParser.parseToProperties(
-// pattern,
-// tuple.currency != null ? PatternStringParser.IGNORE_ROUNDING_ALWAYS
-// : PatternStringParser.IGNORE_ROUNDING_NEVER);
-// propertiesFromTuple(tuple, properties);
-// actual = NumberParserImpl.parseStatic(tuple.parse,
-// ppos,
-// properties,
-// DecimalFormatSymbols.getInstance(tuple.locale));
-// } catch (ParseException e) {
-// e.printStackTrace();
-// return "parse exception: " + e.getMessage();
-// }
-// if (ppos.getIndex() == 0 || actual.getCurrency().getCurrencyCode().equals("XXX")) {
-// return "Parse failed; got " + actual + ", but expected " + tuple.output;
-// }
-// BigDecimal expectedNumber = new BigDecimal(tuple.output);
-// if (expectedNumber.compareTo(new BigDecimal(actual.getNumber().toString())) != 0) {
-// return "Wrong number: Expected: " + expectedNumber + ", got: " + actual;
-// }
-// String expectedCurrency = tuple.outputCurrency;
-// if (!expectedCurrency.equals(actual.getCurrency().toString())) {
-// return "Wrong currency: Expected: " + expectedCurrency + ", got: " + actual;
-// }
-// return null;
-// }
+ @Override
+ public String parseCurrency(DataDrivenNumberFormatTestData tuple) {
+ String pattern = (tuple.pattern == null) ? "0" : tuple.pattern;
+ DecimalFormatProperties properties;
+ ParsePosition ppos = new ParsePosition(0);
+ CurrencyAmount actual;
+ try {
+ properties = PatternStringParser.parseToProperties(
+ pattern,
+ tuple.currency != null ? PatternStringParser.IGNORE_ROUNDING_ALWAYS
+ : PatternStringParser.IGNORE_ROUNDING_NEVER);
+ propertiesFromTuple(tuple, properties);
+ actual = NumberParserImpl.parseStaticCurrency(tuple.parse,
+ ppos,
+ properties,
+ DecimalFormatSymbols.getInstance(tuple.locale));
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ return "parse exception: " + e.getMessage();
+ }
+ if (ppos.getIndex() == 0 || actual.getCurrency().getCurrencyCode().equals("XXX")) {
+ return "Parse failed; got " + actual + ", but expected " + tuple.output;
+ }
+ BigDecimal expectedNumber = new BigDecimal(tuple.output);
+ if (expectedNumber.compareTo(new BigDecimal(actual.getNumber().toString())) != 0) {
+ return "Wrong number: Expected: " + expectedNumber + ", got: " + actual;
+ }
+ String expectedCurrency = tuple.outputCurrency;
+ if (!expectedCurrency.equals(actual.getCurrency().toString())) {
+ return "Wrong currency: Expected: " + expectedCurrency + ", got: " + actual;
+ }
+ return null;
+ }
};
/**
{ 3, "-๐ฑ๐ญ๐ฐ๐ฎ๐ฏ-", "0", 11, -51423. },
{ 3, "a51423US dollars", "a0ยคยคยค", 16, 51423. },
{ 3, "a 51423 US dollars", "a0ยคยคยค", 18, 51423. },
+ { 3, "514.23 USD", "0", 10, 514.23 },
+ { 3, "514.23 GBP", "0", 10, 514.23 },
{ 3, "a ๐ฑ๐ญ๐ฐ๐ฎ๐ฏ b", "a0b", 14, 51423. },
{ 3, "-a ๐ฑ๐ญ๐ฐ๐ฎ๐ฏ b", "a0b", 15, -51423. },
{ 3, "a -๐ฑ๐ญ๐ฐ๐ฎ๐ฏ b", "a0b", 15, -51423. },
{ 2, "a40b", "a0'0b'", 4, 4. }, // slow code path find the suffix "0b"
{ 3, "๐ฑ.๐ญ๐ฐ๐ฎE๐ฏ", "0", 12, 5142. },
{ 3, "๐ฑ.๐ญ๐ฐ๐ฎE-๐ฏ", "0", 13, 0.005142 },
+ { 3, "๐ฑ.๐ญ๐ฐ๐ฎe-๐ฏ", "0", 13, 0.005142 },
{ 3, "5,142.50 Canadian dollars", "0", 25, 5142.5 },
{ 3, "0", "0", 1, 0.0 } };