ICU-8610 Full support for skeletons in ICU4J. Needs a few more tests.

X-SVN-Rev: 41038
This commit is contained in:
Shane Carr 2018-03-01 09:24:37 +00:00
parent 59e4fc5172
commit c5e86f87c8
3 changed files with 580 additions and 205 deletions

View File

@ -12,7 +12,11 @@ import java.util.concurrent.ConcurrentHashMap;
import com.ibm.icu.impl.PatternProps; import com.ibm.icu.impl.PatternProps;
import com.ibm.icu.impl.StringSegment; import com.ibm.icu.impl.StringSegment;
import com.ibm.icu.impl.number.MacroProps; import com.ibm.icu.impl.number.MacroProps;
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
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.NumberingSystem;
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;
import com.ibm.icu.util.MeasureUnit; import com.ibm.icu.util.MeasureUnit;
@ -25,7 +29,25 @@ import com.ibm.icu.util.NoUnit;
class NumberSkeletonImpl { class NumberSkeletonImpl {
static enum StemType { static enum StemType {
OTHER, ROUNDER, FRACTION_ROUNDER, MAYBE_INCREMENT_ROUNDER, CURRENCY_ROUNDER, MEASURE_UNIT, UNIT_WIDTH OTHER,
COMPACT_NOTATION,
SCIENTIFIC_NOTATION,
SIMPLE_NOTATION,
NO_UNIT,
CURRENCY,
MEASURE_UNIT,
PER_MEASURE_UNIT,
ROUNDER,
FRACTION_ROUNDER,
MAYBE_INCREMENT_ROUNDER,
CURRENCY_ROUNDER,
GROUPING,
INTEGER_WIDTH,
LATIN,
NUMBERING_SYSTEM,
UNIT_WIDTH,
SIGN_DISPLAY,
DECIMAL_DISPLAY
} }
static class SkeletonDataStructure { static class SkeletonDataStructure {
@ -61,6 +83,16 @@ class NumberSkeletonImpl {
static final SkeletonDataStructure skeletonData = new SkeletonDataStructure(); static final SkeletonDataStructure skeletonData = new SkeletonDataStructure();
static { static {
skeletonData.put(StemType.COMPACT_NOTATION, "compact-short", Notation.compactShort());
skeletonData.put(StemType.COMPACT_NOTATION, "compact-long", Notation.compactLong());
skeletonData.put(StemType.SCIENTIFIC_NOTATION, "scientific", Notation.scientific());
skeletonData.put(StemType.SCIENTIFIC_NOTATION, "engineering", Notation.engineering());
skeletonData.put(StemType.SIMPLE_NOTATION, "simple-notation", Notation.simple());
skeletonData.put(StemType.NO_UNIT, "base-unit", NoUnit.BASE);
skeletonData.put(StemType.NO_UNIT, "percent", NoUnit.PERCENT);
skeletonData.put(StemType.NO_UNIT, "permille", NoUnit.PERMILLE);
skeletonData.put(StemType.ROUNDER, "round-integer", Rounder.integer()); skeletonData.put(StemType.ROUNDER, "round-integer", Rounder.integer());
skeletonData.put(StemType.ROUNDER, "round-unlimited", Rounder.unlimited()); skeletonData.put(StemType.ROUNDER, "round-unlimited", Rounder.unlimited());
skeletonData.put(StemType.ROUNDER, skeletonData.put(StemType.ROUNDER,
@ -68,11 +100,32 @@ class NumberSkeletonImpl {
Rounder.currency(CurrencyUsage.STANDARD)); Rounder.currency(CurrencyUsage.STANDARD));
skeletonData.put(StemType.ROUNDER, "round-currency-cash", Rounder.currency(CurrencyUsage.CASH)); skeletonData.put(StemType.ROUNDER, "round-currency-cash", Rounder.currency(CurrencyUsage.CASH));
skeletonData.put(StemType.GROUPING, "group-off", GroupingStrategy.OFF);
skeletonData.put(StemType.GROUPING, "group-min2", GroupingStrategy.MIN2);
skeletonData.put(StemType.GROUPING, "group-auto", GroupingStrategy.AUTO);
skeletonData.put(StemType.GROUPING, "group-on-aligned", GroupingStrategy.ON_ALIGNED);
skeletonData.put(StemType.GROUPING, "group-thousands", GroupingStrategy.THOUSANDS);
skeletonData.put(StemType.LATIN, "latin", NumberingSystem.LATIN);
skeletonData.put(StemType.UNIT_WIDTH, "unit-width-narrow", UnitWidth.NARROW); skeletonData.put(StemType.UNIT_WIDTH, "unit-width-narrow", UnitWidth.NARROW);
skeletonData.put(StemType.UNIT_WIDTH, "unit-width-short", UnitWidth.SHORT); skeletonData.put(StemType.UNIT_WIDTH, "unit-width-short", UnitWidth.SHORT);
skeletonData.put(StemType.UNIT_WIDTH, "unit-width-full-name", UnitWidth.FULL_NAME); skeletonData.put(StemType.UNIT_WIDTH, "unit-width-full-name", UnitWidth.FULL_NAME);
skeletonData.put(StemType.UNIT_WIDTH, "unit-width-iso-code", UnitWidth.ISO_CODE); skeletonData.put(StemType.UNIT_WIDTH, "unit-width-iso-code", UnitWidth.ISO_CODE);
skeletonData.put(StemType.UNIT_WIDTH, "unit-width-hidden", UnitWidth.HIDDEN); skeletonData.put(StemType.UNIT_WIDTH, "unit-width-hidden", UnitWidth.HIDDEN);
skeletonData.put(StemType.SIGN_DISPLAY, "sign-auto", SignDisplay.AUTO);
skeletonData.put(StemType.SIGN_DISPLAY, "sign-always", SignDisplay.ALWAYS);
skeletonData.put(StemType.SIGN_DISPLAY, "sign-never", SignDisplay.NEVER);
skeletonData.put(StemType.SIGN_DISPLAY, "sign-accounting", SignDisplay.ACCOUNTING);
skeletonData.put(StemType.SIGN_DISPLAY, "sign-accounting-always", SignDisplay.ACCOUNTING_ALWAYS);
skeletonData.put(StemType.SIGN_DISPLAY, "sign-except-zero", SignDisplay.EXCEPT_ZERO);
skeletonData.put(StemType.SIGN_DISPLAY,
"sign-accounting-except-zero",
SignDisplay.ACCOUNTING_EXCEPT_ZERO);
skeletonData.put(StemType.DECIMAL_DISPLAY, "decimal-auto", DecimalSeparatorDisplay.AUTO);
skeletonData.put(StemType.DECIMAL_DISPLAY, "decimal-always", DecimalSeparatorDisplay.ALWAYS);
} }
private static final Map<String, UnlocalizedNumberFormatter> cache = new ConcurrentHashMap<String, UnlocalizedNumberFormatter>(); private static final Map<String, UnlocalizedNumberFormatter> cache = new ConcurrentHashMap<String, UnlocalizedNumberFormatter>();
@ -183,6 +236,10 @@ class NumberSkeletonImpl {
switch (stem) { switch (stem) {
case MAYBE_INCREMENT_ROUNDER: case MAYBE_INCREMENT_ROUNDER:
case MEASURE_UNIT: case MEASURE_UNIT:
case PER_MEASURE_UNIT:
case CURRENCY:
case INTEGER_WIDTH:
case NUMBERING_SYSTEM:
throw new SkeletonSyntaxException("Stem requires an option", segment); throw new SkeletonSyntaxException("Stem requires an option", segment);
default: default:
break; break;
@ -200,14 +257,40 @@ class NumberSkeletonImpl {
if (stem != null) { if (stem != null) {
Object value = skeletonData.stemToValue(content); Object value = skeletonData.stemToValue(content);
switch (stem) { switch (stem) {
case COMPACT_NOTATION:
case SCIENTIFIC_NOTATION:
case SIMPLE_NOTATION:
checkNull(macros.notation, content);
macros.notation = (Notation) value;
break;
case NO_UNIT:
checkNull(macros.unit, content);
macros.unit = (NoUnit) value;
break;
case ROUNDER: case ROUNDER:
checkNull(macros.rounder, content); checkNull(macros.rounder, content);
macros.rounder = (Rounder) value; macros.rounder = (Rounder) value;
break; break;
case GROUPING:
checkNull(macros.grouping, content);
macros.grouping = value;
break;
case LATIN:
checkNull(macros.symbols, content);
macros.symbols = value;
break;
case UNIT_WIDTH: case UNIT_WIDTH:
checkNull(macros.unitWidth, content); checkNull(macros.unitWidth, content);
macros.unitWidth = (UnitWidth) value; macros.unitWidth = (UnitWidth) value;
break; break;
case SIGN_DISPLAY:
checkNull(macros.sign, content);
macros.sign = (SignDisplay) value;
break;
case DECIMAL_DISPLAY:
checkNull(macros.decimal, content);
macros.decimal = (DecimalSeparatorDisplay) value;
break;
default: default:
assert false; assert false;
} }
@ -216,19 +299,35 @@ class NumberSkeletonImpl {
// Second try: literal stems that require an option // Second try: literal stems that require an option
if (content.equals("round-increment")) { if (content.equals("round-increment")) {
checkNull(macros.rounder, content);
return StemType.MAYBE_INCREMENT_ROUNDER; return StemType.MAYBE_INCREMENT_ROUNDER;
} else if (content.equals("measure-unit")) { } else if (content.equals("measure-unit")) {
checkNull(macros.unit, content);
return StemType.MEASURE_UNIT; return StemType.MEASURE_UNIT;
} else if (content.equals("per-measure-unit")) {
checkNull(macros.perUnit, content);
return StemType.PER_MEASURE_UNIT;
} else if (content.equals("currency")) {
checkNull(macros.unit, content);
return StemType.CURRENCY;
} else if (content.equals("integer-width")) {
checkNull(macros.integerWidth, content);
return StemType.INTEGER_WIDTH;
} else if (content.equals("numbering-system")) {
checkNull(macros.symbols, content);
return StemType.NUMBERING_SYSTEM;
} }
// Second try: stem "blueprint" syntax // Third try: stem "blueprint" syntax
switch (content.charAt(0)) { switch (content.charAt(0)) {
case '.': case '.':
stem = StemType.FRACTION_ROUNDER; stem = StemType.FRACTION_ROUNDER;
checkNull(macros.rounder, content);
parseFractionStem(content, macros); parseFractionStem(content, macros);
break; break;
case '@': case '@':
stem = StemType.ROUNDER; stem = StemType.ROUNDER;
checkNull(macros.rounder, content);
parseDigitsStem(content, macros); parseDigitsStem(content, macros);
break; break;
} }
@ -241,6 +340,43 @@ class NumberSkeletonImpl {
} }
private static StemType parseOption(StemType stem, CharSequence content, MacroProps macros) { private static StemType parseOption(StemType stem, CharSequence content, MacroProps macros) {
///// Required options: /////
switch (stem) {
case CURRENCY:
parseCurrencyOption(content, macros);
return StemType.OTHER;
case MEASURE_UNIT:
parseMeasureUnitOption(content, macros);
return StemType.OTHER;
case PER_MEASURE_UNIT:
parseMeasurePerUnitOption(content, macros);
return StemType.OTHER;
case MAYBE_INCREMENT_ROUNDER:
parseIncrementOption(content, macros);
return StemType.ROUNDER;
case INTEGER_WIDTH:
parseIntegerWidthOption(content, macros);
return StemType.OTHER;
case NUMBERING_SYSTEM:
parseNumberingSystemOption(content, macros);
return StemType.OTHER;
}
///// Non-required options: /////
// Scientific options
switch (stem) {
case SCIENTIFIC_NOTATION:
if (parseExponentWidthOption(content, macros)) {
return StemType.SCIENTIFIC_NOTATION;
}
if (parseExponentSignOption(content, macros)) {
return StemType.SCIENTIFIC_NOTATION;
}
}
// Frac-sig option // Frac-sig option
switch (stem) { switch (stem) {
case FRACTION_ROUNDER: case FRACTION_ROUNDER:
@ -249,51 +385,61 @@ class NumberSkeletonImpl {
} }
} }
// Increment option
switch (stem) {
case MAYBE_INCREMENT_ROUNDER:
// The increment option is required.
parseIncrementOption(content, macros);
return StemType.ROUNDER;
}
// Rounding mode option // Rounding mode option
switch (stem) { switch (stem) {
case ROUNDER: case ROUNDER:
case FRACTION_ROUNDER: case FRACTION_ROUNDER:
case CURRENCY_ROUNDER: case CURRENCY_ROUNDER:
if (parseRoundingModeOption(content, macros)) { if (parseRoundingModeOption(content, macros)) {
break; return StemType.ROUNDER;
} }
} }
// Measure unit option
switch (stem) {
case MEASURE_UNIT:
// The measure unit option is required.
parseMeasureUnitOption(content, macros);
return StemType.OTHER;
}
// Unknown option // Unknown option
throw new SkeletonSyntaxException("Unknown option", content); throw new SkeletonSyntaxException("Unknown option", content);
} }
/////
private static void generateSkeleton(MacroProps macros, StringBuilder sb) { private static void generateSkeleton(MacroProps macros, StringBuilder sb) {
if (macros.rounder != null) { if (macros.notation != null) {
generateRoundingValue(macros, sb); generateNotationValue(macros, sb);
sb.append(' '); sb.append(' ');
} }
if (macros.unit != null) { if (macros.unit != null) {
generateUnitValue(macros, sb); generateUnitValue(macros, sb);
sb.append(' '); sb.append(' ');
} }
if (macros.perUnit != null) {
generatePerUnitValue(macros, sb);
sb.append(' ');
}
if (macros.rounder != null) {
generateRoundingValue(macros, sb);
sb.append(' ');
}
if (macros.grouping != null) {
generateGroupingValue(macros, sb);
sb.append(' ');
}
if (macros.integerWidth != null) {
generateIntegerWidthValue(macros, sb);
sb.append(' ');
}
if (macros.symbols != null) {
generateSymbolsValue(macros, sb);
sb.append(' ');
}
if (macros.unitWidth != null) { if (macros.unitWidth != null) {
generateUnitWidthValue(macros, sb); generateUnitWidthValue(macros, sb);
sb.append(' '); sb.append(' ');
} }
if (macros.sign != null) {
generateSignValue(macros, sb);
sb.append(' ');
}
if (macros.decimal != null) {
generateDecimalValue(macros, sb);
sb.append(' ');
}
// Remove the trailing space // Remove the trailing space
if (sb.length() > 0) { if (sb.length() > 0) {
@ -303,6 +449,90 @@ class NumberSkeletonImpl {
///// /////
private static boolean parseExponentWidthOption(CharSequence content, MacroProps macros) {
if (content.charAt(0) != '+') {
return false;
}
int offset = 1;
int minExp = 0;
for (; offset < content.length(); offset++) {
if (content.charAt(offset) == 'e') {
minExp++;
} else {
break;
}
}
if (offset < content.length()) {
return false;
}
// Use the public APIs to enforce bounds checking
macros.notation = ((ScientificNotation) macros.notation).withMinExponentDigits(minExp);
return true;
}
private static void generateExponentWidthOption(int minInt, int maxInt, StringBuilder sb) {
sb.append('+');
appendMultiple(sb, 'e', minInt);
}
private static boolean parseExponentSignOption(CharSequence content, MacroProps macros) {
Object value = skeletonData.stemToValue(content);
if (value != null && value instanceof SignDisplay) {
macros.notation = ((ScientificNotation) macros.notation)
.withExponentSignDisplay((SignDisplay) value);
return true;
}
return false;
}
private static void generateCurrencyOption(Currency currency, StringBuilder sb) {
sb.append(currency.getCurrencyCode());
}
private static void parseCurrencyOption(CharSequence content, MacroProps macros) {
String currencyCode = content.subSequence(0, content.length()).toString();
try {
macros.unit = Currency.getInstance(currencyCode);
} catch (IllegalArgumentException e) {
throw new SkeletonSyntaxException("Invalid currency", content, e);
}
}
private static void parseMeasureUnitOption(CharSequence content, MacroProps macros) {
// NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric)
// http://unicode.org/reports/tr35/#Validity_Data
int firstHyphen = 0;
while (firstHyphen < content.length() && content.charAt(firstHyphen) != '-') {
firstHyphen++;
}
if (firstHyphen == content.length()) {
throw new SkeletonSyntaxException("Invalid measure unit option", content);
}
String type = content.subSequence(0, firstHyphen).toString();
String subType = content.subSequence(firstHyphen + 1, content.length()).toString();
Set<MeasureUnit> units = MeasureUnit.getAvailable(type);
for (MeasureUnit unit : units) {
if (subType.equals(unit.getSubtype())) {
macros.unit = unit;
return;
}
}
throw new SkeletonSyntaxException("Unknown measure unit", content);
}
private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) {
sb.append(unit.getType() + "-" + unit.getSubtype());
}
private static void parseMeasurePerUnitOption(CharSequence content, MacroProps macros) {
// A little bit of a hack: safe the current unit (numerator), call the main measure unit parsing
// code, put back the numerator unit, and put the new unit into per-unit.
MeasureUnit numerator = macros.unit;
parseMeasureUnitOption(content, macros);
macros.perUnit = macros.unit;
macros.unit = numerator;
}
private static void parseFractionStem(CharSequence content, MacroProps macros) { private static void parseFractionStem(CharSequence content, MacroProps macros) {
assert content.charAt(0) == '.'; assert content.charAt(0) == '.';
int offset = 1; int offset = 1;
@ -412,7 +642,7 @@ class NumberSkeletonImpl {
} }
FractionRounder oldRounder = (FractionRounder) macros.rounder; FractionRounder oldRounder = (FractionRounder) macros.rounder;
// A little bit of a hack: parse the option as a digits stem, and extract the min/max sig from // A little bit of a hack: parse the option as a digits stem, and extract the min/max sig from
// the new Rounder saved into the macros // the new Rounder saved into the macros.
parseDigitsStem(content, macros); parseDigitsStem(content, macros);
Rounder.SignificantRounderImpl intermediate = (Rounder.SignificantRounderImpl) macros.rounder; Rounder.SignificantRounderImpl intermediate = (Rounder.SignificantRounderImpl) macros.rounder;
if (intermediate.maxSig == -1) { if (intermediate.maxSig == -1) {
@ -455,31 +685,134 @@ class NumberSkeletonImpl {
sb.append(mode.toString()); sb.append(mode.toString());
} }
private static void parseMeasureUnitOption(CharSequence content, MacroProps macros) { private static void parseIntegerWidthOption(CharSequence content, MacroProps macros) {
// NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) int offset = 0;
// http://unicode.org/reports/tr35/#Validity_Data int minInt = 0;
int firstHyphen = 0; int maxInt;
while (firstHyphen < content.length() && content.charAt(firstHyphen) != '-') { if (content.charAt(0) == '+') {
firstHyphen++; maxInt = -1;
offset++;
} else {
maxInt = 0;
} }
String type = content.subSequence(0, firstHyphen).toString(); for (; offset < content.length(); offset++) {
String subType = content.subSequence(firstHyphen + 1, content.length()).toString(); if (content.charAt(offset) == '#') {
Set<MeasureUnit> units = MeasureUnit.getAvailable(type); maxInt++;
for (MeasureUnit unit : units) { } else {
if (subType.equals(unit.getSubtype())) { break;
macros.unit = unit;
return;
} }
} }
throw new SkeletonSyntaxException("Unknown unit", content); if (offset < content.length()) {
for (; offset < content.length(); offset++) {
if (content.charAt(offset) == '0') {
minInt++;
} else {
break;
}
}
}
if (maxInt != -1) {
maxInt += minInt;
}
if (offset < content.length()) {
throw new SkeletonSyntaxException("Invalid integer width stem", content);
}
// Use the public APIs to enforce bounds checking
if (maxInt == -1) {
macros.integerWidth = IntegerWidth.zeroFillTo(minInt);
} else {
macros.integerWidth = IntegerWidth.zeroFillTo(minInt).truncateAt(maxInt);
}
} }
private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) { private static void generateIntegerWidthOption(int minInt, int maxInt, StringBuilder sb) {
sb.append(unit.getType() + "-" + unit.getSubtype()); if (maxInt == -1) {
sb.append('+');
} else {
appendMultiple(sb, '#', maxInt - minInt);
}
appendMultiple(sb, '0', minInt);
}
private static void parseNumberingSystemOption(CharSequence content, MacroProps macros) {
String nsName = content.subSequence(0, content.length()).toString();
NumberingSystem ns = NumberingSystem.getInstanceByName(nsName);
if (ns == null) {
throw new SkeletonSyntaxException("Unknown numbering system", content);
}
macros.symbols = ns;
}
private static void generateNumberingSystemOption(NumberingSystem ns, StringBuilder sb) {
sb.append(ns.getName());
} }
///// /////
private static void generateNotationValue(MacroProps macros, StringBuilder sb) {
// Check for literals
String literal = skeletonData.valueToStem(macros.notation);
if (literal != null) {
sb.append(literal);
return;
}
// Generate the stem
if (macros.notation instanceof CompactNotation) {
// Compact notation generated from custom data (not supported in skeleton)
// The other compact notations are literals
} else if (macros.notation instanceof ScientificNotation) {
ScientificNotation impl = (ScientificNotation) macros.notation;
if (impl.engineeringInterval == 3) {
sb.append("engineering");
} else {
sb.append("scientific");
}
if (impl.minExponentDigits > 1) {
sb.append('/');
generateExponentWidthOption(impl.minExponentDigits, -1, sb);
}
if (impl.exponentSignDisplay != SignDisplay.AUTO) {
sb.append('/');
sb.append(skeletonData.valueToStem(impl.exponentSignDisplay));
}
} else {
assert macros.notation instanceof SimpleNotation;
sb.append("notation-simple");
}
}
private static void generateUnitValue(MacroProps macros, StringBuilder sb) {
// Check for literals
String literal = skeletonData.valueToStem(macros.unit);
if (literal != null) {
sb.append(literal);
return;
}
// Generate the stem
if (macros.unit instanceof Currency) {
sb.append("currency/");
generateCurrencyOption((Currency) macros.unit, sb);
} else if (macros.unit instanceof NoUnit) {
// This should be taken care of by the literals.
assert false;
} else {
sb.append("measure-unit/");
generateMeasureUnitOption(macros.unit, sb);
}
}
private static void generatePerUnitValue(MacroProps macros, StringBuilder sb) {
// Per-units are currently expected to be only MeasureUnits.
if (macros.unit instanceof Currency || macros.unit instanceof NoUnit) {
assert false;
} else {
sb.append("per-measure-unit/");
generateMeasureUnitOption(macros.perUnit, sb);
}
}
private static void generateRoundingValue(MacroProps macros, StringBuilder sb) { private static void generateRoundingValue(MacroProps macros, StringBuilder sb) {
// Check for literals // Check for literals
String literal = skeletonData.valueToStem(macros.rounder); String literal = skeletonData.valueToStem(macros.rounder);
@ -527,30 +860,39 @@ class NumberSkeletonImpl {
} }
} }
private static void generateUnitValue(MacroProps macros, StringBuilder sb) { private static void generateGroupingValue(MacroProps macros, StringBuilder sb) {
// Check for literals appendExpectedLiteral(macros.grouping, sb);
String literal = skeletonData.valueToStem(macros.unit); }
if (literal != null) {
sb.append(literal);
return;
}
// Generate the stem private static void generateIntegerWidthValue(MacroProps macros, StringBuilder sb) {
if (macros.unit instanceof Currency) { sb.append("integer-width/");
// TODO generateIntegerWidthOption(macros.integerWidth.minInt, macros.integerWidth.maxInt, sb);
} else if (macros.unit instanceof NoUnit) { }
// TODO
private static void generateSymbolsValue(MacroProps macros, StringBuilder sb) {
if (macros.symbols instanceof NumberingSystem) {
NumberingSystem ns = (NumberingSystem) macros.symbols;
if (ns.getName().equals("latn")) {
sb.append("latin");
} else {
sb.append("numbering-system/");
generateNumberingSystemOption(ns, sb);
}
} else { } else {
sb.append("measure-unit/"); // DecimalFormatSymbols (not supported in skeleton)
generateMeasureUnitOption(macros.unit, sb);
} }
} }
private static void generateUnitWidthValue(MacroProps macros, StringBuilder sb) { private static void generateUnitWidthValue(MacroProps macros, StringBuilder sb) {
// There should be a literal. appendExpectedLiteral(macros.unitWidth, sb);
String literal = skeletonData.valueToStem(macros.unitWidth); }
assert literal != null;
sb.append(literal); private static void generateSignValue(MacroProps macros, StringBuilder sb) {
appendExpectedLiteral(macros.sign, sb);
}
private static void generateDecimalValue(MacroProps macros, StringBuilder sb) {
appendExpectedLiteral(macros.decimal, sb);
} }
///// /////
@ -566,4 +908,10 @@ class NumberSkeletonImpl {
sb.appendCodePoint(cp); sb.appendCodePoint(cp);
} }
} }
private static void appendExpectedLiteral(Object value, StringBuilder sb) {
String literal = skeletonData.valueToStem(value);
assert literal != null;
sb.append(literal);
}
} }

View File

@ -38,7 +38,11 @@ public class NumberSkeletonTest {
"@#+", "@#+",
"round-increment/xxx", "round-increment/xxx",
"round-increment/0.1.2", "round-increment/0.1.2",
}; "currency/dummy",
"measure-unit/foo",
"integer-width/xxx",
"integer-width/0+",
"integer-width/+0#", };
for (String cas : cases) { for (String cas : cases) {
try { try {
@ -51,19 +55,33 @@ public class NumberSkeletonTest {
} }
@Test @Test
public void stemsRequiringOption() { public void unknownTokens() {
String[] cases = { String[] cases = { "measure-unit/foo-bar", "numbering-system/dummy" };
"round-increment",
"round-increment/",
"round-increment scientific",
};
for (String cas : cases) { for (String cas : cases) {
try { try {
NumberFormatter.fromSkeleton(cas); NumberFormatter.fromSkeleton(cas);
fail(); fail();
} catch (SkeletonSyntaxException expected) { } catch (SkeletonSyntaxException expected) {
assertTrue(expected.getMessage(), expected.getMessage().contains("requires an option")); assertTrue(expected.getMessage(), expected.getMessage().contains("Unknown"));
}
}
}
@Test
public void stemsRequiringOption() {
String[] stems = { "round-increment", "currency", "measure-unit", "integer-width", };
String[] suffixes = { "", "/", " scientific", "/ scientific" };
for (String stem : stems) {
for (String suffix : suffixes) {
try {
NumberFormatter.fromSkeleton(stem + suffix);
fail();
} catch (SkeletonSyntaxException expected) {
assertTrue(expected.getMessage(),
expected.getMessage().contains("requires an option"));
}
} }
} }
} }