Home | History | Annotate | Download | only in util
      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      */
     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      */
    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      */
    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