blob: 0bde19ed553f353845ab9f8697cf6b8b9c2b649b [file] [log] [blame]
package org.unicode.cldr.util;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.unicode.cldr.util.DayPeriodInfo.DayPeriod;
import org.unicode.cldr.util.PluralRulesUtil.KeywordStatus;
import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo;
import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count;
import org.unicode.cldr.util.SupplementalDataInfo.PluralType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.ibm.icu.text.PluralRules;
public class LogicalGrouping {
public static final ImmutableSet<String> metazonesDSTSet = ImmutableSet.of(
"Acre", "Africa_Western", "Alaska", "Almaty", "Amazon",
"America_Central", "America_Eastern", "America_Mountain", "America_Pacific", "Anadyr", "Apia",
"Aqtau", "Aqtobe", "Arabian", "Argentina", "Argentina_Western", "Armenia",
"Atlantic", "Australia_Central", "Australia_CentralWestern", "Australia_Eastern", "Australia_Western",
"Azerbaijan", "Azores", "Bangladesh", "Brasilia", "Cape_Verde",
"Chatham", "Chile", "China", "Choibalsan", "Colombia", "Cook", "Cuba", "Easter",
"Europe_Central", "Europe_Eastern", "Europe_Western", "Falkland", "Fiji", "Georgia",
"Greenland_Eastern", "Greenland_Western", "Hawaii_Aleutian", "Hong_Kong", "Hovd",
"Iran", "Irkutsk", "Israel", "Japan", "Kamchatka", "Korea", "Krasnoyarsk",
"Lord_Howe", "Macau", "Magadan", "Mauritius", "Mexico_Northwest", "Mexico_Pacific", "Mongolia", "Moscow", "New_Caledonia",
"New_Zealand", "Newfoundland", "Noronha", "Novosibirsk", "Omsk", "Pakistan", "Paraguay", "Peru", "Philippines",
"Pierre_Miquelon", "Qyzylorda", "Sakhalin", "Samara", "Samoa",
"Taipei", "Tonga", "Turkmenistan", "Uruguay", "Uzbekistan",
"Vanuatu", "Vladivostok", "Volgograd", "Yakutsk", "Yekaterinburg");
public static final ImmutableList<String> days = ImmutableList.of("sun", "mon", "tue", "wed", "thu", "fri", "sat");
public static final ImmutableSet<String> calendarsWith13Months = ImmutableSet.of("coptic", "ethiopic", "hebrew");
public static final ImmutableSet<String> compactDecimalFormatLengths = ImmutableSet.of("short", "long");
private static final ImmutableSet<String> ampm = ImmutableSet.of("am", "pm");
private static final ImmutableSet<String> nowUnits = ImmutableSet.of("second", "second-short", "second-narrow",
"minute", "minute-short", "minute-narrow", "hour", "hour-short", "hour-narrow");
/**
* Return the set of paths that are in the same logical set as the given path
*
* @param path
* - the distinguishing xpath
*/
public static Set<String> getPaths(CLDRFile cldrFile, String path) {
ImmutableSet<String> metazone_string_types = ImmutableSet.of("generic", "standard", "daylight");
Set<String> result = new TreeSet<String>();
if (path == null) return result;
result.add(path);
// Figure out the plurals forms, as we will probably need them.
XPathParts parts = new XPathParts();
parts.set(path);
if (path.indexOf("/metazone") > 0) {
String metazoneName = parts.getAttributeValue(3, "type");
if (metazonesDSTSet.contains(metazoneName)) {
for (String str : metazone_string_types) {
result.add(path.substring(0, path.lastIndexOf('/') + 1) + str);
}
}
} else if (path.indexOf("/days") > 0) {
String dayName = parts.size() > 7 ? parts.getAttributeValue(7, "type") : null;
if (dayName != null && days.contains(dayName)) { // This is just a quick check to make sure the path is
// good.
for (String str : days) {
parts.setAttribute("day", "type", str);
result.add(parts.toString());
}
}
} else if (path.indexOf("/dayPeriods") > 0) {
if (path.endsWith("alias")) {
result.add(path);
} else {
String dayPeriodType = parts.findAttributeValue("dayPeriod", "type");
if (ampm.contains(dayPeriodType)) {
for (String s : ampm) {
parts.setAttribute("dayPeriod", "type", s);
result.add(parts.toString());
}
} else {
SupplementalDataInfo supplementalData = SupplementalDataInfo.getInstance(
cldrFile.getSupplementalDirectory());
DayPeriodInfo.Type dayPeriodContext = DayPeriodInfo.Type.fromString(parts.findAttributeValue("dayPeriodContext", "type"));
DayPeriodInfo dpi = supplementalData.getDayPeriods(dayPeriodContext, cldrFile.getLocaleID());
List<DayPeriod> dayPeriods = dpi.getPeriods();
DayPeriod thisDayPeriod = DayPeriod.fromString(dayPeriodType);
if (dayPeriods.contains(thisDayPeriod)) {
for (DayPeriod d : dayPeriods) {
parts.setAttribute("dayPeriod", "type", d.name());
result.add(parts.toString());
}
}
}
}
} else if (path.indexOf("/quarters") > 0) {
String quarterName = parts.size() > 7 ? parts.getAttributeValue(7, "type") : null;
Integer quarter = quarterName == null ? 0 : Integer.valueOf(quarterName);
if (quarter > 0 && quarter <= 4) { // This is just a quick check to make sure the path is good.
for (Integer i = 1; i <= 4; i++) {
parts.setAttribute("quarter", "type", i.toString());
result.add(parts.toString());
}
}
} else if (path.indexOf("/months") > 0) {
String calType = parts.size() > 3 ? parts.getAttributeValue(3, "type") : null;
String monthName = parts.size() > 7 ? parts.getAttributeValue(7, "type") : null;
Integer month = monthName == null ? 0 : Integer.valueOf(monthName);
int calendarMonthMax = calendarsWith13Months.contains(calType) ? 13 : 12;
if (month > 0 && month <= calendarMonthMax) { // This is just a quick check to make sure the path is good.
for (Integer i = 1; i <= calendarMonthMax; i++) {
parts.setAttribute("month", "type", i.toString());
if ("hebrew".equals(calType)) {
parts.removeAttribute("month", "yeartype");
}
result.add(parts.toString());
}
if ("hebrew".equals(calType)) { // Add extra hebrew calendar leap month
parts.setAttribute("month", "type", Integer.toString(7));
parts.setAttribute("month", "yeartype", "leap");
result.add(parts.toString());
}
}
} else if (parts.containsElement("relative")) {
String fieldType = parts.findAttributeValue("field", "type");
String relativeType = parts.findAttributeValue("relative", "type");
Integer relativeValue = relativeType == null ? 999 : Integer.valueOf(relativeType);
if (relativeValue >= -3 && relativeValue <= 3) { // This is just a quick check to make sure the path is good.
if (!(nowUnits.contains(fieldType) && relativeValue == 0)) { // Workaround for "now", "this hour", "this minute"
int limit = 1;
if (fieldType != null && fieldType.startsWith("day")) {
limit = 3;
}
for (Integer i = -1*limit; i <= limit; i++) {
parts.setAttribute("relative", "type", i.toString());
result.add(parts.toString());
}
}
}
} else if (path.indexOf("/decimalFormatLength") > 0) {
PluralInfo pluralInfo = getPluralInfo(cldrFile);
Set<Count> pluralTypes = pluralInfo.getCounts();
String decimalFormatLengthType = parts.size() > 3 ? parts.getAttributeValue(3, "type") : null;
String decimalFormatPatternType = parts.size() > 5 ? parts.getAttributeValue(5, "type") : null;
if (decimalFormatLengthType != null && decimalFormatPatternType != null &&
compactDecimalFormatLengths.contains(decimalFormatLengthType)) {
int numZeroes = decimalFormatPatternType.length() - 1;
int baseZeroes = (numZeroes / 3) * 3;
for (int i = 0; i < 3; i++) {
String patType = "1" + String.format(String.format("%%0%dd", baseZeroes + i), 0); // This gives us "baseZeroes+i" zeroes at the end.
parts.setAttribute(5, "type", patType);
for (Count count : pluralTypes) {
parts.setAttribute(5, "count", count.toString());
result.add(parts.toString());
}
}
}
} else if (path.indexOf("[@count=") > 0) {
PluralInfo pluralInfo = getPluralInfo(cldrFile);
Set<Count> pluralTypes = pluralInfo.getCounts();
String lastElement = parts.getElement(-1);
for (Count count : pluralTypes) {
parts.setAttribute(lastElement, "count", count.toString());
result.add(parts.toString());
}
}
return result;
}
/**
* Returns the plural info for a given locale.
*/
private static PluralInfo getPluralInfo(CLDRFile cldrFile) {
SupplementalDataInfo supplementalData = SupplementalDataInfo.getInstance(
cldrFile.getSupplementalDirectory());
return supplementalData.getPlurals(PluralType.cardinal,
cldrFile.getLocaleID());
}
/**
* @param cldrFile
* @param path
* @return true if the specified path is optional in the logical grouping
* that it belongs to.
*/
public static boolean isOptional(CLDRFile cldrFile, String path) {
XPathParts parts = new XPathParts().set(path);
if (parts.containsElement("relative")) {
String fieldType = parts.findAttributeValue("field", "type");
String relativeType = parts.findAttributeValue("relative", "type");
Integer relativeValue = relativeType == null ? 999 : Integer.valueOf(relativeType);
if (fieldType != null && fieldType.startsWith("day") && Math.abs(relativeValue.intValue()) >= 2) {
return true; // relative days +2 +3 -2 -3 are optional in a logical group.
}
}
// Paths with count="(zero|one)" are optional if their usage is covered
// fully by paths with count="(0|1)", which are always optional themselves.
if (!path.contains("[@count=")) return false;
String pluralType = parts.getAttributeValue(-1, "count");
if (pluralType.equals("0") || pluralType.equals("1")) return true;
if (!pluralType.equals("zero") && !pluralType.equals("one")) return false;
PluralRules pluralRules = getPluralInfo(cldrFile).getPluralRules();
parts.setAttribute(-1, "count", "0");
Set<Double> explicits = new HashSet<Double>();
if (cldrFile.isHere(parts.toString())) {
explicits.add(0.0);
}
parts.setAttribute(-1, "count", "1");
if (cldrFile.isHere(parts.toString())) {
explicits.add(1.0);
}
if (!explicits.isEmpty()) {
// HACK: The com.ibm.icu.text prefix is needed so that ST can find it
// (no idea why).
KeywordStatus status = org.unicode.cldr.util.PluralRulesUtil.getKeywordStatus(
pluralRules, pluralType, 0, explicits, true);
if (status == KeywordStatus.SUPPRESSED) {
return true;
}
}
return false;
}
}