• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.unicode.cldr.util;
2 
3 import java.util.HashSet;
4 import java.util.List;
5 import java.util.Set;
6 import java.util.TreeSet;
7 
8 import org.unicode.cldr.util.DayPeriodInfo.DayPeriod;
9 import org.unicode.cldr.util.PluralRulesUtil.KeywordStatus;
10 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo;
11 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count;
12 import org.unicode.cldr.util.SupplementalDataInfo.PluralType;
13 
14 import com.google.common.collect.ImmutableList;
15 import com.google.common.collect.ImmutableSet;
16 import com.ibm.icu.text.PluralRules;
17 
18 public class LogicalGrouping {
19 
20     public static final ImmutableSet<String> metazonesDSTSet = ImmutableSet.of(
21         "Acre", "Africa_Western", "Alaska", "Almaty", "Amazon",
22         "America_Central", "America_Eastern", "America_Mountain", "America_Pacific", "Anadyr", "Apia",
23         "Aqtau", "Aqtobe", "Arabian", "Argentina", "Argentina_Western", "Armenia",
24         "Atlantic", "Australia_Central", "Australia_CentralWestern", "Australia_Eastern", "Australia_Western",
25         "Azerbaijan", "Azores", "Bangladesh", "Brasilia", "Cape_Verde",
26         "Chatham", "Chile", "China", "Choibalsan", "Colombia", "Cook", "Cuba", "Easter",
27         "Europe_Central", "Europe_Eastern", "Europe_Western", "Falkland", "Fiji", "Georgia",
28         "Greenland_Eastern", "Greenland_Western", "Hawaii_Aleutian", "Hong_Kong", "Hovd",
29         "Iran", "Irkutsk", "Israel", "Japan", "Kamchatka", "Korea", "Krasnoyarsk",
30         "Lord_Howe", "Macau", "Magadan", "Mauritius", "Mexico_Northwest", "Mexico_Pacific", "Mongolia", "Moscow", "New_Caledonia",
31         "New_Zealand", "Newfoundland", "Noronha", "Novosibirsk", "Omsk", "Pakistan", "Paraguay", "Peru", "Philippines",
32         "Pierre_Miquelon", "Qyzylorda", "Sakhalin", "Samara", "Samoa",
33         "Taipei", "Tonga", "Turkmenistan", "Uruguay", "Uzbekistan",
34         "Vanuatu", "Vladivostok", "Volgograd", "Yakutsk", "Yekaterinburg");
35 
36     public static final ImmutableList<String> days = ImmutableList.of("sun", "mon", "tue", "wed", "thu", "fri", "sat");
37 
38     public static final ImmutableSet<String> calendarsWith13Months = ImmutableSet.of("coptic", "ethiopic", "hebrew");
39     public static final ImmutableSet<String> compactDecimalFormatLengths = ImmutableSet.of("short", "long");
40     private static final ImmutableSet<String> ampm = ImmutableSet.of("am", "pm");
41     private static final ImmutableSet<String> nowUnits = ImmutableSet.of("second", "second-short", "second-narrow",
42         "minute", "minute-short", "minute-narrow", "hour", "hour-short", "hour-narrow");
43 
44     /**
45      * Return the set of paths that are in the same logical set as the given path
46      *
47      * @param path
48      *            - the distinguishing xpath
49      */
getPaths(CLDRFile cldrFile, String path)50     public static Set<String> getPaths(CLDRFile cldrFile, String path) {
51         ImmutableSet<String> metazone_string_types = ImmutableSet.of("generic", "standard", "daylight");
52 
53         Set<String> result = new TreeSet<String>();
54         if (path == null) return result;
55         result.add(path);
56         // Figure out the plurals forms, as we will probably need them.
57 
58         XPathParts parts = new XPathParts();
59         parts.set(path);
60 
61         if (path.indexOf("/metazone") > 0) {
62             String metazoneName = parts.getAttributeValue(3, "type");
63             if (metazonesDSTSet.contains(metazoneName)) {
64                 for (String str : metazone_string_types) {
65                     result.add(path.substring(0, path.lastIndexOf('/') + 1) + str);
66                 }
67             }
68         } else if (path.indexOf("/days") > 0) {
69             String dayName = parts.size() > 7 ? parts.getAttributeValue(7, "type") : null;
70             if (dayName != null && days.contains(dayName)) { // This is just a quick check to make sure the path is
71                                                              // good.
72                 for (String str : days) {
73                     parts.setAttribute("day", "type", str);
74                     result.add(parts.toString());
75                 }
76             }
77         } else if (path.indexOf("/dayPeriods") > 0) {
78             if (path.endsWith("alias")) {
79                 result.add(path);
80             } else {
81                 String dayPeriodType = parts.findAttributeValue("dayPeriod", "type");
82 
83                 if (ampm.contains(dayPeriodType)) {
84                     for (String s : ampm) {
85                         parts.setAttribute("dayPeriod", "type", s);
86                         result.add(parts.toString());
87                     }
88                 } else {
89                     SupplementalDataInfo supplementalData = SupplementalDataInfo.getInstance(
90                         cldrFile.getSupplementalDirectory());
91                     DayPeriodInfo.Type dayPeriodContext = DayPeriodInfo.Type.fromString(parts.findAttributeValue("dayPeriodContext", "type"));
92                     DayPeriodInfo dpi = supplementalData.getDayPeriods(dayPeriodContext, cldrFile.getLocaleID());
93                     List<DayPeriod> dayPeriods = dpi.getPeriods();
94                     DayPeriod thisDayPeriod = DayPeriod.fromString(dayPeriodType);
95                     if (dayPeriods.contains(thisDayPeriod)) {
96                         for (DayPeriod d : dayPeriods) {
97                             parts.setAttribute("dayPeriod", "type", d.name());
98                             result.add(parts.toString());
99                         }
100                     }
101                 }
102             }
103         } else if (path.indexOf("/quarters") > 0) {
104             String quarterName = parts.size() > 7 ? parts.getAttributeValue(7, "type") : null;
105             Integer quarter = quarterName == null ? 0 : Integer.valueOf(quarterName);
106             if (quarter > 0 && quarter <= 4) { // This is just a quick check to make sure the path is good.
107                 for (Integer i = 1; i <= 4; i++) {
108                     parts.setAttribute("quarter", "type", i.toString());
109                     result.add(parts.toString());
110                 }
111             }
112         } else if (path.indexOf("/months") > 0) {
113             String calType = parts.size() > 3 ? parts.getAttributeValue(3, "type") : null;
114             String monthName = parts.size() > 7 ? parts.getAttributeValue(7, "type") : null;
115             Integer month = monthName == null ? 0 : Integer.valueOf(monthName);
116             int calendarMonthMax = calendarsWith13Months.contains(calType) ? 13 : 12;
117             if (month > 0 && month <= calendarMonthMax) { // This is just a quick check to make sure the path is good.
118                 for (Integer i = 1; i <= calendarMonthMax; i++) {
119                     parts.setAttribute("month", "type", i.toString());
120                     if ("hebrew".equals(calType)) {
121                         parts.removeAttribute("month", "yeartype");
122                     }
123                     result.add(parts.toString());
124                 }
125                 if ("hebrew".equals(calType)) { // Add extra hebrew calendar leap month
126                     parts.setAttribute("month", "type", Integer.toString(7));
127                     parts.setAttribute("month", "yeartype", "leap");
128                     result.add(parts.toString());
129                 }
130             }
131         } else if (parts.containsElement("relative")) {
132             String fieldType = parts.findAttributeValue("field", "type");
133             String relativeType = parts.findAttributeValue("relative", "type");
134             Integer relativeValue = relativeType == null ? 999 : Integer.valueOf(relativeType);
135             if (relativeValue >= -3 && relativeValue <= 3) { // This is just a quick check to make sure the path is good.
136                 if (!(nowUnits.contains(fieldType) && relativeValue == 0)) { // Workaround for "now", "this hour", "this minute"
137                     int limit = 1;
138                     if (fieldType != null && fieldType.startsWith("day")) {
139                         limit = 3;
140                     }
141                     for (Integer i = -1 * limit; i <= limit; i++) {
142                         parts.setAttribute("relative", "type", i.toString());
143                         result.add(parts.toString());
144                     }
145                 }
146             }
147         } else if (path.indexOf("/decimalFormatLength") > 0) {
148             PluralInfo pluralInfo = getPluralInfo(cldrFile);
149             Set<Count> pluralTypes = pluralInfo.getCounts();
150             String decimalFormatLengthType = parts.size() > 3 ? parts.getAttributeValue(3, "type") : null;
151             String decimalFormatPatternType = parts.size() > 5 ? parts.getAttributeValue(5, "type") : null;
152             if (decimalFormatLengthType != null && decimalFormatPatternType != null &&
153                 compactDecimalFormatLengths.contains(decimalFormatLengthType)) {
154                 int numZeroes = decimalFormatPatternType.length() - 1;
155                 int baseZeroes = (numZeroes / 3) * 3;
156                 for (int i = 0; i < 3; i++) {
157                     String patType = "1" + String.format(String.format("%%0%dd", baseZeroes + i), 0); // This gives us "baseZeroes+i" zeroes at the end.
158                     parts.setAttribute(5, "type", patType);
159                     for (Count count : pluralTypes) {
160                         parts.setAttribute(5, "count", count.toString());
161                         result.add(parts.toString());
162                     }
163                 }
164             }
165         } else if (path.indexOf("[@count=") > 0) {
166             PluralInfo pluralInfo = getPluralInfo(cldrFile);
167             Set<Count> pluralTypes = pluralInfo.getCounts();
168             String lastElement = parts.getElement(-1);
169             for (Count count : pluralTypes) {
170                 parts.setAttribute(lastElement, "count", count.toString());
171                 result.add(parts.toString());
172             }
173         }
174         return result;
175     }
176 
177     /**
178      * Returns the plural info for a given locale.
179      */
getPluralInfo(CLDRFile cldrFile)180     private static PluralInfo getPluralInfo(CLDRFile cldrFile) {
181         SupplementalDataInfo supplementalData = SupplementalDataInfo.getInstance(
182             cldrFile.getSupplementalDirectory());
183         return supplementalData.getPlurals(PluralType.cardinal,
184             cldrFile.getLocaleID());
185     }
186 
187     /**
188      * @param cldrFile
189      * @param path
190      * @return true if the specified path is optional in the logical grouping
191      *         that it belongs to.
192      */
isOptional(CLDRFile cldrFile, String path)193     public static boolean isOptional(CLDRFile cldrFile, String path) {
194         XPathParts parts = new XPathParts().set(path);
195 
196         if (parts.containsElement("relative")) {
197             String fieldType = parts.findAttributeValue("field", "type");
198             String relativeType = parts.findAttributeValue("relative", "type");
199             Integer relativeValue = relativeType == null ? 999 : Integer.valueOf(relativeType);
200             if (fieldType != null && fieldType.startsWith("day") && Math.abs(relativeValue.intValue()) >= 2) {
201                 return true; // relative days +2 +3 -2 -3 are optional in a logical group.
202             }
203         }
204         // Paths with count="(zero|one)" are optional if their usage is covered
205         // fully by paths with count="(0|1)", which are always optional themselves.
206         if (!path.contains("[@count=")) return false;
207         String pluralType = parts.getAttributeValue(-1, "count");
208         if (pluralType.equals("0") || pluralType.equals("1")) return true;
209         if (!pluralType.equals("zero") && !pluralType.equals("one")) return false;
210 
211         PluralRules pluralRules = getPluralInfo(cldrFile).getPluralRules();
212         parts.setAttribute(-1, "count", "0");
213         Set<Double> explicits = new HashSet<Double>();
214         if (cldrFile.isHere(parts.toString())) {
215             explicits.add(0.0);
216         }
217         parts.setAttribute(-1, "count", "1");
218         if (cldrFile.isHere(parts.toString())) {
219             explicits.add(1.0);
220         }
221         if (!explicits.isEmpty()) {
222             // HACK: The com.ibm.icu.text prefix is needed so that ST can find it
223             // (no idea why).
224             KeywordStatus status = org.unicode.cldr.util.PluralRulesUtil.getKeywordStatus(
225                 pluralRules, pluralType, 0, explicits, true);
226             if (status == KeywordStatus.SUPPRESSED) {
227                 return true;
228             }
229         }
230         return false;
231     }
232 }
233