This feature is typically used to select alternate translations (e.g. short forms)
for certain paths. -->
- <!-- <altPath target="//path/to/value[@attr='foo']" source="//path/to/value[@attr='bar']"/> -->
-
+ <!-- <altPath target="//path/to/value[@attr='foo']"
+ source="//path/to/value[@attr='bar']"
+ locales="xx,yy_ZZ"/> -->
</convert>
</target>
</project>
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static org.unicode.cldr.api.CldrDataType.LDML;
+import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.unicode.cldr.api.CldrValue;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableTable;
+import com.google.common.collect.Table;
/**
* A factory for data suppliers which can filter CLDR values by substituting values from one path
* values are obtained. For each map entry, the target and source paths must be in the same
* namespace (i.e. have the same path element names).
*/
- public static CldrDataSupplier transform(CldrDataSupplier src, Map<CldrPath, CldrPath> altPaths) {
- return new CldrDataFilter(src, altPaths);
+ public static CldrDataSupplier transform(
+ CldrDataSupplier src,
+ Map<CldrPath, CldrPath> globalAltPaths,
+ Table<String, CldrPath, CldrPath> localeAltPaths) {
+ return new CldrDataFilter(src, globalAltPaths, localeAltPaths);
}
private static final class CldrDataFilter extends CldrDataSupplier {
private final CldrDataSupplier src;
// Mapping from target (destination) to source path. This is necessary since two targets
// could come from the same source).
- private final ImmutableMap<CldrPath, CldrPath> altPaths;
+ private final ImmutableMap<CldrPath, CldrPath> globalAltPaths;
+ private final ImmutableTable<String, CldrPath, CldrPath> localeAltPaths;
CldrDataFilter(
- CldrDataSupplier src, Map<CldrPath, CldrPath> altPaths) {
+ CldrDataSupplier src,
+ Map<CldrPath, CldrPath> globalAltPaths,
+ Table<String, CldrPath, CldrPath> localeAltPaths) {
this.src = checkNotNull(src);
- this.altPaths = ImmutableMap.copyOf(altPaths);
- altPaths.forEach((t, s) -> checkArgument(hasSameNamespace(checkLdml(t), checkLdml(s)),
+ this.globalAltPaths = ImmutableMap.copyOf(globalAltPaths);
+ this.localeAltPaths = ImmutableTable.copyOf(localeAltPaths);
+ this.globalAltPaths
+ .forEach((t, s) -> checkArgument(hasSameNamespace(checkLdml(t), checkLdml(s)),
"alternate paths must have the same namespace: target=%s, source=%s", t, s));
+ this.localeAltPaths.cellSet()
+ .forEach(c -> checkArgument(
+ hasSameNamespace(checkLdml(c.getColumnKey()), checkLdml(c.getValue())),
+ "alternate paths must have the same namespace: locale=%s, target=%s, source=%s",
+ c.getRowKey(), c.getColumnKey(), c.getValue()));
}
@Override
public CldrDataSupplier withDraftStatusAtLeast(CldrDraftStatus draftStatus) {
- return new CldrDataFilter(src.withDraftStatusAtLeast(draftStatus), altPaths);
+ return new CldrDataFilter(
+ src.withDraftStatusAtLeast(draftStatus), globalAltPaths, localeAltPaths);
}
@Override
public CldrData getDataForLocale(String localeId, CldrResolution resolution) {
- return new AltData(src.getDataForLocale(localeId, resolution));
+ return new AltData(src.getDataForLocale(localeId, resolution), localeId);
}
@Override
}
private final class AltData extends FilteredData {
- AltData(CldrData srcData) {
+ // Calculated per locale/data instance to make lokup as fast as possible.
+ private final ImmutableMap<CldrPath, CldrPath> altPaths;
+ // Any source paths which are not also target paths are removed. This is legacy
+ // behaviour inherited from the original build tools, the reason for which is not
+ // known. If it becomes desirable to retain the source values in their original
+ // locations, this can just be removed.
+ private final ImmutableSet<CldrPath> toRemove;
+
+ AltData(CldrData srcData, String localeId) {
super(srcData);
+ ImmutableMap<CldrPath, CldrPath> altPaths = globalAltPaths;
+ if (!localeAltPaths.row(localeId).isEmpty()) {
+ Map<CldrPath, CldrPath> combinedPaths = new HashMap<>();
+ // Locale specific path mappings overwrite global ones.
+ combinedPaths.putAll(globalAltPaths);
+ combinedPaths.putAll(localeAltPaths.row(localeId));
+ altPaths = ImmutableMap.copyOf(combinedPaths);
+ }
+ this.altPaths = altPaths;
+ this.toRemove = altPaths.values().stream()
+ .filter(p -> !this.altPaths.containsKey(p))
+ .collect(toImmutableSet());
}
@Override
return altValue.replacePath(value.getPath());
}
}
- return value;
+ return toRemove.contains(value.getPath()) ? null : value;
}
}
}
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
+import static com.google.common.collect.ImmutableTable.toImmutableTable;
+import static com.google.common.collect.Tables.immutableCell;
import static java.util.stream.Collectors.joining;
import static org.unicode.cldr.api.CldrPath.parseDistinguishingPath;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Iterables;
import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Table;
+import com.google.common.collect.Table.Cell;
+import com.google.common.collect.Tables;
// Note: Auto-magical Ant methods are listed as "unused" by IDEs, unless the warning is suppressed.
public final class ConvertIcuDataTask extends Task {
private final SetMultimap<IcuLocaleDir, String> perDirectoryIds = HashMultimap.create();
private final IcuConverterConfig.Builder config = IcuConverterConfig.builder();
// Don't try and resolve actual paths until inside the execute method.
- private final Map<String, String> altPathMap = new HashMap<>();
+ private final List<AltPath> altPaths = new ArrayList<>();
// TODO(CLDR-13381): Move into CLDR API; e.g. withPseudoLocales()
private boolean includePseudoLocales = false;
private Predicate<String> idFilter = id -> true;
public static final class AltPath extends Task {
private String source = "";
private String target = "";
+ private ImmutableSet<String> localeIds = ImmutableSet.of();
@SuppressWarnings("unused")
public void setTarget(String target) {
this.source = source.replace('\'', '"');
}
+ @SuppressWarnings("unused")
+ public void setLocales(String localeIds) {
+ this.localeIds = parseLocaleIds(localeIds);
+ }
+
@Override
public void init() throws BuildException {
checkBuild(!source.isEmpty(), "Source path not be empty");
// Don't convert to CldrPath here (it triggers a bunch of CLDR data loading for the DTDs).
// Wait until the "execute()" method since in future we expect to use the configured CLDR
// directory explicitly there.
- checkBuild(this.altPathMap.put(altPath.target, altPath.source) == null,
- "Duplicate <altPath> elements (same target): %s", altPath.target);
+ altPaths.add(altPath);
}
@SuppressWarnings("unused")
// We must do this wrapping of the data supplier _before_ creating the supplemental data
// instance since adding pseudo locales affects the set of available locales.
// TODO: Move some/all of this into the base converter and control it via the config.
- if (!altPathMap.isEmpty()) {
- Map<CldrPath, CldrPath> pathMap = new HashMap<>();
- altPathMap.forEach(
- (t, s) -> pathMap.put(parseDistinguishingPath(t), parseDistinguishingPath(s)));
- src = AlternateLocaleData.transform(src, pathMap);
+ if (!altPaths.isEmpty()) {
+ src = AlternateLocaleData.transform(src, getGlobalAltPaths(), getLocaleAltPaths());
}
if (includePseudoLocales) {
src = PseudoLocales.addPseudoLocalesTo(src);
LdmlConverter.convert(src, supplementalData, config.build());
}
+ private ImmutableMap<CldrPath, CldrPath> getGlobalAltPaths() {
+ // This fails if the same key appears more than once.
+ return altPaths.stream()
+ .filter(a -> a.localeIds.isEmpty())
+ .collect(toImmutableMap(
+ a -> parseDistinguishingPath(a.target),
+ a -> parseDistinguishingPath(a.source)));
+ }
+
+ private ImmutableTable<String, CldrPath, CldrPath> getLocaleAltPaths() {
+ return altPaths.stream()
+ .flatMap(
+ a -> a.localeIds.stream().map(
+ id -> immutableCell(
+ id,
+ parseDistinguishingPath(a.target),
+ parseDistinguishingPath(a.source))))
+ // Weirdly there's no collector method to just collect cells.
+ .collect(toImmutableTable(Cell::getRowKey, Cell::getColumnKey, Cell::getValue));
+ }
+
private static void checkBuild(boolean condition, String message, Object... args) {
if (!condition) {
throw new BuildException(String.format(message, args));