Merge pull request #33 from icu-units/skeleton

Add Unit Usage support to Number Skeletons.
This commit is contained in:
Hugo van der Merwe 2020-07-16 10:56:28 +02:00 committed by GitHub
commit 3cf23222ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 101 additions and 59 deletions

View file

@ -97,6 +97,7 @@ void U_CALLCONV initNumberSkeletons(UErrorCode& status) {
b.add(u"measure-unit", STEM_MEASURE_UNIT, status);
b.add(u"per-measure-unit", STEM_PER_MEASURE_UNIT, status);
b.add(u"unit", STEM_UNIT, status);
b.add(u"usage", STEM_UNIT_USAGE, status);
b.add(u"currency", STEM_CURRENCY, status);
b.add(u"integer-width", STEM_INTEGER_WIDTH, status);
b.add(u"numbering-system", STEM_NUMBERING_SYSTEM, status);
@ -550,6 +551,7 @@ MacroProps skeleton::parseSkeleton(
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:
case STATE_NUMBERING_SYSTEM:
@ -705,7 +707,7 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se
macros.decimal = stem_to_object::decimalSeparatorDisplay(stem);
return STATE_NULL;
// Stems requiring an option:
// Stems requiring an option:
case STEM_PRECISION_INCREMENT:
CHECK_NULL(seen, precision, status);
@ -724,6 +726,10 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se
CHECK_NULL(seen, perUnit, status);
return STATE_IDENTIFIER_UNIT;
case STEM_UNIT_USAGE:
CHECK_NULL(seen, usage, status);
return STATE_UNIT_USAGE;
case STEM_CURRENCY:
CHECK_NULL(seen, unit, status);
return STATE_CURRENCY_UNIT;
@ -763,6 +769,9 @@ ParseState skeleton::parseOption(ParseState stem, const StringSegment& segment,
case STATE_IDENTIFIER_UNIT:
blueprint_helpers::parseIdentifierUnitOption(segment, macros, status);
return STATE_NULL;
case STATE_UNIT_USAGE:
blueprint_helpers::parseUnitUsageOption(segment, macros, status);
return STATE_NULL;
case STATE_INCREMENT_PRECISION:
blueprint_helpers::parseIncrementOption(segment, macros, status);
return STATE_NULL;
@ -837,6 +846,10 @@ void GeneratorHelpers::generateSkeleton(const MacroProps& macros, UnicodeString&
sb.append(u' ');
}
if (U_FAILURE(status)) { return; }
if (GeneratorHelpers::usage(macros, sb, status)) {
sb.append(u' ');
}
if (U_FAILURE(status)) { return; }
if (GeneratorHelpers::precision(macros, sb, status)) {
sb.append(u' ');
}
@ -1057,6 +1070,17 @@ void blueprint_helpers::parseIdentifierUnitOption(const StringSegment& segment,
}
}
void blueprint_helpers::parseUnitUsageOption(const StringSegment &segment, MacroProps &macros,
UErrorCode &status) {
// Need to do char <-> UChar conversion...
U_ASSERT(U_SUCCESS(status));
CharString buffer;
SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
macros.usage.set(buffer.toStringPiece());
// We do not do any validation of the usage string: it depends on the
// unitPreferenceData in the units resources.
}
void blueprint_helpers::parseFractionStem(const StringSegment& segment, MacroProps& macros,
UErrorCode& status) {
U_ASSERT(segment.charAt(0) == u'.');
@ -1545,6 +1569,15 @@ bool GeneratorHelpers::perUnit(const MacroProps& macros, UnicodeString& sb, UErr
}
}
bool GeneratorHelpers::usage(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
if (macros.usage.fLength > 0) {
sb.append(u"usage/", -1);
sb.append(UnicodeString(macros.usage.fUsage, -1, US_INV));
return true;
}
return false;
}
bool GeneratorHelpers::precision(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
if (macros.precision.fType == Precision::RND_NONE) {
sb.append(u"precision-unlimited", -1);

View file

@ -22,10 +22,12 @@ struct SeenMacroProps;
// namespace for enums and entrypoint functions
namespace skeleton {
///////////////////////////////////////////////////////////////////////////////////////
// NOTE: For an example of how to add a new stem to the number skeleton parser, see: //
// http://bugs.icu-project.org/trac/changeset/41193 //
///////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
// NOTE: For examples of how to add a new stem to the number skeleton parser, see: //
// https://github.com/unicode-org/icu/commit/a2a7982216b2348070dc71093775ac7195793d73 //
// and //
// https://github.com/unicode-org/icu/commit/6fe86f3934a8a5701034f648a8f7c5087e84aa28 //
////////////////////////////////////////////////////////////////////////////////////////
/**
* While parsing a skeleton, this enum records what type of option we expect to find next.
@ -47,6 +49,7 @@ enum ParseState {
STATE_MEASURE_UNIT,
STATE_PER_MEASURE_UNIT,
STATE_IDENTIFIER_UNIT,
STATE_UNIT_USAGE,
STATE_CURRENCY_UNIT,
STATE_INTEGER_WIDTH,
STATE_NUMBERING_SYSTEM,
@ -112,6 +115,7 @@ enum StemEnum {
STEM_MEASURE_UNIT,
STEM_PER_MEASURE_UNIT,
STEM_UNIT,
STEM_UNIT_USAGE,
STEM_CURRENCY,
STEM_INTEGER_WIDTH,
STEM_NUMBERING_SYSTEM,
@ -242,6 +246,8 @@ void parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros,
void parseIdentifierUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void parseUnitUsageOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void parseFractionStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
void generateFractionStem(int32_t minFrac, int32_t maxFrac, UnicodeString& sb, UErrorCode& status);
@ -304,6 +310,8 @@ class GeneratorHelpers {
static bool perUnit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
static bool usage(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
static bool precision(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
static bool roundingMode(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
@ -332,6 +340,7 @@ struct SeenMacroProps {
bool notation = false;
bool unit = false;
bool perUnit = false;
bool usage = false;
bool precision = false;
bool roundingMode = false;
bool grouper = false;

View file

@ -1161,7 +1161,9 @@ class U_I18N_API Usage : public UMemory {
/** @internal */
int16_t length() const { return fLength; }
/** @internal */
/** @internal
* Makes a copy of value.
*/
void set(StringPiece value);
/** @internal */
@ -1177,6 +1179,9 @@ class U_I18N_API Usage : public UMemory {
// Allow NumberFormatterImpl to access fUsage.
friend class impl::NumberFormatterImpl;
// Allow skeleton generation code to access private members.
friend class impl::GeneratorHelpers;
// Allow MacroProps/MicroProps to initialize empty instances.
friend struct impl::MacroProps;

View file

@ -329,6 +329,9 @@ int32_t getPreferenceMetadataIndex(const MaybeStackVector<UnitPreferenceMetadata
} else if (uprv_strcmp(desired.usage.data(), "default") != 0) {
desired.usage.truncate(0).append("default", status);
} else {
// TODO(icu-units/icu#36): reconsider consistency of errors.
// Currently this U_MISSING_RESOURCE_ERROR propagates when a
// U_NUMBER_SKELETON_SYNTAX_ERROR might be much more intuitive.
status = U_MISSING_RESOURCE_ERROR;
return -1;
}

View file

@ -687,23 +687,21 @@ void NumberFormatterApiTest::unitUsage() {
formattedNum.getOutputUnit(status).getIdentifier() + "\"",
MeasureUnit::getMeter() == formattedNum.getOutputUnit(status));
assertEquals("unitUsage() en-ZA road", "300 m", formattedNum.toString(status));
// TODO(units,hugovdm): this works with "assertUndefinedSkeleton(f);"
// commented out. Design skeletons ("measure-unit/length-meter usage/road"?)
// then use this:
// assertFormatDescendingBig(u"unitUsage() en-ZA road",
// nullptr,
// nullptr,
// unloc_formatter,
// Locale("en-ZA"),
// u"87\u00A0650 km",
// u"8\u00A0765 km",
// u"877 km",
// u"88 km",
// u"8,8 km",
// u"877 m",
// u"88 m",
// u"8,8 m",
// u"0 m");
assertFormatDescendingBig(
u"unitUsage() en-ZA road",
u"measure-unit/length-meter usage/road",
u"unit/meter usage/road",
unloc_formatter,
Locale("en-ZA"),
u"87\u00A0650 km",
u"8\u00A0765 km",
u"877 km",
u"88 km",
u"8,8 km",
u"877 m",
u"88 m",
u"8,8 m",
u"0 m");
formatter = unloc_formatter.locale("en-GB");
formattedNum = formatter.formatDouble(300, status);
@ -711,23 +709,21 @@ void NumberFormatterApiTest::unitUsage() {
formattedNum.getOutputUnit(status).getIdentifier() + "\"",
MeasureUnit::getYard() == formattedNum.getOutputUnit(status));
assertEquals("unitUsage() en-GB road", "328 yd", formattedNum.toString(status));
// TODO(units,hugovdm): this works with "assertUndefinedSkeleton(f);"
// commented out. Design skeletons ("measure-unit/length-meter usage/road"?)
// then use this:
// assertFormatDescendingBig(u"unitUsage() en-GB road",
// nullptr,
// nullptr,
// unloc_formatter,
// Locale("en-GB"),
// u"54,463 mi",
// u"5,446 mi",
// u"545 mi",
// u"54 mi",
// u"5.4 mi",
// u"0.54 mi",
// u"96 yd",
// u"9.6 yd",
// u"0 yd");
assertFormatDescendingBig(
u"unitUsage() en-GB road",
u"measure-unit/length-meter usage/road",
u"unit/meter usage/road",
unloc_formatter,
Locale("en-GB"),
u"54,463 mi",
u"5,446 mi",
u"545 mi",
u"54 mi",
u"5.4 mi",
u"0.54 mi",
u"96 yd",
u"9.6 yd",
u"0 yd");
formatter = unloc_formatter.locale("en-US");
formattedNum = formatter.formatDouble(300, status);
@ -735,25 +731,21 @@ void NumberFormatterApiTest::unitUsage() {
formattedNum.getOutputUnit(status).getIdentifier() + "\"",
MeasureUnit::getFoot() == formattedNum.getOutputUnit(status));
assertEquals("unitUsage() en-US road", "984 ft", formattedNum.toString(status));
// TODO(units,hugovdm): this works with "assertUndefinedSkeleton(f);"
// commented out. Design skeletons ("measure-unit/length-meter usage/road"?)
// then use this:
// assertFormatDescendingBig(u"unitUsage() en-US road",
// nullptr,
// nullptr,
// unloc_formatter,
// Locale("en-US"),
// u"54,463 mi",
// u"5,446 mi",
// u"545 mi",
// u"54 mi",
// u"5.4 mi",
// u"0.54 mi",
// u"288 ft",
// u"29 ft",
// u"0 ft");
// TODO(hugovdm): consider fixing TODO(ICU-20941) too?
assertFormatDescendingBig(
u"unitUsage() en-US road",
u"measure-unit/length-meter usage/road",
u"unit/meter usage/road",
unloc_formatter,
Locale("en-US"),
u"54,463 mi",
u"5,446 mi",
u"545 mi",
u"54 mi",
u"5.4 mi",
u"0.54 mi",
u"288 ft",
u"29 ft",
u"0 ft");
}
void NumberFormatterApiTest::unitCompoundMeasure() {