ICU-13513 Starting to tie in with existing code. Working through the data-driven test file first.
X-SVN-Rev: 40726
This commit is contained in:
parent
47d7ebe968
commit
a0de8d89c5
@ -0,0 +1,56 @@
|
|||||||
|
// © 2017 and later: Unicode, Inc. and others.
|
||||||
|
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||||
|
package com.ibm.icu.impl.number;
|
||||||
|
|
||||||
|
import com.ibm.icu.impl.StandardPlural;
|
||||||
|
import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo;
|
||||||
|
import com.ibm.icu.text.CurrencyPluralInfo;
|
||||||
|
|
||||||
|
public class CurrencyPluralInfoAffixProvider implements AffixPatternProvider {
|
||||||
|
private final AffixPatternProvider[] affixesByPlural;
|
||||||
|
|
||||||
|
public CurrencyPluralInfoAffixProvider(CurrencyPluralInfo cpi) {
|
||||||
|
affixesByPlural = new ParsedPatternInfo[StandardPlural.COUNT];
|
||||||
|
for (StandardPlural plural : StandardPlural.VALUES) {
|
||||||
|
affixesByPlural[plural.ordinal()] = PatternStringParser
|
||||||
|
.parseToPatternInfo(cpi.getCurrencyPluralPattern(plural.getKeyword()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char charAt(int flags, int i) {
|
||||||
|
int pluralOrdinal = (flags & Flags.PLURAL_MASK);
|
||||||
|
return affixesByPlural[pluralOrdinal].charAt(flags, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int length(int flags) {
|
||||||
|
int pluralOrdinal = (flags & Flags.PLURAL_MASK);
|
||||||
|
return affixesByPlural[pluralOrdinal].length(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean positiveHasPlusSign() {
|
||||||
|
return affixesByPlural[StandardPlural.OTHER.ordinal()].positiveHasPlusSign();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNegativeSubpattern() {
|
||||||
|
return affixesByPlural[StandardPlural.OTHER.ordinal()].hasNegativeSubpattern();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean negativeHasMinusSign() {
|
||||||
|
return affixesByPlural[StandardPlural.OTHER.ordinal()].negativeHasMinusSign();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasCurrencySign() {
|
||||||
|
return affixesByPlural[StandardPlural.OTHER.ordinal()].hasCurrencySign();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsSymbolType(int type) {
|
||||||
|
return affixesByPlural[StandardPlural.OTHER.ordinal()].containsSymbolType(type);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
// © 2017 and later: Unicode, Inc. and others.
|
||||||
|
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||||
|
package com.ibm.icu.impl.number;
|
||||||
|
|
||||||
|
public class PropertiesAffixPatternProvider implements AffixPatternProvider {
|
||||||
|
private final String posPrefix;
|
||||||
|
private final String posSuffix;
|
||||||
|
private final String negPrefix;
|
||||||
|
private final String negSuffix;
|
||||||
|
|
||||||
|
public PropertiesAffixPatternProvider(DecimalFormatProperties properties) {
|
||||||
|
// There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the
|
||||||
|
// explicit setters (setPositivePrefix and friends). The way to resolve the settings is as follows:
|
||||||
|
//
|
||||||
|
// 1) If the explicit setting is present for the field, use it.
|
||||||
|
// 2) Otherwise, follows UTS 35 rules based on the pattern string.
|
||||||
|
//
|
||||||
|
// Importantly, the explicit setters affect only the one field they override. If you set the positive
|
||||||
|
// prefix, that should not affect the negative prefix. Since it is impossible for the user of this class
|
||||||
|
// to know whether the origin for a string was the override or the pattern, we have to say that we always
|
||||||
|
// have a negative subpattern and perform all resolution logic here.
|
||||||
|
|
||||||
|
// Convenience: Extract the properties into local variables.
|
||||||
|
// Variables are named with three chars: [p/n][p/s][o/p]
|
||||||
|
// [p/n] => p for positive, n for negative
|
||||||
|
// [p/s] => p for prefix, s for suffix
|
||||||
|
// [o/p] => o for escaped custom override string, p for pattern string
|
||||||
|
String ppo = AffixUtils.escape(properties.getPositivePrefix());
|
||||||
|
String pso = AffixUtils.escape(properties.getPositiveSuffix());
|
||||||
|
String npo = AffixUtils.escape(properties.getNegativePrefix());
|
||||||
|
String nso = AffixUtils.escape(properties.getNegativeSuffix());
|
||||||
|
String ppp = properties.getPositivePrefixPattern();
|
||||||
|
String psp = properties.getPositiveSuffixPattern();
|
||||||
|
String npp = properties.getNegativePrefixPattern();
|
||||||
|
String nsp = properties.getNegativeSuffixPattern();
|
||||||
|
|
||||||
|
if (ppo != null) {
|
||||||
|
posPrefix = ppo;
|
||||||
|
} else if (ppp != null) {
|
||||||
|
posPrefix = ppp;
|
||||||
|
} else {
|
||||||
|
// UTS 35: Default positive prefix is empty string.
|
||||||
|
posPrefix = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pso != null) {
|
||||||
|
posSuffix = pso;
|
||||||
|
} else if (psp != null) {
|
||||||
|
posSuffix = psp;
|
||||||
|
} else {
|
||||||
|
// UTS 35: Default positive suffix is empty string.
|
||||||
|
posSuffix = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (npo != null) {
|
||||||
|
negPrefix = npo;
|
||||||
|
} else if (npp != null) {
|
||||||
|
negPrefix = npp;
|
||||||
|
} else {
|
||||||
|
// UTS 35: Default negative prefix is "-" with positive prefix.
|
||||||
|
// Important: We prepend the "-" to the pattern, not the override!
|
||||||
|
negPrefix = ppp == null ? "-" : "-" + ppp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nso != null) {
|
||||||
|
negSuffix = nso;
|
||||||
|
} else if (nsp != null) {
|
||||||
|
negSuffix = nsp;
|
||||||
|
} else {
|
||||||
|
// UTS 35: Default negative prefix is the positive prefix.
|
||||||
|
negSuffix = psp == null ? "" : psp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char charAt(int flags, int i) {
|
||||||
|
return getStringForFlags(flags).charAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int length(int flags) {
|
||||||
|
return getStringForFlags(flags).length();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getStringForFlags(int flags) {
|
||||||
|
boolean prefix = (flags & Flags.PREFIX) != 0;
|
||||||
|
boolean negative = (flags & Flags.NEGATIVE_SUBPATTERN) != 0;
|
||||||
|
if (prefix && negative) {
|
||||||
|
return negPrefix;
|
||||||
|
} else if (prefix) {
|
||||||
|
return posPrefix;
|
||||||
|
} else if (negative) {
|
||||||
|
return negSuffix;
|
||||||
|
} else {
|
||||||
|
return posSuffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean positiveHasPlusSign() {
|
||||||
|
return AffixUtils.containsType(posPrefix, AffixUtils.TYPE_PLUS_SIGN)
|
||||||
|
|| AffixUtils.containsType(posSuffix, AffixUtils.TYPE_PLUS_SIGN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNegativeSubpattern() {
|
||||||
|
// See comments in the constructor for more information on why this is always true.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean negativeHasMinusSign() {
|
||||||
|
return AffixUtils.containsType(negPrefix, AffixUtils.TYPE_MINUS_SIGN)
|
||||||
|
|| AffixUtils.containsType(negSuffix, AffixUtils.TYPE_MINUS_SIGN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasCurrencySign() {
|
||||||
|
return AffixUtils.hasCurrencySymbols(posPrefix) || AffixUtils.hasCurrencySymbols(posSuffix)
|
||||||
|
|| AffixUtils.hasCurrencySymbols(negPrefix) || AffixUtils.hasCurrencySymbols(negSuffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsSymbolType(int type) {
|
||||||
|
return AffixUtils.containsType(posPrefix, type) || AffixUtils.containsType(posSuffix, type)
|
||||||
|
|| AffixUtils.containsType(negPrefix, type) || AffixUtils.containsType(negSuffix, type);
|
||||||
|
}
|
||||||
|
}
|
@ -16,22 +16,37 @@ import com.ibm.icu.text.UnicodeSet;
|
|||||||
*/
|
*/
|
||||||
public class DecimalMatcher implements NumberParseMatcher {
|
public class DecimalMatcher implements NumberParseMatcher {
|
||||||
|
|
||||||
/**
|
// TODO: Re-generate these sets from the database. They probably haven't been updated in a while.
|
||||||
* @return
|
private static final UnicodeSet UNISET_PERIOD_LIKE = new UnicodeSet("[.\\u2024\\u3002\\uFE12\\uFE52\\uFF0E\\uFF61]")
|
||||||
*/
|
.freeze();
|
||||||
|
private static final UnicodeSet UNISET_STRICT_PERIOD_LIKE = new UnicodeSet("[.\\u2024\\uFE52\\uFF0E\\uFF61]")
|
||||||
|
.freeze();
|
||||||
|
private static final UnicodeSet UNISET_COMMA_LIKE = new UnicodeSet(
|
||||||
|
"[,\\u060C\\u066B\\u3001\\uFE10\\uFE11\\uFE50\\uFE51\\uFF0C\\uFF64]").freeze();
|
||||||
|
private static final UnicodeSet UNISET_STRICT_COMMA_LIKE = new UnicodeSet("[,\\u066B\\uFE10\\uFE50\\uFF0C]")
|
||||||
|
.freeze();
|
||||||
|
private static final UnicodeSet UNISET_OTHER_GROUPING_SEPARATORS = new UnicodeSet(
|
||||||
|
"[\\ '\\u00A0\\u066C\\u2000-\\u200A\\u2018\\u2019\\u202F\\u205F\\u3000\\uFF07]").freeze();
|
||||||
|
|
||||||
public static DecimalMatcher getInstance(DecimalFormatSymbols symbols) {
|
public static DecimalMatcher getInstance(DecimalFormatSymbols symbols) {
|
||||||
// TODO(sffc): Auto-generated method stub
|
String groupingSeparator = symbols.getGroupingSeparatorString();
|
||||||
return new DecimalMatcher(symbols.getDigitStrings(),
|
UnicodeSet groupingSet = UNISET_COMMA_LIKE.contains(groupingSeparator) ? UNISET_COMMA_LIKE.cloneAsThawed().addAll(UNISET_OTHER_GROUPING_SEPARATORS).freeze()
|
||||||
new UnicodeSet("[,]").freeze(),
|
: UNISET_PERIOD_LIKE.contains(groupingSeparator) ? UNISET_PERIOD_LIKE.cloneAsThawed().addAll(UNISET_OTHER_GROUPING_SEPARATORS).freeze()
|
||||||
new UnicodeSet("[.]").freeze(),
|
: UNISET_OTHER_GROUPING_SEPARATORS.contains(groupingSeparator)
|
||||||
false);
|
? UNISET_OTHER_GROUPING_SEPARATORS
|
||||||
|
: new UnicodeSet().addAll(groupingSeparator).freeze();
|
||||||
|
|
||||||
|
String decimalSeparator = symbols.getDecimalSeparatorString();
|
||||||
|
UnicodeSet decimalSet = UNISET_COMMA_LIKE.contains(decimalSeparator) ? UNISET_COMMA_LIKE
|
||||||
|
: UNISET_PERIOD_LIKE.contains(decimalSeparator) ? UNISET_PERIOD_LIKE
|
||||||
|
: new UnicodeSet().addAll(decimalSeparator).freeze();
|
||||||
|
|
||||||
|
return new DecimalMatcher(symbols.getDigitStrings(), groupingSet, decimalSet, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DecimalMatcher getExponentInstance(DecimalFormatSymbols symbols) {
|
public static DecimalMatcher getExponentInstance(DecimalFormatSymbols symbols) {
|
||||||
return new DecimalMatcher(symbols.getDigitStrings(),
|
return new DecimalMatcher(symbols
|
||||||
new UnicodeSet("[,]").freeze(),
|
.getDigitStrings(), new UnicodeSet("[,]").freeze(), new UnicodeSet("[.]").freeze(), true);
|
||||||
new UnicodeSet("[.]").freeze(),
|
|
||||||
true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String[] digitStrings;
|
private final String[] digitStrings;
|
||||||
@ -39,6 +54,7 @@ public class DecimalMatcher implements NumberParseMatcher {
|
|||||||
private final UnicodeSet decimalUniSet;
|
private final UnicodeSet decimalUniSet;
|
||||||
private final UnicodeSet separatorSet;
|
private final UnicodeSet separatorSet;
|
||||||
public boolean requireGroupingMatch = false;
|
public boolean requireGroupingMatch = false;
|
||||||
|
public boolean groupingEnabled = true;
|
||||||
private final int grouping1 = 3;
|
private final int grouping1 = 3;
|
||||||
private final int grouping2 = 3;
|
private final int grouping2 = 3;
|
||||||
private final boolean isScientific;
|
private final boolean isScientific;
|
||||||
@ -51,7 +67,11 @@ public class DecimalMatcher implements NumberParseMatcher {
|
|||||||
this.digitStrings = digitStrings;
|
this.digitStrings = digitStrings;
|
||||||
this.groupingUniSet = groupingUniSet;
|
this.groupingUniSet = groupingUniSet;
|
||||||
this.decimalUniSet = decimalUniSet;
|
this.decimalUniSet = decimalUniSet;
|
||||||
separatorSet = groupingUniSet.cloneAsThawed().addAll(decimalUniSet).freeze();
|
if (groupingEnabled) {
|
||||||
|
separatorSet = groupingUniSet.cloneAsThawed().addAll(decimalUniSet).freeze();
|
||||||
|
} else {
|
||||||
|
separatorSet = decimalUniSet;
|
||||||
|
}
|
||||||
this.isScientific = isScientific;
|
this.isScientific = isScientific;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +85,7 @@ public class DecimalMatcher implements NumberParseMatcher {
|
|||||||
int currGroup = 0;
|
int currGroup = 0;
|
||||||
int separator = -1;
|
int separator = -1;
|
||||||
int lastSeparatorOffset = segment.getOffset();
|
int lastSeparatorOffset = segment.getOffset();
|
||||||
|
int scientificAdjustment = 0;
|
||||||
boolean hasPartialPrefix = false;
|
boolean hasPartialPrefix = false;
|
||||||
boolean seenBothSeparators = false;
|
boolean seenBothSeparators = false;
|
||||||
while (segment.length() > 0) {
|
while (segment.length() > 0) {
|
||||||
@ -97,7 +118,7 @@ public class DecimalMatcher implements NumberParseMatcher {
|
|||||||
// If found, save it in the DecimalQuantity or scientific adjustment.
|
// If found, save it in the DecimalQuantity or scientific adjustment.
|
||||||
if (digit >= 0) {
|
if (digit >= 0) {
|
||||||
if (isScientific) {
|
if (isScientific) {
|
||||||
result.scientificAdjustment = digit + result.scientificAdjustment * 10;
|
scientificAdjustment = digit + scientificAdjustment * 10;
|
||||||
} else {
|
} else {
|
||||||
if (result.quantity == null) {
|
if (result.quantity == null) {
|
||||||
result.quantity = new DecimalQuantity_DualStorageBCD();
|
result.quantity = new DecimalQuantity_DualStorageBCD();
|
||||||
@ -117,13 +138,13 @@ public class DecimalMatcher implements NumberParseMatcher {
|
|||||||
if (requireGroupingMatch && currGroup == 0) {
|
if (requireGroupingMatch && currGroup == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (separator == cp && groupingUniSet.contains(cp)) {
|
} else if (groupingEnabled && separator == cp && groupingUniSet.contains(cp)) {
|
||||||
// Second or later grouping separator.
|
// Second or later grouping separator.
|
||||||
if (requireGroupingMatch && currGroup != grouping2) {
|
if (requireGroupingMatch && currGroup != grouping2) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (separator != cp && decimalUniSet.contains(cp)) {
|
} else if (groupingEnabled && separator != cp && decimalUniSet.contains(cp)) {
|
||||||
// Decimal separator.
|
// Decimal separator after a grouping separator.
|
||||||
if (requireGroupingMatch && currGroup != grouping1) {
|
if (requireGroupingMatch && currGroup != grouping1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -141,10 +162,13 @@ public class DecimalMatcher implements NumberParseMatcher {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seenBothSeparators || (separator != -1 && decimalUniSet.contains(separator))) {
|
if (isScientific) {
|
||||||
|
result.quantity.adjustMagnitude(scientificAdjustment);
|
||||||
|
} else if (seenBothSeparators || (separator != -1 && decimalUniSet.contains(separator))) {
|
||||||
result.quantity.adjustMagnitude(-currGroup);
|
result.quantity.adjustMagnitude(-currGroup);
|
||||||
} else if (requireGroupingMatch && separator != -1 && groupingUniSet.contains(separator)
|
} else if (separator != -1
|
||||||
&& currGroup != grouping1) {
|
&& ((requireGroupingMatch && groupingUniSet.contains(separator) && currGroup != grouping1)
|
||||||
|
|| !groupingEnabled)) {
|
||||||
result.quantity.adjustMagnitude(-currGroup);
|
result.quantity.adjustMagnitude(-currGroup);
|
||||||
result.quantity.roundToMagnitude(0, RoundingUtils.mathContextUnlimited(RoundingMode.FLOOR));
|
result.quantity.roundToMagnitude(0, RoundingUtils.mathContextUnlimited(RoundingMode.FLOOR));
|
||||||
segment.setOffset(lastSeparatorOffset);
|
segment.setOffset(lastSeparatorOffset);
|
||||||
|
@ -2,14 +2,19 @@
|
|||||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||||
package com.ibm.icu.impl.number.parse;
|
package com.ibm.icu.impl.number.parse;
|
||||||
|
|
||||||
|
import java.text.ParsePosition;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.ibm.icu.impl.number.AffixPatternProvider;
|
import com.ibm.icu.impl.number.AffixPatternProvider;
|
||||||
import com.ibm.icu.impl.number.AffixUtils;
|
import com.ibm.icu.impl.number.AffixUtils;
|
||||||
|
import com.ibm.icu.impl.number.CustomSymbolCurrency;
|
||||||
|
import com.ibm.icu.impl.number.DecimalFormatProperties;
|
||||||
import com.ibm.icu.impl.number.MutablePatternModifier;
|
import com.ibm.icu.impl.number.MutablePatternModifier;
|
||||||
|
import com.ibm.icu.impl.number.Parse.ParseMode;
|
||||||
import com.ibm.icu.impl.number.PatternStringParser;
|
import com.ibm.icu.impl.number.PatternStringParser;
|
||||||
|
import com.ibm.icu.impl.number.PropertiesAffixPatternProvider;
|
||||||
import com.ibm.icu.number.NumberFormatter.SignDisplay;
|
import com.ibm.icu.number.NumberFormatter.SignDisplay;
|
||||||
import com.ibm.icu.number.NumberFormatter.UnitWidth;
|
import com.ibm.icu.number.NumberFormatter.UnitWidth;
|
||||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||||
@ -23,7 +28,9 @@ import com.ibm.icu.util.ULocale;
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class NumberParserImpl {
|
public class NumberParserImpl {
|
||||||
public static NumberParserImpl createParserFromPattern(String pattern) {
|
public static NumberParserImpl createParserFromPattern(String pattern, boolean strictGrouping) {
|
||||||
|
// Temporary frontend for testing.
|
||||||
|
|
||||||
NumberParserImpl parser = new NumberParserImpl();
|
NumberParserImpl parser = new NumberParserImpl();
|
||||||
ULocale locale = ULocale.ENGLISH;
|
ULocale locale = ULocale.ENGLISH;
|
||||||
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
|
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
|
||||||
@ -32,10 +39,7 @@ public class NumberParserImpl {
|
|||||||
AffixPatternProvider provider = PatternStringParser.parseToPatternInfo(pattern);
|
AffixPatternProvider provider = PatternStringParser.parseToPatternInfo(pattern);
|
||||||
mod.setPatternInfo(provider);
|
mod.setPatternInfo(provider);
|
||||||
mod.setPatternAttributes(SignDisplay.AUTO, false);
|
mod.setPatternAttributes(SignDisplay.AUTO, false);
|
||||||
mod.setSymbols(symbols,
|
mod.setSymbols(symbols, Currency.getInstance("USD"), UnitWidth.FULL_NAME, null);
|
||||||
Currency.getInstance("USD"),
|
|
||||||
UnitWidth.FULL_NAME,
|
|
||||||
null);
|
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
if (provider.containsSymbolType(AffixUtils.TYPE_PERCENT)) {
|
if (provider.containsSymbolType(AffixUtils.TYPE_PERCENT)) {
|
||||||
flags |= ParsedNumber.FLAG_PERCENT;
|
flags |= ParsedNumber.FLAG_PERCENT;
|
||||||
@ -45,18 +49,77 @@ public class NumberParserImpl {
|
|||||||
}
|
}
|
||||||
AffixMatcher.generateFromPatternModifier(mod, flags, parser);
|
AffixMatcher.generateFromPatternModifier(mod, flags, parser);
|
||||||
|
|
||||||
parser.addMatcher(DecimalMatcher.getInstance(symbols));
|
DecimalMatcher decimalMatcher = DecimalMatcher.getInstance(symbols);
|
||||||
|
decimalMatcher.requireGroupingMatch = strictGrouping;
|
||||||
|
parser.addMatcher(decimalMatcher);
|
||||||
parser.addMatcher(WhitespaceMatcher.getInstance());
|
parser.addMatcher(WhitespaceMatcher.getInstance());
|
||||||
parser.addMatcher(new MinusSignMatcher());
|
parser.addMatcher(new MinusSignMatcher());
|
||||||
parser.addMatcher(new ScientificMatcher(symbols));
|
parser.addMatcher(new ScientificMatcher(symbols));
|
||||||
parser.addMatcher(new CurrencyMatcher(locale));
|
parser.addMatcher(new CurrencyMatcher(locale));
|
||||||
|
|
||||||
parser.setComparator(new Comparator<ParsedNumber>() {
|
parser.freeze();
|
||||||
@Override
|
return parser;
|
||||||
public int compare(ParsedNumber o1, ParsedNumber o2) {
|
}
|
||||||
return o1.charsConsumed - o2.charsConsumed;
|
|
||||||
}
|
public static Number parseStatic(String input,
|
||||||
});
|
ParsePosition ppos,
|
||||||
|
DecimalFormatProperties properties,
|
||||||
|
DecimalFormatSymbols symbols) {
|
||||||
|
NumberParserImpl parser = createParserFromProperties(properties, symbols);
|
||||||
|
ParsedNumber result = new ParsedNumber();
|
||||||
|
parser.parse(input, true, result);
|
||||||
|
ppos.setIndex(result.charsConsumed);
|
||||||
|
return result.getDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NumberParserImpl createParserFromProperties(
|
||||||
|
DecimalFormatProperties properties,
|
||||||
|
DecimalFormatSymbols symbols) {
|
||||||
|
NumberParserImpl parser = new NumberParserImpl();
|
||||||
|
ULocale locale = symbols.getULocale();
|
||||||
|
Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols);
|
||||||
|
|
||||||
|
//////////////////////
|
||||||
|
/// 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);
|
||||||
|
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)) {
|
||||||
|
flags |= ParsedNumber.FLAG_PERCENT;
|
||||||
|
}
|
||||||
|
if (provider.containsSymbolType(AffixUtils.TYPE_PERMILLE)) {
|
||||||
|
flags |= ParsedNumber.FLAG_PERMILLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
AffixMatcher.generateFromPatternModifier(mod, flags, parser);
|
||||||
|
|
||||||
|
///////////////////////////////
|
||||||
|
/// OTHER STANDARD MATCHERS ///
|
||||||
|
///////////////////////////////
|
||||||
|
|
||||||
|
DecimalMatcher decimalMatcher = DecimalMatcher.getInstance(symbols);
|
||||||
|
decimalMatcher.groupingEnabled = properties.getGroupingSize() > 0;
|
||||||
|
decimalMatcher.requireGroupingMatch = properties.getParseMode() == ParseMode.STRICT;
|
||||||
|
parser.addMatcher(decimalMatcher);
|
||||||
|
parser.addMatcher(WhitespaceMatcher.getInstance());
|
||||||
|
parser.addMatcher(new MinusSignMatcher());
|
||||||
|
parser.addMatcher(new ScientificMatcher(symbols));
|
||||||
|
|
||||||
|
////////////////////////
|
||||||
|
/// CURRENCY MATCHER ///
|
||||||
|
////////////////////////
|
||||||
|
|
||||||
|
parser.addMatcher(new CurrencyMatcher(locale));
|
||||||
|
|
||||||
parser.freeze();
|
parser.freeze();
|
||||||
return parser;
|
return parser;
|
||||||
}
|
}
|
||||||
@ -67,6 +130,7 @@ public class NumberParserImpl {
|
|||||||
|
|
||||||
public NumberParserImpl() {
|
public NumberParserImpl() {
|
||||||
matchers = new ArrayList<NumberParseMatcher>();
|
matchers = new ArrayList<NumberParseMatcher>();
|
||||||
|
comparator = ParsedNumber.COMPARATOR; // default value
|
||||||
frozen = false;
|
frozen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||||
package com.ibm.icu.impl.number.parse;
|
package com.ibm.icu.impl.number.parse;
|
||||||
|
|
||||||
import com.ibm.icu.impl.number.DecimalQuantity;
|
import java.util.Comparator;
|
||||||
|
|
||||||
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
|
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -12,17 +13,46 @@ import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
|
|||||||
public class ParsedNumber {
|
public class ParsedNumber {
|
||||||
|
|
||||||
public DecimalQuantity_DualStorageBCD quantity = null;
|
public DecimalQuantity_DualStorageBCD quantity = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of chars accepted during parsing. This is NOT necessarily the same as the StringSegment offset; "weak"
|
||||||
|
* chars, like whitespace, change the offset, but the charsConsumed is not touched until a "strong" char is
|
||||||
|
* encountered.
|
||||||
|
*/
|
||||||
public int charsConsumed = 0;
|
public int charsConsumed = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean flags (see constants below).
|
||||||
|
*/
|
||||||
public int flags = 0;
|
public int flags = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The prefix string that got consumed.
|
||||||
|
*/
|
||||||
public String prefix = null;
|
public String prefix = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The suffix string that got consumed.
|
||||||
|
*/
|
||||||
public String suffix = null;
|
public String suffix = null;
|
||||||
public int scientificAdjustment = 0;
|
|
||||||
|
/**
|
||||||
|
* The currency that got consumed.
|
||||||
|
*/
|
||||||
public String currencyCode = null;
|
public String currencyCode = null;
|
||||||
|
|
||||||
public static final int FLAG_NEGATIVE = 0x0001;
|
public static final int FLAG_NEGATIVE = 0x0001;
|
||||||
public static final int FLAG_PERCENT = 0x0002;
|
public static final int FLAG_PERCENT = 0x0002;
|
||||||
public static final int FLAG_PERMILLE = 0x0004;
|
public static final int FLAG_PERMILLE = 0x0004;
|
||||||
|
|
||||||
|
/** A Comparator that favors ParsedNumbers with the most chars consumed. */
|
||||||
|
public static final Comparator<ParsedNumber> COMPARATOR = new Comparator<ParsedNumber>() {
|
||||||
|
@Override
|
||||||
|
public int compare(ParsedNumber o1, ParsedNumber o2) {
|
||||||
|
return o1.charsConsumed - o2.charsConsumed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param other
|
* @param other
|
||||||
*/
|
*/
|
||||||
@ -32,7 +62,6 @@ public class ParsedNumber {
|
|||||||
flags = other.flags;
|
flags = other.flags;
|
||||||
prefix = other.prefix;
|
prefix = other.prefix;
|
||||||
suffix = other.suffix;
|
suffix = other.suffix;
|
||||||
scientificAdjustment = other.scientificAdjustment;
|
|
||||||
currencyCode = other.currencyCode;
|
currencyCode = other.currencyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,9 +70,7 @@ public class ParsedNumber {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public double getDouble() {
|
public double getDouble() {
|
||||||
DecimalQuantity copy = quantity.createCopy();
|
double d = quantity.toDouble();
|
||||||
copy.adjustMagnitude(scientificAdjustment);
|
|
||||||
double d = copy.toDouble();
|
|
||||||
if (0 != (flags & FLAG_NEGATIVE)) {
|
if (0 != (flags & FLAG_NEGATIVE)) {
|
||||||
d = -d;
|
d = -d;
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,12 @@ import com.ibm.icu.text.DecimalFormatSymbols;
|
|||||||
public class ScientificMatcher implements NumberParseMatcher {
|
public class ScientificMatcher implements NumberParseMatcher {
|
||||||
|
|
||||||
private final String exponentSeparatorString;
|
private final String exponentSeparatorString;
|
||||||
|
private final String minusSignString;
|
||||||
private final DecimalMatcher exponentMatcher;
|
private final DecimalMatcher exponentMatcher;
|
||||||
|
|
||||||
public ScientificMatcher(DecimalFormatSymbols symbols) {
|
public ScientificMatcher(DecimalFormatSymbols symbols) {
|
||||||
exponentSeparatorString = symbols.getExponentSeparator();
|
exponentSeparatorString = symbols.getExponentSeparator();
|
||||||
|
minusSignString = symbols.getMinusSignString();
|
||||||
exponentMatcher = DecimalMatcher.getExponentInstance(symbols);
|
exponentMatcher = DecimalMatcher.getExponentInstance(symbols);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,19 +28,33 @@ public class ScientificMatcher implements NumberParseMatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// First match the scientific separator, and then match another number after it.
|
// First match the scientific separator, and then match another number after it.
|
||||||
int overlap = segment.getCommonPrefixLength(exponentSeparatorString);
|
int overlap1 = segment.getCommonPrefixLength(exponentSeparatorString);
|
||||||
if (overlap == exponentSeparatorString.length()) {
|
if (overlap1 == exponentSeparatorString.length()) {
|
||||||
// Full exponent separator match; try to match digits.
|
// Full exponent separator match; allow a sign, and then try to match digits.
|
||||||
segment.adjustOffset(overlap);
|
segment.adjustOffset(overlap1);
|
||||||
|
int overlap2 = segment.getCommonPrefixLength(minusSignString);
|
||||||
|
boolean sign = false;
|
||||||
|
if (overlap2 == minusSignString.length()) {
|
||||||
|
sign = true;
|
||||||
|
segment.adjustOffset(overlap2);
|
||||||
|
} else if (overlap2 == segment.length()) {
|
||||||
|
// Partial sign match
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int digitsOffset = segment.getOffset();
|
int digitsOffset = segment.getOffset();
|
||||||
|
int oldMagnitude = result.quantity.getMagnitude();
|
||||||
boolean digitsReturnValue = exponentMatcher.match(segment, result);
|
boolean digitsReturnValue = exponentMatcher.match(segment, result);
|
||||||
|
if (result.quantity.getMagnitude() != oldMagnitude && sign) {
|
||||||
|
result.quantity.adjustMagnitude(2*(oldMagnitude - result.quantity.getMagnitude()));
|
||||||
|
}
|
||||||
if (segment.getOffset() == digitsOffset) {
|
if (segment.getOffset() == digitsOffset) {
|
||||||
// No digits were matched; un-match the exponent separator.
|
// No digits were matched; un-match the exponent separator.
|
||||||
segment.adjustOffset(-overlap);
|
segment.adjustOffset(-overlap1);
|
||||||
}
|
}
|
||||||
return digitsReturnValue;
|
return digitsReturnValue;
|
||||||
|
|
||||||
} else if (overlap == segment.length()) {
|
} else if (overlap1 == segment.length()) {
|
||||||
// Partial exponent separator match
|
// Partial exponent separator match
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
// © 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 StrictMatcher implements NumberParseMatcher {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean match(StringSegment segment, ParsedNumber result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postProcess(ParsedNumber result) {
|
||||||
|
if (result.prefix == null && result.suffix == null) {
|
||||||
|
// Do something?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,16 +5,15 @@ package com.ibm.icu.number;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
|
|
||||||
import com.ibm.icu.impl.StandardPlural;
|
|
||||||
import com.ibm.icu.impl.number.AffixPatternProvider;
|
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.CustomSymbolCurrency;
|
||||||
import com.ibm.icu.impl.number.DecimalFormatProperties;
|
import com.ibm.icu.impl.number.DecimalFormatProperties;
|
||||||
import com.ibm.icu.impl.number.MacroProps;
|
import com.ibm.icu.impl.number.MacroProps;
|
||||||
import com.ibm.icu.impl.number.MultiplierImpl;
|
import com.ibm.icu.impl.number.MultiplierImpl;
|
||||||
import com.ibm.icu.impl.number.Padder;
|
import com.ibm.icu.impl.number.Padder;
|
||||||
import com.ibm.icu.impl.number.PatternStringParser;
|
import com.ibm.icu.impl.number.PatternStringParser;
|
||||||
import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo;
|
import com.ibm.icu.impl.number.PropertiesAffixPatternProvider;
|
||||||
import com.ibm.icu.impl.number.RoundingUtils;
|
import com.ibm.icu.impl.number.RoundingUtils;
|
||||||
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
|
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
|
||||||
import com.ibm.icu.number.NumberFormatter.SignDisplay;
|
import com.ibm.icu.number.NumberFormatter.SignDisplay;
|
||||||
@ -22,7 +21,6 @@ import com.ibm.icu.number.Rounder.FractionRounderImpl;
|
|||||||
import com.ibm.icu.number.Rounder.IncrementRounderImpl;
|
import com.ibm.icu.number.Rounder.IncrementRounderImpl;
|
||||||
import com.ibm.icu.number.Rounder.SignificantRounderImpl;
|
import com.ibm.icu.number.Rounder.SignificantRounderImpl;
|
||||||
import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
|
import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
|
||||||
import com.ibm.icu.text.CurrencyPluralInfo;
|
|
||||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||||
import com.ibm.icu.util.Currency;
|
import com.ibm.icu.util.Currency;
|
||||||
import com.ibm.icu.util.Currency.CurrencyUsage;
|
import com.ibm.icu.util.Currency.CurrencyUsage;
|
||||||
@ -60,7 +58,7 @@ final class NumberPropertyMapper {
|
|||||||
* @param symbols
|
* @param symbols
|
||||||
* The symbols associated with the property bag.
|
* The symbols associated with the property bag.
|
||||||
* @param exportedProperties
|
* @param exportedProperties
|
||||||
* A property bag in which to store validated properties.
|
* A property bag in which to store validated properties. Used by some DecimalFormat getters.
|
||||||
* @return A new MacroProps containing all of the information in the Properties.
|
* @return A new MacroProps containing all of the information in the Properties.
|
||||||
*/
|
*/
|
||||||
public static MacroProps oldToNew(DecimalFormatProperties properties, DecimalFormatSymbols symbols,
|
public static MacroProps oldToNew(DecimalFormatProperties properties, DecimalFormatSymbols symbols,
|
||||||
@ -334,178 +332,4 @@ final class NumberPropertyMapper {
|
|||||||
|
|
||||||
return macros;
|
return macros;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class PropertiesAffixPatternProvider implements AffixPatternProvider {
|
|
||||||
private final String posPrefix;
|
|
||||||
private final String posSuffix;
|
|
||||||
private final String negPrefix;
|
|
||||||
private final String negSuffix;
|
|
||||||
|
|
||||||
public PropertiesAffixPatternProvider(DecimalFormatProperties properties) {
|
|
||||||
// There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the
|
|
||||||
// explicit setters (setPositivePrefix and friends). The way to resolve the settings is as follows:
|
|
||||||
//
|
|
||||||
// 1) If the explicit setting is present for the field, use it.
|
|
||||||
// 2) Otherwise, follows UTS 35 rules based on the pattern string.
|
|
||||||
//
|
|
||||||
// Importantly, the explicit setters affect only the one field they override. If you set the positive
|
|
||||||
// prefix, that should not affect the negative prefix. Since it is impossible for the user of this class
|
|
||||||
// to know whether the origin for a string was the override or the pattern, we have to say that we always
|
|
||||||
// have a negative subpattern and perform all resolution logic here.
|
|
||||||
|
|
||||||
// Convenience: Extract the properties into local variables.
|
|
||||||
// Variables are named with three chars: [p/n][p/s][o/p]
|
|
||||||
// [p/n] => p for positive, n for negative
|
|
||||||
// [p/s] => p for prefix, s for suffix
|
|
||||||
// [o/p] => o for escaped custom override string, p for pattern string
|
|
||||||
String ppo = AffixUtils.escape(properties.getPositivePrefix());
|
|
||||||
String pso = AffixUtils.escape(properties.getPositiveSuffix());
|
|
||||||
String npo = AffixUtils.escape(properties.getNegativePrefix());
|
|
||||||
String nso = AffixUtils.escape(properties.getNegativeSuffix());
|
|
||||||
String ppp = properties.getPositivePrefixPattern();
|
|
||||||
String psp = properties.getPositiveSuffixPattern();
|
|
||||||
String npp = properties.getNegativePrefixPattern();
|
|
||||||
String nsp = properties.getNegativeSuffixPattern();
|
|
||||||
|
|
||||||
if (ppo != null) {
|
|
||||||
posPrefix = ppo;
|
|
||||||
} else if (ppp != null) {
|
|
||||||
posPrefix = ppp;
|
|
||||||
} else {
|
|
||||||
// UTS 35: Default positive prefix is empty string.
|
|
||||||
posPrefix = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pso != null) {
|
|
||||||
posSuffix = pso;
|
|
||||||
} else if (psp != null) {
|
|
||||||
posSuffix = psp;
|
|
||||||
} else {
|
|
||||||
// UTS 35: Default positive suffix is empty string.
|
|
||||||
posSuffix = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (npo != null) {
|
|
||||||
negPrefix = npo;
|
|
||||||
} else if (npp != null) {
|
|
||||||
negPrefix = npp;
|
|
||||||
} else {
|
|
||||||
// UTS 35: Default negative prefix is "-" with positive prefix.
|
|
||||||
// Important: We prepend the "-" to the pattern, not the override!
|
|
||||||
negPrefix = ppp == null ? "-" : "-" + ppp;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nso != null) {
|
|
||||||
negSuffix = nso;
|
|
||||||
} else if (nsp != null) {
|
|
||||||
negSuffix = nsp;
|
|
||||||
} else {
|
|
||||||
// UTS 35: Default negative prefix is the positive prefix.
|
|
||||||
negSuffix = psp == null ? "" : psp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public char charAt(int flags, int i) {
|
|
||||||
return getStringForFlags(flags).charAt(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int length(int flags) {
|
|
||||||
return getStringForFlags(flags).length();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getStringForFlags(int flags) {
|
|
||||||
boolean prefix = (flags & Flags.PREFIX) != 0;
|
|
||||||
boolean negative = (flags & Flags.NEGATIVE_SUBPATTERN) != 0;
|
|
||||||
if (prefix && negative) {
|
|
||||||
return negPrefix;
|
|
||||||
} else if (prefix) {
|
|
||||||
return posPrefix;
|
|
||||||
} else if (negative) {
|
|
||||||
return negSuffix;
|
|
||||||
} else {
|
|
||||||
return posSuffix;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean positiveHasPlusSign() {
|
|
||||||
return AffixUtils.containsType(posPrefix, AffixUtils.TYPE_PLUS_SIGN)
|
|
||||||
|| AffixUtils.containsType(posSuffix, AffixUtils.TYPE_PLUS_SIGN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNegativeSubpattern() {
|
|
||||||
// See comments in the constructor for more information on why this is always true.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean negativeHasMinusSign() {
|
|
||||||
return AffixUtils.containsType(negPrefix, AffixUtils.TYPE_MINUS_SIGN)
|
|
||||||
|| AffixUtils.containsType(negSuffix, AffixUtils.TYPE_MINUS_SIGN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasCurrencySign() {
|
|
||||||
return AffixUtils.hasCurrencySymbols(posPrefix) || AffixUtils.hasCurrencySymbols(posSuffix)
|
|
||||||
|| AffixUtils.hasCurrencySymbols(negPrefix) || AffixUtils.hasCurrencySymbols(negSuffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsSymbolType(int type) {
|
|
||||||
return AffixUtils.containsType(posPrefix, type) || AffixUtils.containsType(posSuffix, type)
|
|
||||||
|| AffixUtils.containsType(negPrefix, type) || AffixUtils.containsType(negSuffix, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class CurrencyPluralInfoAffixProvider implements AffixPatternProvider {
|
|
||||||
private final AffixPatternProvider[] affixesByPlural;
|
|
||||||
|
|
||||||
public CurrencyPluralInfoAffixProvider(CurrencyPluralInfo cpi) {
|
|
||||||
affixesByPlural = new ParsedPatternInfo[StandardPlural.COUNT];
|
|
||||||
for (StandardPlural plural : StandardPlural.VALUES) {
|
|
||||||
affixesByPlural[plural.ordinal()] = PatternStringParser
|
|
||||||
.parseToPatternInfo(cpi.getCurrencyPluralPattern(plural.getKeyword()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public char charAt(int flags, int i) {
|
|
||||||
int pluralOrdinal = (flags & Flags.PLURAL_MASK);
|
|
||||||
return affixesByPlural[pluralOrdinal].charAt(flags, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int length(int flags) {
|
|
||||||
int pluralOrdinal = (flags & Flags.PLURAL_MASK);
|
|
||||||
return affixesByPlural[pluralOrdinal].length(flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean positiveHasPlusSign() {
|
|
||||||
return affixesByPlural[StandardPlural.OTHER.ordinal()].positiveHasPlusSign();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNegativeSubpattern() {
|
|
||||||
return affixesByPlural[StandardPlural.OTHER.ordinal()].hasNegativeSubpattern();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean negativeHasMinusSign() {
|
|
||||||
return affixesByPlural[StandardPlural.OTHER.ordinal()].negativeHasMinusSign();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasCurrencySign() {
|
|
||||||
return affixesByPlural[StandardPlural.OTHER.ordinal()].hasCurrencySign();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsSymbolType(int type) {
|
|
||||||
return affixesByPlural[StandardPlural.OTHER.ordinal()].containsSymbolType(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -441,11 +441,11 @@ en_US 1 123,456 123456
|
|||||||
en_US 0 123,456 123
|
en_US 0 123,456 123
|
||||||
en_US 1 123.456 123.456
|
en_US 1 123.456 123.456
|
||||||
en_US 0 123.456 123.456
|
en_US 0 123.456 123.456
|
||||||
fr_FR 1 123,456 123.456
|
it_IT 1 123,456 123.456
|
||||||
fr_FR 0 123,456 123.456
|
it_IT 0 123,456 123.456
|
||||||
// JDK returns 123 here; not sure why.
|
// JDK returns 123 here; not sure why.
|
||||||
fr_FR 1 123.456 123456 K
|
it_IT 1 123.456 123456 K
|
||||||
fr_FR 0 123.456 123
|
it_IT 0 123.456 123
|
||||||
|
|
||||||
test no grouping in pattern with parsing
|
test no grouping in pattern with parsing
|
||||||
set pattern 0
|
set pattern 0
|
||||||
@ -736,7 +736,7 @@ parse output breaks
|
|||||||
(5,347.25) -5347.25
|
(5,347.25) -5347.25
|
||||||
// J requires prefix and suffix for lenient parsing, but C doesn't
|
// J requires prefix and suffix for lenient parsing, but C doesn't
|
||||||
5,347.25 5347.25 JK
|
5,347.25 5347.25 JK
|
||||||
(5,347.25 -5347.25 J
|
(5,347.25 -5347.25 JP
|
||||||
// S is successful at parsing this as -5347.25 in lenient mode
|
// S is successful at parsing this as -5347.25 in lenient mode
|
||||||
-5,347.25 -5347.25 CJK
|
-5,347.25 -5347.25 CJK
|
||||||
+3.52E4 35200
|
+3.52E4 35200
|
||||||
@ -750,7 +750,8 @@ parse output breaks
|
|||||||
(34,,25 E-1) -342.5 CJK
|
(34,,25 E-1) -342.5 CJK
|
||||||
// Spaces are not allowed after exponent symbol
|
// Spaces are not allowed after exponent symbol
|
||||||
// C parses up to the E but J bails
|
// C parses up to the E but J bails
|
||||||
(34 25E -1) -3425 JK
|
// P does not make the number negative
|
||||||
|
(34 25E -1) -3425 JKP
|
||||||
+3.52EE4 3.52
|
+3.52EE4 3.52
|
||||||
+1,234,567.8901 1234567.8901
|
+1,234,567.8901 1234567.8901
|
||||||
+1,23,4567.8901 1234567.8901
|
+1,23,4567.8901 1234567.8901
|
||||||
@ -775,18 +776,19 @@ parse output breaks
|
|||||||
( 19 45 ) -1945 JK
|
( 19 45 ) -1945 JK
|
||||||
(,,19,45) -1945
|
(,,19,45) -1945
|
||||||
// C parses to the space, but J bails
|
// C parses to the space, but J bails
|
||||||
(,,19 45) -19 J
|
// P makes the number positive
|
||||||
|
(,,19 45) -19 JP
|
||||||
// J bails b/c comma different separator than space. C doesn't treat leading spaces
|
// J bails b/c comma different separator than space. C doesn't treat leading spaces
|
||||||
// as a separator.
|
// as a separator.
|
||||||
( 19,45) -1945 JK
|
( 19,45) -1945 JKP
|
||||||
// J bails. Doesn't allow trailing separators when there is prefix and suffix.
|
// J bails. Doesn't allow trailing separators when there is prefix and suffix.
|
||||||
(,,19,45,) -1945 J
|
(,,19,45,) -1945 J
|
||||||
// J bails on next 4 because J doesn't allow letters inside prefix and suffix.
|
// J bails on next 4 because J doesn't allow letters inside prefix and suffix.
|
||||||
// C will parse up to the letter.
|
// C will parse up to the letter.
|
||||||
(,,19,45,d1) -1945 J
|
(,,19,45,d1) -1945 JP
|
||||||
(,,19,45d1) -1945 J
|
(,,19,45d1) -1945 JP
|
||||||
( 19 45 d1) -1945 JK
|
( 19 45 d1) -1945 JKP
|
||||||
( 19 45d1) -1945 JK
|
( 19 45d1) -1945 JKP
|
||||||
// J does allow trailing separator before a decimal point
|
// J does allow trailing separator before a decimal point
|
||||||
(19,45,.25) -1945.25
|
(19,45,.25) -1945.25
|
||||||
// 2nd decimal points are ignored
|
// 2nd decimal points are ignored
|
||||||
@ -829,7 +831,7 @@ parse output breaks
|
|||||||
(65347.25) -65347.25
|
(65347.25) -65347.25
|
||||||
(65,347.25) -65347.25
|
(65,347.25) -65347.25
|
||||||
// JDK does allow separators in the wrong place and parses as -5347.25
|
// JDK does allow separators in the wrong place and parses as -5347.25
|
||||||
(53,47.25) fail K
|
(53,47.25) fail KP
|
||||||
// strict requires prefix or suffix, except in C
|
// strict requires prefix or suffix, except in C
|
||||||
65,347.25 fail
|
65,347.25 fail
|
||||||
+3.52E4 35200
|
+3.52E4 35200
|
||||||
|
@ -7,6 +7,7 @@ import java.math.RoundingMode;
|
|||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.ParsePosition;
|
import java.text.ParsePosition;
|
||||||
|
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.ibm.icu.dev.test.TestUtil;
|
import com.ibm.icu.dev.test.TestUtil;
|
||||||
@ -16,6 +17,7 @@ import com.ibm.icu.impl.number.Parse;
|
|||||||
import com.ibm.icu.impl.number.Parse.ParseMode;
|
import com.ibm.icu.impl.number.Parse.ParseMode;
|
||||||
import com.ibm.icu.impl.number.PatternStringParser;
|
import com.ibm.icu.impl.number.PatternStringParser;
|
||||||
import com.ibm.icu.impl.number.PatternStringUtils;
|
import com.ibm.icu.impl.number.PatternStringUtils;
|
||||||
|
import com.ibm.icu.impl.number.parse.NumberParserImpl;
|
||||||
import com.ibm.icu.number.LocalizedNumberFormatter;
|
import com.ibm.icu.number.LocalizedNumberFormatter;
|
||||||
import com.ibm.icu.number.NumberFormatter;
|
import com.ibm.icu.number.NumberFormatter;
|
||||||
import com.ibm.icu.text.DecimalFormat;
|
import com.ibm.icu.text.DecimalFormat;
|
||||||
@ -573,6 +575,100 @@ public class NumberFormatDataDrivenTest {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parsing, but no other features.
|
||||||
|
*/
|
||||||
|
private DataDrivenNumberFormatTestUtility.CodeUnderTest ICU60_Parsing =
|
||||||
|
new DataDrivenNumberFormatTestUtility.CodeUnderTest() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Character Id() {
|
||||||
|
return 'P';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String parse(DataDrivenNumberFormatTestData tuple) {
|
||||||
|
String pattern = (tuple.pattern == null) ? "0" : tuple.pattern;
|
||||||
|
DecimalFormatProperties properties;
|
||||||
|
ParsePosition ppos = new ParsePosition(0);
|
||||||
|
Number 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 (IllegalArgumentException e) {
|
||||||
|
return "parse exception: " + e.getMessage();
|
||||||
|
}
|
||||||
|
if (actual == null && ppos.getIndex() != 0) {
|
||||||
|
throw new AssertionError("Error: value is null but parse position is not zero");
|
||||||
|
}
|
||||||
|
if (ppos.getIndex() == 0) {
|
||||||
|
return "Parse failed; got " + actual + ", but expected " + tuple.output;
|
||||||
|
}
|
||||||
|
if (tuple.output.equals("NaN")) {
|
||||||
|
if (!Double.isNaN(actual.doubleValue())) {
|
||||||
|
return "Expected NaN, but got: " + actual;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} else if (tuple.output.equals("Inf")) {
|
||||||
|
if (!Double.isInfinite(actual.doubleValue()) || Double.compare(actual.doubleValue(), 0.0) < 0) {
|
||||||
|
return "Expected Inf, but got: " + actual;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} else if (tuple.output.equals("-Inf")) {
|
||||||
|
if (!Double.isInfinite(actual.doubleValue()) || Double.compare(actual.doubleValue(), 0.0) > 0) {
|
||||||
|
return "Expected -Inf, but got: " + actual;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} else if (tuple.output.equals("fail")) {
|
||||||
|
return null;
|
||||||
|
} else if (new BigDecimal(tuple.output).compareTo(new BigDecimal(actual.toString())) != 0) {
|
||||||
|
return "Expected: " + tuple.output + ", got: " + actual;
|
||||||
|
} else {
|
||||||
|
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.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;
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All features except formatting.
|
* All features except formatting.
|
||||||
*/
|
*/
|
||||||
@ -738,6 +834,7 @@ public class NumberFormatDataDrivenTest {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore
|
||||||
public void TestDataDrivenICU58() {
|
public void TestDataDrivenICU58() {
|
||||||
// Android can't access DecimalFormat_ICU58 for testing (ticket #13283).
|
// Android can't access DecimalFormat_ICU58 for testing (ticket #13283).
|
||||||
if (TestUtil.getJavaVendor() == TestUtil.JavaVendor.Android) return;
|
if (TestUtil.getJavaVendor() == TestUtil.JavaVendor.Android) return;
|
||||||
@ -750,6 +847,7 @@ public class NumberFormatDataDrivenTest {
|
|||||||
// something may or may not work. However the test data assumes a specific
|
// something may or may not work. However the test data assumes a specific
|
||||||
// Java runtime version. We should probably disable this test case - #13372
|
// Java runtime version. We should probably disable this test case - #13372
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore
|
||||||
public void TestDataDrivenJDK() {
|
public void TestDataDrivenJDK() {
|
||||||
// Android implements java.text.DecimalFormat with ICU4J (ticket #13322).
|
// Android implements java.text.DecimalFormat with ICU4J (ticket #13322).
|
||||||
// Oracle/OpenJDK 9's behavior is not exactly same with Oracle/OpenJDK 8.
|
// Oracle/OpenJDK 9's behavior is not exactly same with Oracle/OpenJDK 8.
|
||||||
@ -764,12 +862,20 @@ public class NumberFormatDataDrivenTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore
|
||||||
public void TestDataDrivenICULatest_Format() {
|
public void TestDataDrivenICULatest_Format() {
|
||||||
DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures(
|
DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures(
|
||||||
"numberformattestspecification.txt", ICU60);
|
"numberformattestspecification.txt", ICU60);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
public void TestDataDrivenICULatest_Parsing() {
|
||||||
|
DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures(
|
||||||
|
"numberformattestspecification.txt", ICU60_Parsing);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore
|
||||||
public void TestDataDrivenICULatest_Other() {
|
public void TestDataDrivenICULatest_Other() {
|
||||||
DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures(
|
DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures(
|
||||||
"numberformattestspecification.txt", ICU60_Other);
|
"numberformattestspecification.txt", ICU60_Other);
|
||||||
|
@ -22,6 +22,7 @@ public class NumberParserTest {
|
|||||||
// a) Flags:
|
// a) Flags:
|
||||||
// --- Bit 0x01 => Test greedy implementation
|
// --- Bit 0x01 => Test greedy implementation
|
||||||
// --- Bit 0x02 => Test slow implementation
|
// --- Bit 0x02 => Test slow implementation
|
||||||
|
// --- Bit 0x04 => Test strict grouping separators
|
||||||
// b) Input string
|
// b) Input string
|
||||||
// c) Pattern
|
// c) Pattern
|
||||||
// d) Expected chars consumed
|
// d) Expected chars consumed
|
||||||
@ -34,13 +35,13 @@ public class NumberParserTest {
|
|||||||
{ 3, "𝟱𝟭𝟰𝟮𝟯x", "0", 10, 51423. },
|
{ 3, "𝟱𝟭𝟰𝟮𝟯x", "0", 10, 51423. },
|
||||||
{ 3, " 𝟱𝟭𝟰𝟮𝟯", "0", 11, 51423. },
|
{ 3, " 𝟱𝟭𝟰𝟮𝟯", "0", 11, 51423. },
|
||||||
{ 3, "𝟱𝟭𝟰𝟮𝟯 ", "0", 10, 51423. },
|
{ 3, "𝟱𝟭𝟰𝟮𝟯 ", "0", 10, 51423. },
|
||||||
{ 3, "𝟱𝟭,𝟰𝟮𝟯", "0", 11, 51423. },
|
{ 7, "𝟱𝟭,𝟰𝟮𝟯", "0", 11, 51423. },
|
||||||
{ 3, "𝟳𝟴,𝟵𝟱𝟭,𝟰𝟮𝟯", "0", 18, 78951423. },
|
{ 7, "𝟳𝟴,𝟵𝟱𝟭,𝟰𝟮𝟯", "0", 18, 78951423. },
|
||||||
{ 3, "𝟳𝟴,𝟵𝟱𝟭.𝟰𝟮𝟯", "0", 18, 78951.423 },
|
{ 7, "𝟳𝟴,𝟵𝟱𝟭.𝟰𝟮𝟯", "0", 18, 78951.423 },
|
||||||
{ 3, "𝟳𝟴,𝟬𝟬𝟬", "0", 11, 78000. },
|
{ 7, "𝟳𝟴,𝟬𝟬𝟬", "0", 11, 78000. },
|
||||||
{ 3, "𝟳𝟴,𝟬𝟬𝟬.𝟬𝟬𝟬", "0", 18, 78000. },
|
{ 7, "𝟳𝟴,𝟬𝟬𝟬.𝟬𝟬𝟬", "0", 18, 78000. },
|
||||||
{ 3, "𝟳𝟴,𝟬𝟬𝟬.𝟬𝟮𝟯", "0", 18, 78000.023 },
|
{ 7, "𝟳𝟴,𝟬𝟬𝟬.𝟬𝟮𝟯", "0", 18, 78000.023 },
|
||||||
{ 3, "𝟳𝟴.𝟬𝟬𝟬.𝟬𝟮𝟯", "0", 11, 78. },
|
{ 7, "𝟳𝟴.𝟬𝟬𝟬.𝟬𝟮𝟯", "0", 11, 78. },
|
||||||
{ 3, "-𝟱𝟭𝟰𝟮𝟯", "0", 11, -51423. },
|
{ 3, "-𝟱𝟭𝟰𝟮𝟯", "0", 11, -51423. },
|
||||||
{ 3, "-𝟱𝟭𝟰𝟮𝟯-", "0", 11, -51423. },
|
{ 3, "-𝟱𝟭𝟰𝟮𝟯-", "0", 11, -51423. },
|
||||||
{ 3, "a51423US dollars", "a0¤¤¤", 16, 51423. },
|
{ 3, "a51423US dollars", "a0¤¤¤", 16, 51423. },
|
||||||
@ -51,6 +52,7 @@ public class NumberParserTest {
|
|||||||
{ 1, "a40b", "a0'0b'", 3, 40. }, // greedy code path thinks "40" is the number
|
{ 1, "a40b", "a0'0b'", 3, 40. }, // greedy code path thinks "40" is the number
|
||||||
{ 2, "a40b", "a0'0b'", 4, 4. }, // slow code path find the suffix "0b"
|
{ 2, "a40b", "a0'0b'", 4, 4. }, // slow code path find the suffix "0b"
|
||||||
{ 3, "𝟱.𝟭𝟰𝟮E𝟯", "0", 12, 5142. },
|
{ 3, "𝟱.𝟭𝟰𝟮E𝟯", "0", 12, 5142. },
|
||||||
|
{ 3, "𝟱.𝟭𝟰𝟮E-𝟯", "0", 13, 0.005142 },
|
||||||
{ 3, "5,142.50 Canadian dollars", "0", 25, 5142.5 },
|
{ 3, "5,142.50 Canadian dollars", "0", 25, 5142.5 },
|
||||||
{ 3, "0", "0", 1, 0.0 } };
|
{ 3, "0", "0", 1, 0.0 } };
|
||||||
|
|
||||||
@ -60,7 +62,7 @@ public class NumberParserTest {
|
|||||||
String pattern = (String) cas[2];
|
String pattern = (String) cas[2];
|
||||||
int expectedCharsConsumed = (Integer) cas[3];
|
int expectedCharsConsumed = (Integer) cas[3];
|
||||||
double resultDouble = (Double) cas[4];
|
double resultDouble = (Double) cas[4];
|
||||||
NumberParserImpl parser = NumberParserImpl.createParserFromPattern(pattern);
|
NumberParserImpl parser = NumberParserImpl.createParserFromPattern(pattern, false);
|
||||||
String message = "Input <" + input + "> Parser " + parser;
|
String message = "Input <" + input + "> Parser " + parser;
|
||||||
|
|
||||||
if (0 != (flags & 0x01)) {
|
if (0 != (flags & 0x01)) {
|
||||||
@ -80,6 +82,16 @@ public class NumberParserTest {
|
|||||||
assertEquals(message, resultDouble, resultObject.getDouble(), 0.0);
|
assertEquals(message, resultDouble, resultObject.getDouble(), 0.0);
|
||||||
assertEquals(message, expectedCharsConsumed, resultObject.charsConsumed);
|
assertEquals(message, expectedCharsConsumed, resultObject.charsConsumed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (0 != (flags & 0x04)) {
|
||||||
|
// Test with strict separators
|
||||||
|
parser = NumberParserImpl.createParserFromPattern(pattern, true);
|
||||||
|
ParsedNumber resultObject = new ParsedNumber();
|
||||||
|
parser.parse(input, true, resultObject);
|
||||||
|
assertNotNull(message, resultObject.quantity);
|
||||||
|
assertEquals(message, resultDouble, resultObject.getDouble(), 0.0);
|
||||||
|
assertEquals(message, expectedCharsConsumed, resultObject.charsConsumed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user