ICU-21840 Fix formatting Aliases

See #2016
This commit is contained in:
Younies 2022-03-15 20:29:45 +00:00 committed by Younies Mahmoud
parent b1269c9121
commit f30956fc9c
5 changed files with 126 additions and 8 deletions

View file

@ -431,13 +431,33 @@ void getMeasureData(const Locale &locale,
subKey.append(unit.getType(), status);
subKey.append("/", status);
// Check if unitSubType is an alias or not.
LocalUResourceBundlePointer aliasBundle(ures_open(U_ICUDATA_ALIAS, "metadata", &status));
UErrorCode aliasStatus = status;
StackUResourceBundle aliasFillIn;
CharString aliasKey;
aliasKey.append("alias/unit/", aliasStatus);
aliasKey.append(unit.getSubtype(), aliasStatus);
aliasKey.append("/replacement", aliasStatus);
ures_getByKeyWithFallback(aliasBundle.getAlias(), aliasKey.data(), aliasFillIn.getAlias(),
&aliasStatus);
CharString unitSubType;
if (!U_FAILURE(aliasStatus)) {
// This means the subType is an alias. Then, replace unitSubType with the replacement.
auto replacement = ures_getUnicodeString(aliasFillIn.getAlias(), &status);
unitSubType.appendInvariantChars(replacement, status);
} else {
unitSubType.append(unit.getSubtype(), status);
}
// Map duration-year-person, duration-week-person, etc. to duration-year, duration-week, ...
// TODO(ICU-20400): Get duration-*-person data properly with aliases.
int32_t subtypeLen = static_cast<int32_t>(uprv_strlen(unit.getSubtype()));
if (subtypeLen > 7 && uprv_strcmp(unit.getSubtype() + subtypeLen - 7, "-person") == 0) {
subKey.append({unit.getSubtype(), subtypeLen - 7}, status);
int32_t subtypeLen = static_cast<int32_t>(uprv_strlen(unitSubType.data()));
if (subtypeLen > 7 && uprv_strcmp(unitSubType.data() + subtypeLen - 7, "-person") == 0) {
subKey.append({unitSubType.data(), subtypeLen - 7}, status);
} else {
subKey.append({unit.getSubtype(), subtypeLen}, status);
subKey.append({unitSubType.data(), subtypeLen}, status);
}
if (width != UNUM_UNIT_WIDTH_FULL_NAME) {

View file

@ -101,7 +101,8 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition {
void toObject();
void toDecimalNumber();
void microPropsInternals();
void formatUnitsAliases();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0) override;
private:

View file

@ -129,6 +129,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha
TESTCASE_AUTO(toObject);
TESTCASE_AUTO(toDecimalNumber);
TESTCASE_AUTO(microPropsInternals);
TESTCASE_AUTO(formatUnitsAliases);
TESTCASE_AUTO_END;
}
@ -6174,6 +6175,37 @@ void NumberFormatterApiTest::microPropsInternals() {
assertEquals("Copy Assigned capacity", 4, copyAssigned.mixedMeasures.getCapacity());
}
void NumberFormatterApiTest::formatUnitsAliases() {
IcuTestErrorCode status(*this, "formatUnitsAliases");
struct TestCase {
const MeasureUnit measureUnit;
const UnicodeString expectedFormat;
} testCases[]{
// Aliases
{MeasureUnit::getMilligramPerDeciliter(), u"2 milligrams per deciliter"},
{MeasureUnit::getLiterPer100Kilometers(), u"2 liters per 100 kilometers"},
{MeasureUnit::getPartPerMillion(), u"2 parts per million"},
{MeasureUnit::getMillimeterOfMercury(), u"2 millimeters of mercury"},
// Replacements
{MeasureUnit::getMilligramOfglucosePerDeciliter(), u"2 milligrams per deciliter"},
{MeasureUnit::forIdentifier("millimeter-ofhg", status), u"2 millimeters of mercury"},
{MeasureUnit::forIdentifier("liter-per-100-kilometer", status), u"2 liters per 100 kilometers"},
{MeasureUnit::forIdentifier("permillion", status), u"2 parts per million"},
};
for (const auto &testCase : testCases) {
UnicodeString actualFormat = NumberFormatter::withLocale(icu::Locale::getEnglish())
.unit(testCase.measureUnit)
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME)
.formatDouble(2.0, status)
.toString(status);
assertEquals("test unit aliases", testCase.expectedFormat, actualFormat);
}
}
/* For skeleton comparisons: this checks the toSkeleton output for `f` and for
* `conciseSkeleton` against the normalized version of `uskeleton` - this does
* not round-trip uskeleton itself.

View file

@ -329,6 +329,21 @@ public class LongNameHandler
}
}
private static final class AliasSink extends UResource.Sink {
String replacement;
@Override
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
UResource.Table aliasTable = value.getTable();
for (int i = 0; aliasTable.getKeyAndValue(i, key, value); ++i) {
String keyString = key.toString();
if (keyString.equals("replacement")) {
this.replacement = value.toString();
}
}
}
}
// NOTE: outArray MUST have at least ARRAY_LENGTH entries. No bounds checking is performed.
static void getMeasureData(
ULocale locale,
@ -346,12 +361,23 @@ public class LongNameHandler
subKey.append(unit.getType());
subKey.append("/");
// If the unit is an alias, replace it is identifier with the replacement.
String unitSubType = unit.getSubtype();
ICUResourceBundle metadataResource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "metadata");
AliasSink aliasSink = new AliasSink();
metadataResource.getAllItemsWithFallbackNoFail("alias/unit/" + unitSubType, aliasSink);
if (aliasSink.replacement != null) {
unitSubType = aliasSink.replacement;
}
// Map duration-year-person, duration-week-person, etc. to duration-year, duration-week, ...
// TODO(ICU-20400): Get duration-*-person data properly with aliases.
if (unit.getSubtype() != null && unit.getSubtype().endsWith("-person")) {
subKey.append(unit.getSubtype(), 0, unit.getSubtype().length() - 7);
if (unitSubType != null && unitSubType.endsWith("-person")) {
subKey.append(unitSubType, 0, unitSubType.length() - 7);
} else {
subKey.append(unit.getSubtype());
subKey.append(unitSubType);
}
if (width != UnitWidth.FULL_NAME) {

View file

@ -5857,6 +5857,45 @@ public class NumberFormatterApiTest extends TestFmwk {
}
}
@Test
public void formatUnitsAliases() {
class TestCase {
final MeasureUnit measureUnit;
final String expectedFormat;
TestCase(MeasureUnit measureUnit, String expectedFormat) {
this.measureUnit = measureUnit;
this.expectedFormat = expectedFormat;
}
}
TestCase[] testCases = {
// Aliases
new TestCase(MeasureUnit.MILLIGRAM_PER_DECILITER, "2 milligrams per deciliter"),
new TestCase(MeasureUnit.LITER_PER_100KILOMETERS, "2 liters per 100 kilometers"),
new TestCase(MeasureUnit.PART_PER_MILLION, "2 parts per million"),
new TestCase(MeasureUnit.MILLIMETER_OF_MERCURY, "2 millimeters of mercury"),
// Replacements
new TestCase(MeasureUnit.MILLIGRAM_OFGLUCOSE_PER_DECILITER, "2 milligrams per deciliter"),
new TestCase(MeasureUnit.forIdentifier("millimeter-ofhg"), "2 millimeters of mercury"),
new TestCase(MeasureUnit.forIdentifier("liter-per-100-kilometer"), "2 liters per 100 kilometers"),
new TestCase(MeasureUnit.forIdentifier("permillion"), "2 parts per million"),
};
for (TestCase testCase : testCases) {
String actualFormat = NumberFormatter
.withLocale(ULocale.ENGLISH)
.unit(testCase.measureUnit)
.unitWidth(UnitWidth.FULL_NAME)
.format(2.0)
.toString();
assertEquals("test unit aliases", testCase.expectedFormat, actualFormat);
}
}
static void assertFormatDescending(
String message,
String skeleton,