case STEM_CURRENCY:
CHECK_NULL(seen, unit, status);
+ CHECK_NULL(seen, perUnit, status);
return STATE_CURRENCY_UNIT;
case STEM_INTEGER_WIDTH:
}
bool GeneratorHelpers::unit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
- if (utils::unitIsCurrency(macros.unit)) {
+ MeasureUnit unit = macros.unit;
+ if (!utils::unitIsBaseUnit(macros.perUnit)) {
+ if (utils::unitIsCurrency(macros.unit) || utils::unitIsCurrency(macros.perUnit)) {
+ status = U_UNSUPPORTED_ERROR;
+ return false;
+ }
+ unit = unit.product(macros.perUnit.reciprocal(status), status);
+ }
+
+ if (utils::unitIsCurrency(unit)) {
sb.append(u"currency/", -1);
- CurrencyUnit currency(macros.unit, status);
+ CurrencyUnit currency(unit, status);
if (U_FAILURE(status)) {
return false;
}
blueprint_helpers::generateCurrencyOption(currency, sb, status);
return true;
- } else if (utils::unitIsBaseUnit(macros.unit)) {
+ } else if (utils::unitIsBaseUnit(unit)) {
// Default value is not shown in normalized form
return false;
- } else if (utils::unitIsPercent(macros.unit)) {
+ } else if (utils::unitIsPercent(unit)) {
sb.append(u"percent", -1);
return true;
- } else if (utils::unitIsPermille(macros.unit)) {
+ } else if (utils::unitIsPermille(unit)) {
sb.append(u"permille", -1);
return true;
} else {
- MeasureUnit unit = macros.unit;
- if (utils::unitIsCurrency(macros.perUnit)) {
- status = U_UNSUPPORTED_ERROR;
- return false;
- }
- if (!utils::unitIsBaseUnit(macros.perUnit)) {
- unit = unit.product(macros.perUnit.reciprocal(status), status);
- }
sb.append(u"unit/", -1);
sb.append(unit.getIdentifier());
return true;
void flexibleSeparators();
void wildcardCharacters();
void perUnitInArabic();
+ void perUnitToSkeleton();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
u"2.4 m/s\u00B2");
}
+// TODO: merge these tests into numbertest_skeletons.cpp instead of here:
void NumberFormatterApiTest::unitSkeletons() {
const struct TestCase {
const char *msg;
u"unit/meter-per-hectosecond", //
u"unit/meter-per-hectosecond"},
+ {"percent compound skeletons handled correctly", //
+ u"unit/percent-per-meter", //
+ u"unit/percent-per-meter"},
+
+ {"permille compound skeletons handled correctly", //
+ u"measure-unit/concentr-permille per-measure-unit/length-meter", //
+ u"unit/permille-per-meter"},
+
+ {"percent simple unit is not actually considered a unit", //
+ u"unit/percent", //
+ u"percent"},
+
+ {"permille simple unit is not actually considered a unit", //
+ u"measure-unit/concentr-permille", //
+ u"permille"},
+
// // TODO: binary prefixes not supported yet!
// {"Round-trip example from icu-units#35", //
// u"unit/kibijoule-per-furlong", //
u"measure-unit/meter per-measure-unit/hectosecond", //
U_NUMBER_SKELETON_SYNTAX_ERROR, //
U_ZERO_ERROR},
+
+ {"\"currency/EUR measure-unit/length-meter\" fails, conflicting skeleton.",
+ u"currency/EUR measure-unit/length-meter", //
+ U_NUMBER_SKELETON_SYNTAX_ERROR, //
+ U_ZERO_ERROR},
+
+ {"\"measure-unit/length-meter currency/EUR\" fails, conflicting skeleton.",
+ u"measure-unit/length-meter currency/EUR", //
+ U_NUMBER_SKELETON_SYNTAX_ERROR, //
+ U_ZERO_ERROR},
+
+ {"\"currency/EUR per-measure-unit/meter\" fails, conflicting skeleton.",
+ u"currency/EUR per-measure-unit/length-meter", //
+ U_NUMBER_SKELETON_SYNTAX_ERROR, //
+ U_ZERO_ERROR},
};
for (auto &cas : failCases) {
IcuTestErrorCode status(*this, cas.msg);
auto nf = NumberFormatter::forSkeleton(cas.inputSkeleton, status);
if (status.expectErrorAndReset(cas.expectedForSkelStatus, cas.msg)) {
- continue;
+ continue;
}
nf.toSkeleton(status);
status.expectErrorAndReset(cas.expectedToSkelStatus, cas.msg);
}
IcuTestErrorCode status(*this, "unitSkeletons");
- MeasureUnit METER_PER_SECOND = MeasureUnit::forIdentifier("meter-per-second", status);
-
assertEquals( //
".unit(METER_PER_SECOND) normalization", //
u"unit/meter-per-second", //
.unit(METER)
.perUnit(MeasureUnit::forIdentifier("hectosecond", status))
.toSkeleton(status));
+
+ status.assertSuccess();
+ assertEquals( //
+ ".unit(CURRENCY) produces a currency/CURRENCY skeleton", //
+ u"currency/GBP", //
+ NumberFormatter::with().unit(GBP).toSkeleton(status));
+ status.assertSuccess();
+ // .unit(CURRENCY).perUnit(ANYTHING) is not supported.
+ NumberFormatter::with().unit(GBP).perUnit(METER).toSkeleton(status);
+ status.expectErrorAndReset(U_UNSUPPORTED_ERROR);
}
void NumberFormatterApiTest::unitUsage() {
TESTCASE_AUTO(flexibleSeparators);
TESTCASE_AUTO(wildcardCharacters);
TESTCASE_AUTO(perUnitInArabic);
+ TESTCASE_AUTO(perUnitToSkeleton);
TESTCASE_AUTO_END;
}
}
}
+void NumberSkeletonTest::perUnitToSkeleton() {
+ IcuTestErrorCode status(*this, "perUnitToSkeleton");
+ struct TestCase {
+ const char16_t* type;
+ const char16_t* subtype;
+ } cases[] = {
+ {u"area", u"acre"},
+ {u"concentr", u"percent"},
+ {u"concentr", u"permille"},
+ {u"concentr", u"permillion"},
+ {u"concentr", u"permyriad"},
+ {u"digital", u"bit"},
+ {u"length", u"yard"},
+ };
+
+ for (const auto& cas1 : cases) {
+ for (const auto& cas2 : cases) {
+ UnicodeString skeleton(u"measure-unit/");
+ skeleton += cas1.type;
+ skeleton += u"-";
+ skeleton += cas1.subtype;
+ skeleton += u" ";
+ skeleton += u"per-measure-unit/";
+ skeleton += cas2.type;
+ skeleton += u"-";
+ skeleton += cas2.subtype;
+
+ status.setScope(skeleton);
+ if (cas1.type != cas2.type && cas1.subtype != cas2.subtype) {
+ UnicodeString toSkeleton = NumberFormatter::forSkeleton(
+ skeleton, status).toSkeleton(status);
+ if (status.errIfFailureAndReset()) {
+ continue;
+ }
+ // Ensure both subtype are in the toSkeleton.
+ UnicodeString msg;
+ msg.append(toSkeleton)
+ .append(" should contain '")
+ .append(UnicodeString(cas1.subtype))
+ .append("' when constructed from ")
+ .append(skeleton);
+ assertTrue(msg, toSkeleton.indexOf(cas1.subtype) >= 0);
+
+ msg.remove();
+ msg.append(toSkeleton)
+ .append(" should contain '")
+ .append(UnicodeString(cas2.subtype))
+ .append("' when constructed from ")
+ .append(skeleton);
+ assertTrue(msg, toSkeleton.indexOf(cas2.subtype) >= 0);
+ }
+ }
+ }
+}
+
#endif /* #if !UCONFIG_NO_FORMATTING */
case STATE_INCREMENT_PRECISION:
case STATE_MEASURE_UNIT:
case STATE_PER_MEASURE_UNIT:
+ case STATE_IDENTIFIER_UNIT:
case STATE_UNIT_USAGE:
case STATE_CURRENCY_UNIT:
case STATE_INTEGER_WIDTH:
BlueprintHelpers.parseScientificStem(segment, macros);
return ParseState.STATE_NULL;
case '0':
- checkNull(macros.notation, segment);
+ checkNull(macros.integerWidth, segment);
BlueprintHelpers.parseIntegerStem(segment, macros);
return ParseState.STATE_NULL;
}
return ParseState.STATE_MEASURE_UNIT;
case STEM_PER_MEASURE_UNIT:
+ // In C++, STEM_CURRENCY's checks mark perUnit as "seen". Here we do
+ // the inverse: checking that macros.unit is not set to a currency.
+ if (macros.unit instanceof Currency) {
+ throw new SkeletonSyntaxException("Duplicated setting", segment);
+ }
checkNull(macros.perUnit, segment);
return ParseState.STATE_PER_MEASURE_UNIT;
case STEM_CURRENCY:
checkNull(macros.unit, segment);
+ checkNull(macros.perUnit, segment);
return ParseState.STATE_CURRENCY_UNIT;
case STEM_INTEGER_WIDTH:
}
private static boolean unit(MacroProps macros, StringBuilder sb) {
- if (macros.unit instanceof Currency) {
+ MeasureUnit unit = macros.unit;
+ if (macros.perUnit != null) {
+ if (macros.unit instanceof Currency || macros.perUnit instanceof Currency) {
+ throw new UnsupportedOperationException(
+ "Cannot generate number skeleton with currency unit and per-unit");
+ }
+ unit = unit.product(macros.perUnit.reciprocal());
+ }
+ if (unit instanceof Currency) {
sb.append("currency/");
- BlueprintHelpers.generateCurrencyOption((Currency) macros.unit, sb);
+ BlueprintHelpers.generateCurrencyOption((Currency)unit, sb);
return true;
- } else if (macros.unit == MeasureUnit.PERCENT) {
+ } else if (unit.equals(MeasureUnit.PERCENT)) {
sb.append("percent");
return true;
- } else if (macros.unit == MeasureUnit.PERMILLE) {
+ } else if (unit.equals(MeasureUnit.PERMILLE)) {
sb.append("permille");
return true;
} else {
- MeasureUnit unit = macros.unit;
- if (macros.perUnit != null) {
- if (macros.perUnit instanceof Currency) {
- throw new UnsupportedOperationException(
- "Cannot generate number skeleton with per-unit that is not a standard measure unit");
- }
- unit = unit.product(macros.perUnit.reciprocal());
- }
sb.append("unit/");
sb.append(unit.getIdentifier());
return true;
"2.4 m/s\u00B2");
}
+ // TODO: merge these tests into NumberSkeletonTest.java instead of here:
@Test
public void unitSkeletons() {
Object[][] cases = {
"unit/meter-per-second"},
{"short-form compound units stay as is", //
- "unit/square-meter-per-square-meter", //
+ "unit/square-meter-per-square-meter", //
"unit/square-meter-per-square-meter"},
{"short-form compound units stay as is", //
- "unit/joule-per-furlong", //
+ "unit/joule-per-furlong", //
"unit/joule-per-furlong"},
{"short-form that doesn't consist of built-in units", //
"unit/meter-per-hectosecond", //
"unit/meter-per-hectosecond"},
+ {"percent compound skeletons handled correctly", //
+ "unit/percent-per-meter", //
+ "unit/percent-per-meter"},
+
+ {"permille compound skeletons handled correctly", //
+ "measure-unit/concentr-permille per-measure-unit/length-meter", //
+ "unit/permille-per-meter"},
+
+ {"percent simple unit is not actually considered a unit", //
+ "unit/percent", //
+ "percent"},
+
+ {"permille simple unit is not actually considered a unit", //
+ "measure-unit/concentr-permille", //
+ "permille"},
+
// // TODO: binary prefixes not supported yet!
// {"Round-trip example from icu-units#35", //
// "unit/kibijoule-per-furlong", //
"measure-unit/meter per-measure-unit/hectosecond", //
true, //
false},
+
+ {"\"currency/EUR measure-unit/length-meter\" fails, conflicting skeleton.",
+ "currency/EUR measure-unit/length-meter", //
+ true, //
+ false},
+
+ {"\"measure-unit/length-meter currency/EUR\" fails, conflicting skeleton.",
+ "measure-unit/length-meter currency/EUR", //
+ true, //
+ false},
+
+ {"\"currency/EUR per-measure-unit/meter\" fails, conflicting skeleton.",
+ "currency/EUR per-measure-unit/length-meter", //
+ true, //
+ false},
};
for (Object[] cas : failCases) {
String msg = (String)cas[0];
}
}
}
+
+ assertEquals( //
+ ".unit(METER_PER_SECOND) normalization", //
+ "unit/meter-per-second", //
+ NumberFormatter.with().unit(MeasureUnit.METER_PER_SECOND).toSkeleton());
+ assertEquals( //
+ ".unit(METER).perUnit(SECOND) normalization", //
+ "unit/meter-per-second",
+ NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND).toSkeleton());
+ assertEquals( //
+ ".unit(MeasureUnit.forIdentifier(\"hectometer\")) normalization", //
+ "unit/hectometer",
+ NumberFormatter.with().unit(MeasureUnit.forIdentifier("hectometer")).toSkeleton());
+ assertEquals( //
+ ".unit(MeasureUnit.forIdentifier(\"hectometer\")) normalization", //
+ "unit/meter-per-hectosecond",
+ NumberFormatter.with()
+ .unit(MeasureUnit.METER)
+ .perUnit(MeasureUnit.forIdentifier("hectosecond"))
+ .toSkeleton());
+
+ assertEquals( //
+ ".unit(CURRENCY) produces a currency/CURRENCY skeleton", //
+ "currency/GBP", //
+ NumberFormatter.with().unit(GBP).toSkeleton());
+
+ // .unit(CURRENCY).perUnit(ANYTHING) is not supported.
+ try {
+ NumberFormatter.with().unit(GBP).perUnit(MeasureUnit.METER).toSkeleton();
+ fail("should give an error, unit(currency) with perUnit() is invalid.");
+ } catch (UnsupportedOperationException e) {
+ // Pass
+ }
}
@Test
}
}
}
+
+ @Test
+ public void perUnitToSkeleton() {
+ String[][] cases = {
+ {"area", "acre"},
+ {"concentr", "percent"},
+ {"concentr", "permille"},
+ {"concentr", "permillion"},
+ {"concentr", "permyriad"},
+ {"digital", "bit"},
+ {"length", "yard"},
+ };
+
+ for (String[] cas1 : cases) {
+ for (String[] cas2 : cases) {
+ String skeleton = "measure-unit/" + cas1[0] + "-" + cas1[1] + " per-measure-unit/" +
+ cas2[0] + "-" + cas2[1];
+
+ if (cas1[0] != cas2[0] && cas1[1] != cas2[1]) {
+ String toSkeleton = NumberFormatter.forSkeleton(skeleton).toSkeleton();
+
+ // Ensure both subtype are in the toSkeleton.
+ String msg;
+ msg = toSkeleton + " should contain '" + cas1[1] + "' when constructed from " +
+ skeleton;
+ assertTrue(msg, toSkeleton.indexOf(cas1[1]) >= 0);
+ msg = toSkeleton + " should contain '" + cas2[1] + "' when constructed from " +
+ skeleton;
+ assertTrue(msg, toSkeleton.indexOf(cas2[1]) >= 0);
+ }
+ }
+ }
+ }
}