blob: 4445facc63d8d4c0b083d82743fdda6436de2ff6 [file] [log] [blame]
// © 2019 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
package org.unicode.icu.tool.cldrtoicu.mapper;
import static com.google.common.truth.Truth.assertThat;
import static java.util.Optional.empty;
import static org.unicode.cldr.api.CldrDataSupplier.CldrResolution.RESOLVED;
import static org.unicode.cldr.api.CldrDataSupplier.CldrResolution.UNRESOLVED;
import static org.unicode.cldr.api.CldrValue.parseValue;
import static org.unicode.icu.tool.cldrtoicu.testing.AssertUtils.assertThrows;
import static org.unicode.icu.tool.cldrtoicu.testing.IcuDataSubjectFactory.assertThat;
import java.util.Arrays;
import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.unicode.cldr.api.CldrValue;
import org.unicode.icu.tool.cldrtoicu.IcuData;
import org.unicode.icu.tool.cldrtoicu.PathValueTransformer.Result;
import org.unicode.icu.tool.cldrtoicu.RbValue;
import org.unicode.icu.tool.cldrtoicu.testing.FakeDataSupplier;
import org.unicode.icu.tool.cldrtoicu.testing.FakeResult;
import org.unicode.icu.tool.cldrtoicu.testing.FakeTransformer;
// Almost all the unit-testing for LocaleMapper is done via AbstractPathValueMapper or
// RegexTransformer (and friends). Very little is left that's special to locale data.
@RunWith(JUnit4.class)
public class LocaleMapperTest {
private final FakeTransformer transformer = new FakeTransformer();
private final FakeDataSupplier src = new FakeDataSupplier();
@Test
public void testSimple() {
//ldml/units/durationUnit[@type="(%A)"]/durationUnitPattern ; /durationUnits/$1
addMapping("xx",
ldml("units/durationUnit[@type=\"foo\"]/durationUnitPattern", "Bar"),
simpleResult("/durationUnits/foo", "Bar"));
IcuData icuData = process("xx");
assertThat(icuData).getPaths().hasSize(1);
assertThat(icuData).hasValuesFor("/durationUnits/foo", "Bar");
}
@Test
public void testCorrectLocaleIsUsed() {
src.addLocaleData(
"xx", ldml("units/durationUnit[@type=\"foo\"]/durationUnitPattern", "XX"));
addMapping(
"yy", ldml("units/durationUnit[@type=\"foo\"]/durationUnitPattern", "YY"),
simpleResult("/durationUnits/foo", "YY"));
src.addLocaleData(
"zz", ldml("units/durationUnit[@type=\"foo\"]/durationUnitPattern", "ZZ"));
IcuData icuData = process("yy");
assertThat(icuData).getPaths().hasSize(1);
assertThat(icuData).hasValuesFor("/durationUnits/foo", "YY");
}
@Test
public void testInheritedValuesNotIncludedByDefault() {
//ldml/units/durationUnit[@type="(%A)"]/durationUnitPattern ; /durationUnits/$1
addMapping("xx",
ldml("units/durationUnit[@type=\"foo\"]/durationUnitPattern", "Bar"),
simpleResult("/durationUnits/foo", "Bar"));
//ldml/localeDisplayNames/keys/key[@type="(%A)"] ; /Keys/$1
addRootMapping(
ldml("localeDisplayNames/keys/key[@type=\"sometype\"]", "Value"),
simpleResult("/Keys/sometype", "Value"));
IcuData icuData = process("xx");
// The 2nd mapping is not used because it does not appear in the unresolved CldrData.
assertThat(icuData).getPaths().hasSize(1);
assertThat(icuData).hasValuesFor("/durationUnits/foo", "Bar");
}
@Test
public void testInheritedValuesIncludedWhenSameResourceBundle() {
//ldml/numbers/currencies/currency[@type="(%W)"]/symbol ; /Currencies/$1 ; fallback=$1
//ldml/numbers/currencies/currency[@type="(%W)"]/displayName ; /Currencies/$1 ; fallback=$1
addMapping("xx",
ldml("numbers/currencies/currency[@type=\"USD\"]/symbol", "US$"),
simpleResult("/Currencies/USD", 1, "US$"));
// This is included because the resource bundle path is the same as above. Note that we
// have to use the index to distinguish results here (this corresponds to the line number
// or the real when the real regex based config is used and determines result ordering).
addRootMapping(
ldml("numbers/currencies/currency[@type=\"USD\"]/displayName", "US Dollar"),
simpleResult("/Currencies/USD", 2, "US Dollar"));
IcuData icuData = process("xx");
// Now the inherited mapping is used because the path appeared for the unresolved CldrData.
assertThat(icuData).getPaths().hasSize(1);
assertThat(icuData).hasValuesFor("/Currencies/USD", singletonValues("US$", "US Dollar"));
}
@Test
public void testChildPathsNotIncludedByDefault() {
// Tests that in the case that one path is the child of another path (rare) the existence
// of the parent path will not trigger the child path to be included.
//
//ldml/.../dateTimeFormats/availableFormats/dateFormatItem[@id="(%A)"]
// ; /calendar/$1/availableFormats/$2
//ldml/.../dateTimeFormats/availableFormats/dateFormatItem[@id="(%A)"][@count="(%A)"]
// ; /calendar/$1/availableFormats/$2/$3
addMapping("xx",
ldml("dates/calendars/calendar[@type=\"foo\"]/dateTimeFormats"
+ "/availableFormats/dateFormatItem[@id=\"bar\"]", "Foo"),
simpleResult("/calendar/foo/availableFormats/bar", "Foo"));
addRootMapping(
ldml("dates/calendars/calendar[@type=\"foo\"]/dateTimeFormats"
+ "/availableFormats/dateFormatItem[@id=\"bar\"][@count=\"one\"]", "Bar"),
simpleResult("/calendar/foo/availableFormats/bar/one", "Bar"));
IcuData icuData = process("xx");
// Now the inherited mapping is used because the path appeared for the unresolved CldrData.
assertThat(icuData).getPaths().hasSize(1);
assertThat(icuData).hasValuesFor("/calendar/foo/availableFormats/bar", "Foo");
}
@Test
public void testParentPathsNotIncludedByDefault() {
// Same as above but swapping inherited vs explicit mappings.
addRootMapping(
ldml("dates/calendars/calendar[@type=\"foo\"]/dateTimeFormats"
+ "/availableFormats/dateFormatItem[@id=\"bar\"]", "Foo"),
simpleResult("/calendar/foo/availableFormats/bar", "Foo"));
addMapping("xx",
ldml("dates/calendars/calendar[@type=\"foo\"]/dateTimeFormats"
+ "/availableFormats/dateFormatItem[@id=\"bar\"][@count=\"one\"]", "Bar"),
simpleResult("/calendar/foo/availableFormats/bar/one", "Bar"));
IcuData icuData = process("xx");
// Now the inherited mapping is used because the path appeared for the unresolved CldrData.
assertThat(icuData).getPaths().hasSize(1);
assertThat(icuData).hasValuesFor("/calendar/foo/availableFormats/bar/one", "Bar");
}
// This is done so that when these paths are written into the ICU data file (and the hidden
// labels are removed) you get the "two layer" array:
//
// {
// "Parent",
// { "Child-1", "Child-2" }
// }
//
// This needs to happen even when only one of the child elements is given explicitly.
@Test
public void testHiddenLabelsIncludeParentPaths() {
// Testing that the existence of a child element using a hidden label *does* trigger the
// parent element to be included.
addRootMapping(
ldml("dates/calendars/calendar[@type=\"foo\"]/dateTimeFormats"
+ "/availableFormats/dateFormatItem[@id=\"bar\"]", "Parent"),
simpleResult("/calendar/foo/availableFormats/bar", "Parent"));
addRootMapping(
ldml("dates/calendars/calendar[@type=\"foo\"]/dateTimeFormats"
+ "/availableFormats/dateFormatItem[@id=\"bar\"][@count=\"one\"]", "Child-1"),
simpleResult("/calendar/foo/availableFormats/bar/<HIDDEN>", 1, "Child-1"));
// This is the only explicit mapping and it triggers the sibling _and_ the parent.
addMapping("xx",
ldml("dates/calendars/calendar[@type=\"foo\"]/dateTimeFormats"
+ "/availableFormats/dateFormatItem[@id=\"bar\"][@count=\"many\"]", "Child-2"),
simpleResult("/calendar/foo/availableFormats/bar/<HIDDEN>", 2, "Child-2"));
IcuData icuData = process("xx");
assertThat(icuData).getPaths().hasSize(2);
assertThat(icuData).hasValuesFor("/calendar/foo/availableFormats/bar", "Parent");
assertThat(icuData)
.hasValuesFor("/calendar/foo/availableFormats/bar/<HIDDEN>",
singletonValues("Child-1", "Child-2"));
}
// This is strange behaviour given the test above, since it means that it's impossible to
// use hidden labels to create a situation where the output ICU data looks like:
//
// {
// "Parent",
// { "Child-1", "Child-2" },
// "Other Parent"
// }
//
// if the child elements can be inherited; since if they are not present, you just get:
//
// {
// "Parent",
// "Other Parent"
// }
//
// Which moves the index of the following elements up by one and makes it impossible to
// define a stable length or index mapping for the array.
//
// However this is relied upon in the /Currencies/XXX case where a child array exists, but
// is optional if none of it's values are explicitly present. For example in en_150.txt:
//
// Currencies{
// EUR{
// "€",
// "Euro",
// {
// "¤#,##0.00",
// ".",
// ",",
// }
// }
// }
//
// In most cases the formatting/grouping information is omitted if it can all be inherited.
//
// This only really works because the child array is the last element in the parent array, so
// not having it present doesn't affect any later elements.
//
// The "group" instruction in the transformation configuration files is a different way to
// allow grouping of sub-arrays which does not have this behaviour.
@Test
public void testHiddenLabelsAreNotIncludedAutomatically() {
// As above, but now only the parent path is included explicitly.
addMapping("xx",
ldml("dates/calendars/calendar[@type=\"foo\"]/dateTimeFormats"
+ "/availableFormats/dateFormatItem[@id=\"bar\"]", "Parent"),
simpleResult("/calendar/foo/availableFormats/bar", "Parent"));
addRootMapping(
ldml("dates/calendars/calendar[@type=\"foo\"]/dateTimeFormats"
+ "/availableFormats/dateFormatItem[@id=\"bar\"][@count=\"one\"]", "Child-1"),
simpleResult("/calendar/foo/availableFormats/bar/<HIDDEN>", 1, "Child-1"));
// This is the only explicit mapping and it triggers the sibling _and_ the parent.
addRootMapping(
ldml("dates/calendars/calendar[@type=\"foo\"]/dateTimeFormats"
+ "/availableFormats/dateFormatItem[@id=\"bar\"][@count=\"many\"]", "Child-2"),
simpleResult("/calendar/foo/availableFormats/bar/<HIDDEN>", 2, "Child-2"));
IcuData icuData = process("xx");
assertThat(icuData).getPaths().hasSize(1);
assertThat(icuData).hasValuesFor("/calendar/foo/availableFormats/bar", "Parent");
}
@Test
public void testDefaultCalendar() {
IcuData icuData = process("xx", Optional.of("pastafarian"));
assertThat(icuData).getPaths().hasSize(1);
assertThat(icuData).hasValuesFor("/calendar/default", "pastafarian");
}
@Test
public void testDateTimeHack() {
//calendar/$1/DateTimePatterns
addMapping("xx",
format("time", "full", "one"),
simpleResult("/calendar/foo/DateTimePatterns", 1, "one"));
addMapping("xx",
format("time", "long", "two"),
simpleResult("/calendar/foo/DateTimePatterns", 2, "two"));
addMapping("xx",
format("time", "medium", "three"),
simpleResult("/calendar/foo/DateTimePatterns", 3, "three"));
addMapping("xx",
format("time", "short", "four"),
simpleResult("/calendar/foo/DateTimePatterns", 4, "four"));
addMapping("xx",
format("date", "full", "five"),
simpleResult("/calendar/foo/DateTimePatterns", 5, "five"));
addMapping("xx",
format("date", "long", "six"),
simpleResult("/calendar/foo/DateTimePatterns", 6, "six"));
addMapping("xx",
format("date", "medium", "seven"),
simpleResult("/calendar/foo/DateTimePatterns", 7, "seven"));
addMapping("xx",
format("date", "short", "eight"),
simpleResult("/calendar/foo/DateTimePatterns", 8, "eight"));
addMapping("xx",
format("dateTime", "full", "nine"),
simpleResult("/calendar/foo/DateTimePatterns", 9, "nine"));
addMapping("xx",
format("dateTime", "long", "ten"),
simpleResult("/calendar/foo/DateTimePatterns", 10, "ten"));
addMapping("xx",
format("dateTime", "medium", "eleven"),
simpleResult("/calendar/foo/DateTimePatterns", 11, "eleven"));
addMapping("xx",
format("dateTime", "short", "twelve"),
simpleResult("/calendar/foo/DateTimePatterns", 12, "twelve"));
IcuData icuData = process("xx");
assertThat(icuData).getPaths().hasSize(1);
assertThat(icuData).hasValuesFor("/calendar/foo/DateTimePatterns",
singletonValues(
"one", "two", "three", "four",
"five", "six", "seven", "eight",
"eleven", // <-- legacy reasons, don't ask!
"nine", "ten", "eleven", "twelve"));
}
@Test
public void testDateTimeHack_wrongNumberofElements() {
// One missing pattern from the start.
addMapping("xx",
format("time", "long", "two"),
simpleResult("/calendar/foo/DateTimePatterns", 2, "two"));
addMapping("xx",
format("time", "medium", "three"),
simpleResult("/calendar/foo/DateTimePatterns", 3, "three"));
addMapping("xx",
format("time", "short", "four"),
simpleResult("/calendar/foo/DateTimePatterns", 4, "four"));
addMapping("xx",
format("date", "full", "five"),
simpleResult("/calendar/foo/DateTimePatterns", 5, "five"));
addMapping("xx",
format("date", "long", "six"),
simpleResult("/calendar/foo/DateTimePatterns", 6, "six"));
addMapping("xx",
format("date", "medium", "seven"),
simpleResult("/calendar/foo/DateTimePatterns", 7, "seven"));
addMapping("xx",
format("date", "short", "eight"),
simpleResult("/calendar/foo/DateTimePatterns", 8, "eight"));
addMapping("xx",
format("dateTime", "full", "nine"),
simpleResult("/calendar/foo/DateTimePatterns", 9, "nine"));
addMapping("xx",
format("dateTime", "long", "ten"),
simpleResult("/calendar/foo/DateTimePatterns", 10, "ten"));
addMapping("xx",
format("dateTime", "medium", "eleven"),
simpleResult("/calendar/foo/DateTimePatterns", 11, "eleven"));
addMapping("xx",
format("dateTime", "short", "twelve"),
simpleResult("/calendar/foo/DateTimePatterns", 12, "twelve"));
IllegalStateException e = assertThrows(IllegalStateException.class, () -> process("xx"));
assertThat(e).hasMessageThat().contains("unexpected");
assertThat(e).hasMessageThat().contains("/calendar/foo/DateTimePatterns");
}
private static CldrValue format(String type,String length, String pattern) {
return ldml(String.format(
"dates/calendars/calendar[@type=\"foo\"]"
+ "/%1$sFormats"
+ "/%1$sFormatLength[@type=\"%2$s\"]"
+ "/%1$sFormat[@type=\"standard\"]/pattern[@type=\"%3$s\"]",
type, length, pattern));
}
// ---- Helper methods ----
IcuData process(String localeId) {
return process(localeId, empty());
}
IcuData process(String localeId, Optional<String> defCalendar) {
IcuData icuData = new IcuData(localeId, true);
LocaleMapper.process(
icuData,
src.getDataForLocale(localeId, UNRESOLVED),
src.getDataForLocale(localeId, RESOLVED),
empty(),
transformer,
defCalendar);
return icuData;
}
private void addMapping(String locale, CldrValue value, Result... results) {
src.addLocaleData(locale, value);
transformer.addResults(value, results);
}
private void addRootMapping(CldrValue value, Result... results) {
src.addLocaleData("root", value);
transformer.addResults(value, results);
}
private static Result simpleResult(String path, String value) {
return FakeResult.of(path, 1, false, value);
}
private static Result simpleResult(String path, int index, String value) {
return FakeResult.of(path, index, false, value);
}
private static CldrValue ldml(String path) {
return ldml(path, "");
}
private static CldrValue ldml(String path, String value) {
return parseValue("//ldml/" + path, value);
}
private static RbValue[] singletonValues(String... values) {
return Arrays.stream(values).map(RbValue::of).toArray(RbValue[]::new);
}
}