Home | History | Annotate | Download | only in test
      1 package org.unicode.cldr.test;
      2 
      3 import java.io.PrintWriter;
      4 import java.io.StringWriter;
      5 import java.text.ChoiceFormat;
      6 import java.util.ArrayList;
      7 import java.util.Arrays;
      8 import java.util.BitSet;
      9 import java.util.Collection;
     10 import java.util.Date;
     11 import java.util.HashMap;
     12 import java.util.HashSet;
     13 import java.util.LinkedHashSet;
     14 import java.util.List;
     15 import java.util.Locale;
     16 import java.util.Map;
     17 import java.util.Set;
     18 import java.util.regex.Matcher;
     19 import java.util.regex.Pattern;
     20 
     21 import org.unicode.cldr.tool.CLDRFileTransformer;
     22 import org.unicode.cldr.tool.CLDRFileTransformer.LocaleTransform;
     23 import org.unicode.cldr.tool.LikelySubtags;
     24 import org.unicode.cldr.util.CLDRConfig;
     25 import org.unicode.cldr.util.CLDRFile;
     26 import org.unicode.cldr.util.CLDRLocale;
     27 import org.unicode.cldr.util.CLDRPaths;
     28 import org.unicode.cldr.util.CldrUtility;
     29 import org.unicode.cldr.util.DayPeriodInfo;
     30 import org.unicode.cldr.util.DayPeriodInfo.DayPeriod;
     31 import org.unicode.cldr.util.EmojiConstants;
     32 import org.unicode.cldr.util.Factory;
     33 import org.unicode.cldr.util.ICUServiceBuilder;
     34 import org.unicode.cldr.util.LanguageTagParser;
     35 import org.unicode.cldr.util.Level;
     36 import org.unicode.cldr.util.PathDescription;
     37 import org.unicode.cldr.util.PatternCache;
     38 import org.unicode.cldr.util.PluralSamples;
     39 import org.unicode.cldr.util.SupplementalDataInfo;
     40 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo;
     41 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count;
     42 import org.unicode.cldr.util.SupplementalDataInfo.PluralType;
     43 import org.unicode.cldr.util.TimezoneFormatter;
     44 import org.unicode.cldr.util.TransliteratorUtilities;
     45 import org.unicode.cldr.util.XListFormatter.ListTypeLength;
     46 import org.unicode.cldr.util.XPathParts;
     47 
     48 import com.ibm.icu.impl.Row.R3;
     49 import com.ibm.icu.impl.Utility;
     50 import com.ibm.icu.text.BreakIterator;
     51 import com.ibm.icu.text.DateFormat;
     52 import com.ibm.icu.text.DateFormatSymbols;
     53 import com.ibm.icu.text.DateTimePatternGenerator;
     54 import com.ibm.icu.text.DecimalFormat;
     55 import com.ibm.icu.text.DecimalFormatSymbols;
     56 import com.ibm.icu.text.ListFormatter;
     57 import com.ibm.icu.text.MessageFormat;
     58 import com.ibm.icu.text.NumberFormat;
     59 import com.ibm.icu.text.PluralRules;
     60 import com.ibm.icu.text.PluralRules.FixedDecimal;
     61 import com.ibm.icu.text.PluralRules.FixedDecimalRange;
     62 import com.ibm.icu.text.PluralRules.FixedDecimalSamples;
     63 import com.ibm.icu.text.PluralRules.SampleType;
     64 import com.ibm.icu.text.SimpleDateFormat;
     65 import com.ibm.icu.text.SimpleFormatter;
     66 import com.ibm.icu.text.Transliterator;
     67 import com.ibm.icu.text.UTF16;
     68 import com.ibm.icu.util.Calendar;
     69 import com.ibm.icu.util.TimeZone;
     70 import com.ibm.icu.util.ULocale;
     71 
     72 /**
     73  * Class to generate examples and help messages for the Survey tool (or console version).
     74  *
     75  * @author markdavis
     76  *
     77  */
     78 public class ExampleGenerator {
     79     private static final CLDRConfig CONFIG = CLDRConfig.getInstance();
     80 
     81     private static final String ALT_STAND_ALONE = "[@alt=\"stand-alone\"]";
     82 
     83     private static final String EXEMPLAR_CITY_LOS_ANGELES = "//ldml/dates/timeZoneNames/zone[@type=\"America/Los_Angeles\"]/exemplarCity";
     84 
     85     private static final boolean SHOW_ERROR = false;
     86 
     87     private static final Pattern URL_PATTERN = Pattern
     88         .compile("http://[\\-a-zA-Z0-9]+(\\.[\\-a-zA-Z0-9]+)*([/#][\\-a-zA-Z0-9]+)*");
     89 
     90     final static boolean DEBUG_SHOW_HELP = false;
     91 
     92     private static SupplementalDataInfo supplementalDataInfo;
     93     private PathDescription pathDescription;
     94 
     95     private final static boolean CACHING = false;
     96 
     97     public final static double NUMBER_SAMPLE = 123456.789;
     98     public final static double NUMBER_SAMPLE_WHOLE = 2345;
     99 
    100     public final static TimeZone ZONE_SAMPLE = TimeZone.getTimeZone("America/Indianapolis");
    101     public final static TimeZone GMT_ZONE_SAMPLE = TimeZone.getTimeZone("Etc/GMT");
    102 
    103     public final static Date DATE_SAMPLE;
    104 
    105     private final static Date DATE_SAMPLE2;
    106     private final static Date DATE_SAMPLE3;
    107     private final static Date DATE_SAMPLE4;
    108 
    109     // private final static String EXEMPLAR_CITY = "Europe/Rome";
    110 
    111     private String backgroundStart = "<span class='cldr_substituted'>";
    112     private String backgroundEnd = "</span>";
    113 
    114     private static final String exampleStart = "<div class='cldr_example'>";
    115     private static final String exampleEnd = "</div>";
    116     private static final String startItalic = "<i>";
    117     private static final String endItalic = "</i>";
    118     private static final String startSup = "<sup>";
    119     private static final String endSup = "</sup>";
    120 
    121     private static final String backgroundStartSymbol = "\uE234";
    122     private static final String backgroundEndSymbol = "\uE235";
    123     private static final String backgroundTempSymbol = "\uE236";
    124     private static final String exampleSeparatorSymbol = "\uE237";
    125     private static final String startItalicSymbol = "\uE238";
    126     private static final String endItalicSymbol = "\uE239";
    127     private static final String startSupSymbol = "\uE23A";
    128     private static final String endSupSymbol = "\uE23B";
    129 
    130     private boolean verboseErrors = false;
    131 
    132     private Calendar calendar = Calendar.getInstance(ZONE_SAMPLE, ULocale.ENGLISH);
    133 
    134     static {
    135         Calendar calendar = Calendar.getInstance(ZONE_SAMPLE, ULocale.ENGLISH);
    136         calendar.set(1999, 8, 5, 13, 25, 59); // 1999-08-05 13:25:59
    137         DATE_SAMPLE = calendar.getTime();
    138         calendar.set(1999, 9, 27, 13, 25, 59); // 1999-09-27 13:25:59
    139         DATE_SAMPLE2 = calendar.getTime();
    140 
    141         calendar.set(1999, 8, 5, 7, 0, 0); // 1999-08-5 07:00:00
    142         DATE_SAMPLE3 = calendar.getTime();
    143         calendar.set(1999, 8, 5, 23, 0, 0); // 1999-08-5 23:00:00
    144         DATE_SAMPLE4 = calendar.getTime();
    145     }
    146 
    147     private CLDRFile cldrFile;
    148 
    149     public CLDRFile getCldrFile() {
    150         return cldrFile;
    151     }
    152 
    153     private CLDRFile englishFile;
    154     Matcher URLMatcher = URL_PATTERN.matcher("");
    155 
    156     private Map<String, String> cache = new HashMap<String, String>();
    157 
    158     private static final String NONE = "\uFFFF";
    159 
    160     // Matcher skipMatcher = PatternCache.get(
    161     // "/localeDisplayNames(?!"
    162     // ).matcher("");
    163     private XPathParts parts = new XPathParts();
    164 
    165     private ICUServiceBuilder icuServiceBuilder = new ICUServiceBuilder();
    166 
    167     private PluralInfo pluralInfo;
    168 
    169     private PluralSamples patternExamples;
    170 
    171     private Map<String, String> subdivisionIdToName;
    172 
    173     /**
    174      * For getting the end of the "background" style. Default is "</span>". It is
    175      * used in composing patterns, so it can show the part that corresponds to the
    176      * value.
    177      *
    178      * @return
    179      */
    180     public String getBackgroundEnd() {
    181         return backgroundEnd;
    182     }
    183 
    184     /**
    185      * For setting the end of the "background" style. Default is "</span>". It is
    186      * used in composing patterns, so it can show the part that corresponds to the
    187      * value.
    188      *
    189      * @return
    190      */
    191     public void setBackgroundEnd(String backgroundEnd) {
    192         this.backgroundEnd = backgroundEnd;
    193     }
    194 
    195     /**
    196      * For getting the "background" style. Default is "<span
    197      * style='background-color: gray'>". It is used in composing patterns, so it
    198      * can show the part that corresponds to the value.
    199      *
    200      * @return
    201      */
    202     public String getBackgroundStart() {
    203         return backgroundStart;
    204     }
    205 
    206     /**
    207      * For setting the "background" style. Default is "<span
    208      * style='background-color: gray'>". It is used in composing patterns, so it
    209      * can show the part that corresponds to the value.
    210      *
    211      * @return
    212      */
    213     public void setBackgroundStart(String backgroundStart) {
    214         this.backgroundStart = backgroundStart;
    215     }
    216 
    217     /**
    218      * Set the verbosity level of internal errors.
    219      * For example, setVerboseErrors(true) will cause
    220      * full stack traces to be shown in some cases.
    221      */
    222     public void setVerboseErrors(boolean verbosity) {
    223         this.verboseErrors = verbosity;
    224     }
    225 
    226     /**
    227      * Create an Example Generator. If this is shared across threads, it must be synchronized.
    228      *
    229      * @param resolvedCldrFile
    230      * @param supplementalDataDirectory
    231      */
    232     public ExampleGenerator(CLDRFile resolvedCldrFile, CLDRFile englishFile, String supplementalDataDirectory) {
    233         if (!resolvedCldrFile.isResolved()) throw new IllegalArgumentException("CLDRFile must be resolved");
    234         if (!englishFile.isResolved()) throw new IllegalArgumentException("English CLDRFile must be resolved");
    235         cldrFile = resolvedCldrFile;
    236         subdivisionIdToName = EmojiSubdivisionNames.getSubdivisionIdToName(cldrFile.getLocaleID());
    237         this.englishFile = englishFile;
    238         synchronized (ExampleGenerator.class) {
    239             if (supplementalDataInfo == null) {
    240                 supplementalDataInfo = SupplementalDataInfo.getInstance(supplementalDataDirectory);
    241             }
    242         }
    243         icuServiceBuilder.setCldrFile(cldrFile);
    244 
    245         pluralInfo = supplementalDataInfo.getPlurals(PluralType.cardinal, cldrFile.getLocaleID());
    246     }
    247 
    248     public enum ExampleType {
    249         NATIVE, ENGLISH
    250     };
    251 
    252     public static class ExampleContext {
    253         private Collection<FixedDecimal> exampleCount;
    254 
    255         public void setExampleCount(Collection<FixedDecimal> exampleCount2) {
    256             this.exampleCount = exampleCount2;
    257         }
    258 
    259         public Collection<FixedDecimal> getExampleCount() {
    260             return exampleCount;
    261         }
    262     }
    263 
    264     public String getExampleHtml(String xpath, String value) {
    265         return getExampleHtml(xpath, value, null, null);
    266     }
    267 
    268     /**
    269      * Returns an example string, in html, if there is one for this path,
    270      * otherwise null. For use in the survey tool, an example might be returned
    271      * *even* if there is no value in the locale. For example, the locale might
    272      * have a path that Engish doesn't, but you want to return the best English
    273      * example. <br>
    274      * The result is valid HTML.
    275      *
    276      * @param xpath
    277      * @return
    278      */
    279     public String getExampleHtml(String xpath, String value, ExampleContext context, ExampleType type) {
    280         if (value == null) {
    281             return null;
    282         }
    283         String cacheKey;
    284         String result = null;
    285         try {
    286             if (CACHING) {
    287                 cacheKey = xpath + "," + value;
    288                 result = cache.get(cacheKey);
    289                 if (result != null) {
    290                     if (result == NONE) {
    291                         return null;
    292                     }
    293                     return result;
    294                 }
    295             }
    296             // If generating examples for an inheritance marker, then we need to find the
    297             // "real" value to generate from.
    298             if (CldrUtility.INHERITANCE_MARKER.equals(value)) {
    299                 if (type.equals(ExampleType.ENGLISH)) {
    300                     value = englishFile.getConstructedBaileyValue(xpath, null, null);
    301                 } else {
    302                     value = cldrFile.getConstructedBaileyValue(xpath, null, null);
    303                 }
    304             }
    305 
    306             // result is null at this point. Get the real value if we can.
    307             parts.set(xpath);
    308             if (parts.contains("dateRangePattern")) { // {0} - {1}
    309                 result = handleDateRangePattern(value, xpath);
    310             } else if (parts.contains("timeZoneNames")) {
    311                 result = handleTimeZoneName(xpath, value);
    312             } else if (parts.contains("localeDisplayNames")) {
    313                 result = handleDisplayNames(xpath, parts, value);
    314             } else if (parts.contains("currency")) {
    315                 result = handleCurrency(xpath, value, context, type);
    316             } else if (parts.contains("dayPeriods")) {
    317                 result = handleDayPeriod(xpath, value, context, type);
    318             } else if (parts.contains("pattern") || parts.contains("dateFormatItem")) {
    319                 if (parts.contains("calendar")) {
    320                     result = handleDateFormatItem(xpath, value);
    321                 } else if (parts.contains("miscPatterns")) {
    322                     result = handleMiscPatterns(parts, value);
    323                 } else if (parts.contains("numbers")) {
    324                     if (parts.contains("currencyFormat")) {
    325                         result = handleCurrencyFormat(parts, value, type);
    326                     } else {
    327                         result = handleDecimalFormat(parts, value, type);
    328                     }
    329                 }
    330             } else if (parts.getElement(2).contains("symbols")) {
    331                 result = handleNumberSymbol(parts, value);
    332             } else if (parts.contains("defaultNumberingSystem") || parts.contains("otherNumberingSystems")) {
    333                 result = handleNumberingSystem(value);
    334             } else if (parts.contains("currencyFormats") && parts.contains("unitPattern")) {
    335                 result = formatCountValue(xpath, parts, value, context, type);
    336             } else if (parts.getElement(-2).equals("compoundUnit")) {
    337                 String count = CldrUtility.ifNull(parts.getAttributeValue(-1, "count"), "other");
    338                 result = handleCompoundUnit(getUnitLength(), Count.valueOf(count), value);
    339             } else if (parts.getElement(-1).equals("unitPattern")) {
    340                 String count = parts.getAttributeValue(-1, "count");
    341                 result = handleFormatUnit(getUnitLength(), Count.valueOf(count), value);
    342             } else if (parts.getElement(-1).equals("durationUnitPattern")) {
    343                 result = handleDurationUnit(value);
    344             } else if (parts.contains("intervalFormats")) {
    345                 result = handleIntervalFormats(parts, xpath, value, context, type);
    346             } else if (parts.getElement(1).equals("delimiters")) {
    347                 result = handleDelimiters(parts, xpath, value);
    348             } else if (parts.getElement(1).equals("listPatterns")) {
    349                 result = handleListPatterns(parts, value);
    350             } else if (parts.getElement(2).equals("ellipsis")) {
    351                 result = handleEllipsis(parts.getAttributeValue(-1, "type"), value);
    352             } else if (parts.getElement(-1).equals("monthPattern")) {
    353                 result = handleMonthPatterns(parts, value);
    354             } else if (parts.getElement(-1).equals("appendItem")) {
    355                 result = handleAppendItems(parts, value);
    356             } else if (parts.getElement(-1).equals("annotation")) {
    357                 result = handleAnnotationName(parts, value);
    358             } else if (parts.getElement(-1).equals("characterLabel")) {
    359                 result = handleLabel(parts, value);
    360             } else if (parts.getElement(-1).equals("characterLabelPattern")) {
    361                 result = handleLabelPattern(parts, value);
    362             } else {
    363                 // didn't detect anything, return empty-handed
    364                 return null;
    365             }
    366         } catch (NullPointerException e) {
    367             if (SHOW_ERROR) {
    368                 e.printStackTrace();
    369             }
    370             return null;
    371         } catch (RuntimeException e) {
    372             String unchained = verboseErrors ? ("<br>" + finalizeBackground(unchainException(e))) : "";
    373             return "<i>Parsing error. " + finalizeBackground(e.getMessage()) + "</i>" + unchained;
    374         }
    375 
    376         String test = parts.getElement(-1);
    377 
    378         //add transliteration if one exists
    379         if (type == ExampleType.NATIVE && result != null) {
    380             result = addTransliteration(result, value);
    381         }
    382 
    383         result = finalizeBackground(result);
    384 
    385         if (CACHING) {
    386             if (result == null) {
    387                 cache.put(cacheKey, NONE);
    388             } else {
    389                 // fix HTML, cache
    390                 cache.put(cacheKey, result);
    391             }
    392         }
    393         return result;
    394     }
    395 
    396     private String handleLabelPattern(XPathParts parts2, String value) {
    397         switch (parts.getAttributeValue(-1, "type")) {
    398         case "category-list":
    399             List<String> examples = new ArrayList<>();
    400             CLDRFile cfile = getCldrFile();
    401             SimpleFormatter initialPattern = SimpleFormatter.compile(setBackground(value));
    402             String path = CLDRFile.getKey(CLDRFile.TERRITORY_NAME, "FR");
    403             String regionName = cfile.getStringValue(path);
    404             String flagName = cfile.getStringValue("//ldml/characterLabels/characterLabel[@type=\"flag\"]");
    405             examples.add(invertBackground(EmojiConstants.getEmojiFromRegionCodes("FR")
    406                 + "  " + initialPattern.format(flagName, regionName)));
    407             return formatExampleList(examples);
    408         default: return null;
    409         }
    410     }
    411 
    412     private String handleLabel(XPathParts parts2, String value) {
    413         // "//ldml/characterLabels/characterLabel[@type=\"" + typeAttributeValue + "\"]"
    414         switch (parts.getAttributeValue(-1, "type")) {
    415         case "flag": {
    416             String value2 = backgroundStartSymbol + value + backgroundEndSymbol;
    417             CLDRFile cfile = getCldrFile();
    418             List<String> examples = new ArrayList<>();
    419             SimpleFormatter initialPattern = SimpleFormatter.compile(cfile.getStringValue("//ldml/characterLabels/characterLabelPattern[@type=\"category-list\"]"));
    420             addFlag(value2, "FR", cfile, initialPattern, examples);
    421             addFlag(value2, "CN", cfile, initialPattern, examples);
    422             addSubdivisionFlag(value2, "gbeng", cfile, initialPattern, examples);
    423             addSubdivisionFlag(value2, "gbsct", cfile, initialPattern, examples);
    424             addSubdivisionFlag(value2, "gbwls", cfile, initialPattern, examples);
    425             return formatExampleList(examples);
    426         }
    427         case "keycap": {
    428             String value2 = backgroundStartSymbol + value + backgroundEndSymbol;
    429             List<String> examples = new ArrayList<>();
    430             CLDRFile cfile = getCldrFile();
    431             SimpleFormatter initialPattern = SimpleFormatter.compile(cfile.getStringValue("//ldml/characterLabels/characterLabelPattern[@type=\"category-list\"]"));
    432             examples.add(invertBackground(initialPattern.format(value2, "1")));
    433             examples.add(invertBackground(initialPattern.format(value2, "10")));
    434             examples.add(invertBackground(initialPattern.format(value2, "#")));
    435             return formatExampleList(examples);
    436         }
    437         default:
    438             return null;
    439         }
    440     }
    441 
    442     private void addFlag(String value2, String isoRegionCode, CLDRFile cfile, SimpleFormatter initialPattern, List<String> examples) {
    443         String path = CLDRFile.getKey(CLDRFile.TERRITORY_NAME, isoRegionCode);
    444         String regionName = cfile.getStringValue(path);
    445         examples.add(invertBackground(EmojiConstants.getEmojiFromRegionCodes(isoRegionCode)
    446             + "  " + initialPattern.format(value2, regionName)));
    447     }
    448 
    449     private void addSubdivisionFlag(String value2, String isoSubdivisionCode, CLDRFile cfile, SimpleFormatter initialPattern, List<String> examples) {
    450         String subdivisionName = subdivisionIdToName.get(isoSubdivisionCode);
    451         if (subdivisionName == null) {
    452             subdivisionName = isoSubdivisionCode;
    453         }
    454         examples.add(invertBackground(EmojiConstants.getEmojiFromSubdivisionCodes(isoSubdivisionCode)
    455             + "  " + initialPattern.format(value2, subdivisionName)));
    456     }
    457 
    458     private String handleAnnotationName(XPathParts parts, String value) {
    459         //ldml/annotations/annotation[@cp=""][@type="tts"]
    460         // skip anything but the name
    461         if (!"tts".equals(parts.getAttributeValue(-1, "type"))) {
    462             return null;
    463         }
    464         String cp = parts.getAttributeValue(-1, "cp");
    465         if (cp == null || cp.isEmpty()) {
    466             return null;
    467         }
    468         Set<String> examples = new LinkedHashSet<>();
    469         int first = cp.codePointAt(0);
    470         switch(first) {
    471         case 0x1F46A: //   U+1F46A FAMILY
    472             examples.add(formatGroup(parts, value, "", "", "", "", ""));
    473             examples.add(formatGroup(parts, value, "", "", "", ""));
    474             break;
    475         case 0x1F48F: //   U+1F48F KISS 
    476             examples.add(formatGroup(parts, value, "", "", ""));
    477             examples.add(formatGroup(parts, value, "", "", ""));
    478            break;
    479         case 0x1F491: //   U+1F491     COUPLE WITH HEART
    480             examples.add(formatGroup(parts, value, "", "", ""));
    481             examples.add(formatGroup(parts, value, "", "", ""));
    482             break;
    483         default:
    484             boolean isSkin = EmojiConstants.MODIFIERS.contains(first);
    485             if (isSkin || EmojiConstants.HAIR.contains(first)) {
    486                 String value2 = backgroundStartSymbol + value + backgroundEndSymbol;
    487                 CLDRFile cfile = getCldrFile();
    488                 String skin = "";
    489                 String hair = "";
    490                 String skinName = getEmojiName(cfile, skin);
    491                 String hairName = getEmojiName(cfile, hair);
    492                 if (hairName == null) {
    493                     hair = "[missing]";
    494                 }
    495                 SimpleFormatter initialPattern = SimpleFormatter.compile(cfile.getStringValue("//ldml/characterLabels/characterLabelPattern[@type=\"category-list\"]"));
    496                 SimpleFormatter listPattern = SimpleFormatter.compile(cfile.getStringValue("//ldml/listPatterns/listPattern[@type=\"unit-short\"]/listPatternPart[@type=\"2\"]"));
    497 
    498                 hair = EmojiConstants.JOINER_STRING + hair;
    499                 formatPeople(cfile, first, isSkin, value2, "", skin, skinName, hair, hairName, initialPattern, listPattern, examples);
    500                 formatPeople(cfile, first, isSkin, value2, "", skin, skinName, hair, hairName, initialPattern, listPattern, examples);
    501             }
    502             break;
    503         }
    504         return formatExampleList(examples);
    505     }
    506 
    507     private String getEmojiName(CLDRFile cfile, String skin) {
    508         return cfile.getStringValue("//ldml/annotations/annotation[@cp=\"" + skin + "\"][@type=\"tts\"]");
    509     }
    510 
    511     //ldml/listPatterns/listPattern[@type="standard-short"]/listPatternPart[@type="2"]
    512     private String formatGroup(XPathParts parts, String value, String sourceEmoji, String... components) {
    513         CLDRFile cfile = getCldrFile();
    514         SimpleFormatter initialPattern = SimpleFormatter.compile(cfile.getStringValue("//ldml/characterLabels/characterLabelPattern[@type=\"category-list\"]"));
    515         String value2 = backgroundEndSymbol + value + backgroundStartSymbol;
    516         String[] names = new String[components.length];
    517         int i = 0;
    518         for (String component : components) {
    519             names[i++] = getEmojiName(cfile, component);
    520         }
    521         return backgroundStartSymbol + sourceEmoji + "  " + initialPattern.format(value2,
    522             longListPatternExample(EmojiConstants.COMPOSED_NAME_LIST.getPath(), "n/a", "n/a2", names));
    523     }
    524 
    525     private void formatPeople(CLDRFile cfile, int first, boolean isSkin, String value2, String person, String skin, String skinName,
    526         String hair, String hairName, SimpleFormatter initialPattern, SimpleFormatter listPattern, Collection<String> examples) {
    527         String cp;
    528         String personName = getEmojiName(cfile, person);
    529         StringBuilder emoji = new StringBuilder(person).appendCodePoint(first);
    530         cp = UTF16.valueOf(first);
    531         cp = isSkin ? cp : EmojiConstants.JOINER_STRING + cp;
    532         examples.add(person + cp + "  " + invertBackground(initialPattern.format(personName,value2)));
    533         emoji.setLength(0);
    534         emoji.append(personName);
    535         if (isSkin) {
    536             skinName = value2;
    537             skin = cp;
    538         } else {
    539             hairName = value2;
    540             hair = cp;
    541         }
    542         examples.add(person + skin + hair + "  " + invertBackground(listPattern.format(initialPattern.format(personName, skinName), hairName)));
    543     }
    544 
    545     private String handleDayPeriod(String xpath, String value, ExampleContext context, ExampleType type) {
    546         //ldml/dates/calendars/calendar[@type="gregorian"]/dayPeriods/dayPeriodContext[@type="format"]/dayPeriodWidth[@type="wide"]/dayPeriod[@type="morning1"]
    547         //ldml/dates/calendars/calendar[@type="gregorian"]/dayPeriods/dayPeriodContext[@type="stand-alone"]/dayPeriodWidth[@type="wide"]/dayPeriod[@type="morning1"]
    548         List<String> examples = new ArrayList<>();
    549         final String dayPeriodType = parts.getAttributeValue(5, "type");
    550         org.unicode.cldr.util.DayPeriodInfo.Type aType = dayPeriodType.equals("format") ? DayPeriodInfo.Type.format : DayPeriodInfo.Type.selection;
    551         DayPeriodInfo dayPeriodInfo = supplementalDataInfo.getDayPeriods(aType, cldrFile.getLocaleID());
    552         String periodString = parts.getAttributeValue(-1, "type");
    553 
    554         DayPeriod dayPeriod = DayPeriod.valueOf(periodString);
    555         String periods = dayPeriodInfo.toString(dayPeriod);
    556         examples.add(periods);
    557         if ("format".equals(dayPeriodType)) {
    558             if (value == null) {
    559                 value = "";
    560             }
    561             R3<Integer, Integer, Boolean> info = dayPeriodInfo.getFirstDayPeriodInfo(dayPeriod);
    562             int time = (((info.get0() + info.get1()) % DayPeriodInfo.DAY_LIMIT) / 2);
    563             //String calendar = parts.getAttributeValue(3, "type");
    564             String timeFormatString = icuServiceBuilder.formatDayPeriod(time, backgroundStartSymbol + value + backgroundEndSymbol);
    565             examples.add(invertBackground(timeFormatString));
    566         }
    567         return formatExampleList(examples.toArray(new String[examples.size()]));
    568     }
    569 
    570     private UnitLength getUnitLength() {
    571         return UnitLength.valueOf(parts.getAttributeValue(-3, "type").toUpperCase(Locale.ENGLISH));
    572     }
    573 
    574     private String handleFormatUnit(UnitLength unitLength, Count count, String value) {
    575         FixedDecimal amount = getBest(count);
    576         if (amount == null) {
    577             return "n/a";
    578         }
    579         DecimalFormat numberFormat = icuServiceBuilder.getNumberFormat(1);
    580         return format(value, backgroundStartSymbol + numberFormat.format(amount) + backgroundEndSymbol);
    581     }
    582 
    583     public String handleCompoundUnit(UnitLength unitLength, Count count, String value) {
    584         /**
    585          *  <units>
    586         <unitLength type="long">
    587             <alias source="locale" path="../unitLength[@type='short']"/>
    588         </unitLength>
    589         <unitLength type="short">
    590             <compoundUnit type="per">
    591                 <unitPattern count="other">{0}/{1}</unitPattern>
    592             </compoundUnit>
    593 
    594          *  <compoundUnit type="per">
    595                 <unitPattern count="one">{0}/{1}</unitPattern>
    596                 <unitPattern count="other">{0}/{1}</unitPattern>
    597             </compoundUnit>
    598          <unit type="length-m">
    599                 <unitPattern count="one">{0} meter</unitPattern>
    600                 <unitPattern count="other">{0} meters</unitPattern>
    601             </unit>
    602 
    603          */
    604 
    605         // we want to get a number that works for the count passed in.
    606         FixedDecimal amount = getBest(count);
    607         if (amount == null) {
    608             return "n/a";
    609         }
    610         String unit1 = backgroundStartSymbol + getFormattedUnit("length-meter", unitLength, amount) + backgroundEndSymbol;
    611         String unit2 = backgroundStartSymbol + getFormattedUnit("duration-second", unitLength, new FixedDecimal(1d, 0), "").trim() + backgroundEndSymbol;
    612         // TODO fix hack
    613         String form = this.pluralInfo.getPluralRules().select(amount);
    614         String perPath = "//ldml/units/unitLength" + unitLength.typeString
    615             + "/compoundUnit[@type=\"per\"]"
    616             + "/compoundUnitPattern";
    617         //ldml/units/unitLength[@type="long"]/compoundUnit[@type="per"]/compoundUnitPattern
    618         return format(getValueFromFormat(perPath, form), unit1, unit2);
    619     }
    620 
    621     private FixedDecimal getBest(Count count) {
    622         FixedDecimalSamples samples = pluralInfo.getPluralRules().getDecimalSamples(count.name(), SampleType.DECIMAL);
    623         if (samples == null) {
    624             samples = pluralInfo.getPluralRules().getDecimalSamples(count.name(), SampleType.INTEGER);
    625         }
    626         if (samples == null) {
    627             return null;
    628         }
    629         Set<FixedDecimalRange> samples2 = samples.getSamples();
    630         FixedDecimalRange range = samples2.iterator().next();
    631         return range.end;
    632     }
    633 
    634     private String handleMiscPatterns(XPathParts parts2, String value) {
    635         DecimalFormat numberFormat = icuServiceBuilder.getNumberFormat(0);
    636         String start = backgroundStartSymbol + numberFormat.format(99) + backgroundEndSymbol;
    637         if ("range".equals(parts.getAttributeValue(-1, "type"))) {
    638             String end = backgroundStartSymbol + numberFormat.format(144) + backgroundEndSymbol;
    639             return format(value, start, end);
    640         } else {
    641             return format(value, start);
    642         }
    643     }
    644 
    645     IntervalFormat intervalFormat = new IntervalFormat();
    646 
    647     static Calendar generatingCalendar = Calendar.getInstance(ULocale.US);
    648 
    649     private static Date getDate(int year, int month, int date, int hour, int minute, int second, TimeZone zone) {
    650         synchronized (generatingCalendar) {
    651             generatingCalendar.setTimeZone(GMT_ZONE_SAMPLE);
    652             generatingCalendar.set(year, month, date, hour, minute, second);
    653             return generatingCalendar.getTime();
    654         }
    655     }
    656 
    657     static Date FIRST_INTERVAL = getDate(2008, 1, 13, 5, 7, 9, GMT_ZONE_SAMPLE);
    658     static Map<String, Date> SECOND_INTERVAL = CldrUtility.asMap(new Object[][] {
    659         { "y", getDate(2009, 2, 14, 17, 8, 10, GMT_ZONE_SAMPLE) },
    660         { "M", getDate(2008, 2, 14, 17, 8, 10, GMT_ZONE_SAMPLE) },
    661         { "d", getDate(2008, 1, 14, 17, 8, 10, GMT_ZONE_SAMPLE) },
    662         { "a", getDate(2008, 1, 13, 17, 8, 10, GMT_ZONE_SAMPLE) },
    663         { "h", getDate(2008, 1, 13, 6, 8, 10, GMT_ZONE_SAMPLE) },
    664         { "m", getDate(2008, 1, 13, 5, 8, 10, GMT_ZONE_SAMPLE) }
    665     });
    666 
    667     private String handleIntervalFormats(XPathParts parts, String xpath, String value,
    668         ExampleContext context, ExampleType type) {
    669         if (!parts.getAttributeValue(3, "type").equals("gregorian")) {
    670             return null;
    671         }
    672         if (parts.getElement(6).equals("intervalFormatFallback")) {
    673             SimpleDateFormat dateFormat = new SimpleDateFormat();
    674             String fallbackFormat = invertBackground(setBackground(value));
    675             return format(fallbackFormat, dateFormat.format(FIRST_INTERVAL),
    676                 dateFormat.format(SECOND_INTERVAL.get("y")));
    677         }
    678         String greatestDifference = parts.getAttributeValue(-1, "id");
    679         if (greatestDifference.equals("H")) greatestDifference = "h";
    680         // intervalFormatFallback
    681         // //ldml/dates/calendars/calendar[@type="gregorian"]/dateTimeFormats/intervalFormats/intervalFormatItem[@id="yMd"]/greatestDifference[@id="y"]
    682         // find where to split the value
    683         intervalFormat.setPattern(value);
    684         return intervalFormat.format(FIRST_INTERVAL, SECOND_INTERVAL.get(greatestDifference));
    685     }
    686 
    687     private String handleDelimiters(XPathParts parts, String xpath, String value) {
    688         String lastElement = parts.getElement(-1);
    689         final String[] elements = {
    690             "quotationStart", "alternateQuotationStart",
    691             "alternateQuotationEnd", "quotationEnd" };
    692         String[] quotes = new String[4];
    693         String baseXpath = xpath.substring(0, xpath.lastIndexOf('/'));
    694         for (int i = 0; i < quotes.length; i++) {
    695             String currElement = elements[i];
    696             if (lastElement.equals(currElement)) {
    697                 quotes[i] = backgroundStartSymbol + value + backgroundEndSymbol;
    698             } else {
    699                 quotes[i] = cldrFile.getWinningValue(baseXpath + '/' + currElement);
    700             }
    701         }
    702         String example = cldrFile
    703             .getStringValue("//ldml/localeDisplayNames/types/type[@key=\"calendar\"][@type=\"gregorian\"]");
    704         // NOTE: the example provided here is partially in English because we don't
    705         // have a translated conversational example in CLDR.
    706         return invertBackground(format("{0}They said {1}" + example + "{2}.{3}", (Object[]) quotes));
    707     }
    708 
    709     private String handleListPatterns(XPathParts parts, String value) {
    710         // listPatternType is either "duration" or null/other list
    711         String listPatternType = parts.getAttributeValue(-2, "type");
    712         if (listPatternType == null || !listPatternType.contains("unit")) {
    713             return handleRegularListPatterns(parts, value, ListTypeLength.from(listPatternType));
    714         } else {
    715             return handleDurationListPatterns(parts, value, UnitLength.from(listPatternType));
    716         }
    717     }
    718 
    719     private String handleRegularListPatterns(XPathParts parts, String value, ListTypeLength listTypeLength) {
    720         String patternType = parts.getAttributeValue(-1, "type");
    721         String pathFormat = "//ldml/localeDisplayNames/territories/territory[@type=\"{0}\"]";
    722         String territory1 = getValueFromFormat(pathFormat, "CH");
    723         String territory2 = getValueFromFormat(pathFormat, "JP");
    724         if (patternType.equals("2")) {
    725             return invertBackground(format(setBackground(value), territory1, territory2));
    726         }
    727         String territory3 = getValueFromFormat(pathFormat, "EG");
    728         String territory4 = getValueFromFormat(pathFormat, "CA");
    729         return longListPatternExample(
    730             listTypeLength.getPath(), patternType, value, territory1, territory2, territory3, territory4);
    731     }
    732 
    733     private String handleDurationListPatterns(XPathParts parts, String value, UnitLength unitWidth) {
    734         String patternType = parts.getAttributeValue(-1, "type");
    735         String duration1 = getFormattedUnit("duration-day", unitWidth, 4);
    736         String duration2 = getFormattedUnit("duration-hour", unitWidth, 2);
    737         if (patternType.equals("2")) {
    738             return invertBackground(format(setBackground(value), duration1, duration2));
    739         }
    740         String duration3 = getFormattedUnit("duration-minute", unitWidth, 37);
    741         String duration4 = getFormattedUnit("duration-second", unitWidth, 23);
    742         return longListPatternExample(
    743             unitWidth.listTypeLength.getPath(), patternType, value, duration1, duration2, duration3, duration4);
    744     }
    745 
    746     public enum UnitLength {
    747         LONG(ListTypeLength.UNIT_WIDE), SHORT(ListTypeLength.UNIT_SHORT), NARROW(ListTypeLength.UNIT_NARROW);
    748         final String typeString;
    749         final ListTypeLength listTypeLength;
    750 
    751         UnitLength(ListTypeLength listTypeLength) {
    752             typeString = "[@type=\"" + name().toLowerCase(Locale.ENGLISH) + "\"]";
    753             this.listTypeLength = listTypeLength;
    754         }
    755 
    756         public static UnitLength from(String listPatternType) {
    757             if (listPatternType.equals("unit")) {
    758                 return UnitLength.LONG;
    759             } else if (listPatternType.equals("unit-narrow")) {
    760                 return UnitLength.NARROW;
    761             } else if (listPatternType.equals("unit-short")) {
    762                 return UnitLength.SHORT;
    763             } else {
    764                 throw new IllegalArgumentException();
    765             }
    766         }
    767     }
    768 
    769     private String getFormattedUnit(String unitType, UnitLength unitWidth, FixedDecimal unitAmount) {
    770         DecimalFormat numberFormat = icuServiceBuilder.getNumberFormat(1);
    771         return getFormattedUnit(unitType, unitWidth, unitAmount, numberFormat.format(unitAmount));
    772     }
    773 
    774     private String getFormattedUnit(String unitType, UnitLength unitWidth, double unitAmount) {
    775         return getFormattedUnit(unitType, unitWidth, new FixedDecimal(unitAmount));
    776     }
    777 
    778     private String getFormattedUnit(String unitType, UnitLength unitWidth, FixedDecimal unitAmount, String formattedUnitAmount) {
    779         String form = this.pluralInfo.getPluralRules().select(unitAmount);
    780         String pathFormat = "//ldml/units/unitLength" + unitWidth.typeString
    781             + "/unit[@type=\"{0}\"]/unitPattern[@count=\"{1}\"]";
    782         return format(getValueFromFormat(pathFormat, unitType, form), formattedUnitAmount);
    783     }
    784 
    785     //ldml/listPatterns/listPattern/listPatternPart[@type="2"]  And
    786     //ldml/listPatterns/listPattern[@type="standard-short"]/listPatternPart[@type="2"] Short And
    787     //ldml/listPatterns/listPattern[@type="or"]/listPatternPart[@type="2"] or list
    788     //ldml/listPatterns/listPattern[@type="unit"]/listPatternPart[@type="2"]
    789     //ldml/listPatterns/listPattern[@type="unit-short"]/listPatternPart[@type="2"]
    790     //ldml/listPatterns/listPattern[@type="unit-narrow"]/listPatternPart[@type="2"]
    791 
    792     private String longListPatternExample(String listPathFormat, String patternType, String value, String... items) {
    793         String doublePattern = getPattern(listPathFormat, "2", patternType, value);
    794         String startPattern = getPattern(listPathFormat, "start", patternType, value);
    795         String middlePattern = getPattern(listPathFormat, "middle", patternType, value);
    796         String endPattern = getPattern(listPathFormat, "end", patternType, value);
    797         ListFormatter listFormatter = new ListFormatter(doublePattern, startPattern, middlePattern, endPattern);
    798         String example = listFormatter.format(items);
    799         return invertBackground(example);
    800     }
    801 
    802 
    803     /**
    804      * Helper method for handleListPatterns. Returns the pattern to be used for
    805      * a specified pattern type.
    806      *
    807      * @param pathFormat
    808      * @param pathPatternType
    809      * @param valuePatternType
    810      * @param value
    811      * @return
    812      */
    813     private String getPattern(String pathFormat, String pathPatternType, String valuePatternType, String value) {
    814         return valuePatternType.equals(pathPatternType) ? setBackground(value) : getValueFromFormat(pathFormat, pathPatternType);
    815     }
    816 
    817     private String getValueFromFormat(String format, Object... arguments) {
    818         return cldrFile.getWinningValue(format(format, arguments));
    819     }
    820 
    821     public String handleEllipsis(String type, String value) {
    822         String pathFormat = "//ldml/localeDisplayNames/territories/territory[@type=\"{0}\"]";
    823         //  <ellipsis type="word-final">{0} </ellipsis>
    824         //  <ellipsis type="word-initial"> {0}</ellipsis>
    825         //  <ellipsis type="word-medial">{0}  {1}</ellipsis>
    826         String territory1 = getValueFromFormat(pathFormat, "CH");
    827         String territory2 = getValueFromFormat(pathFormat, "JP");
    828         // if it isn't a word, break in the middle
    829         if (!type.contains("word")) {
    830             territory1 = clip(territory1, 0, 1);
    831             territory2 = clip(territory2, 1, 0);
    832         }
    833         if (type.contains("initial")) {
    834             territory1 = territory2;
    835         }
    836         return invertBackground(format(setBackground(value), territory1, territory2));
    837     }
    838 
    839     public static String clip(String text, int clipStart, int clipEnd) {
    840         BreakIterator bi = BreakIterator.getCharacterInstance();
    841         bi.setText(text);
    842         for (int i = 0; i < clipStart; ++i) {
    843             bi.next();
    844         }
    845         int start = bi.current();
    846         bi.last();
    847         for (int i = 0; i < clipEnd; ++i) {
    848             bi.previous();
    849         }
    850         int end = bi.current();
    851         return start >= end ? text : text.substring(start, end);
    852     }
    853 
    854     /**
    855      * Handle miscellaneous calendar patterns.
    856      *
    857      * @param parts
    858      * @param value
    859      * @return
    860      */
    861     private String handleMonthPatterns(XPathParts parts, String value) {
    862         String calendar = parts.getAttributeValue(3, "type");
    863         String context = parts.getAttributeValue(5, "type");
    864         String month = "8";
    865         if (!context.equals("numeric")) {
    866             String width = parts.getAttributeValue(6, "type");
    867             String xpath = "//ldml/dates/calendars/calendar[@type=\"{0}\"]/months/monthContext[@type=\"{1}\"]/monthWidth[@type=\"{2}\"]/month[@type=\"8\"]";
    868             month = getValueFromFormat(xpath, calendar, context, width);
    869         }
    870         return invertBackground(format(setBackground(value), month));
    871     }
    872 
    873     private String handleAppendItems(XPathParts parts, String value) {
    874         String request = parts.getAttributeValue(-1, "request");
    875         if (!"Timezone".equals(request)) {
    876             return null;
    877         }
    878         String calendar = parts.getAttributeValue(3, "type");
    879 
    880         SimpleDateFormat sdf = icuServiceBuilder.getDateFormat(calendar, 0, DateFormat.MEDIUM, null);
    881         String zone = cldrFile.getStringValue("//ldml/dates/timeZoneNames/gmtZeroFormat");
    882         String result = format(value, setBackground(sdf.format(DATE_SAMPLE)), setBackground(zone));
    883         return result;
    884     }
    885 
    886     class IntervalFormat {
    887         DateTimePatternGenerator.FormatParser formatParser = new DateTimePatternGenerator.FormatParser();
    888         SimpleDateFormat firstFormat = new SimpleDateFormat();
    889         SimpleDateFormat secondFormat = new SimpleDateFormat();
    890         StringBuilder first = new StringBuilder();
    891         StringBuilder second = new StringBuilder();
    892         BitSet letters = new BitSet();
    893 
    894         public String format(Date earlier, Date later) {
    895             return firstFormat.format(earlier) + secondFormat.format(later);
    896         }
    897 
    898         public IntervalFormat setPattern(String pattern) {
    899             formatParser.set(pattern);
    900             first.setLength(0);
    901             second.setLength(0);
    902             boolean doFirst = true;
    903             letters.clear();
    904 
    905             for (Object item : formatParser.getItems()) {
    906                 if (item instanceof DateTimePatternGenerator.VariableField) {
    907                     char c = item.toString().charAt(0);
    908                     if (letters.get(c)) {
    909                         doFirst = false;
    910                     } else {
    911                         letters.set(c);
    912                     }
    913                     if (doFirst) {
    914                         first.append(item);
    915                     } else {
    916                         second.append(item);
    917                     }
    918                 } else {
    919                     if (doFirst) {
    920                         first.append(formatParser.quoteLiteral((String) item));
    921                     } else {
    922                         second.append(formatParser.quoteLiteral((String) item));
    923                     }
    924                 }
    925             }
    926             String calendar = parts.findAttributeValue("calendar", "type");
    927             firstFormat = icuServiceBuilder.getDateFormat(calendar, first.toString());
    928             firstFormat.setTimeZone(GMT_ZONE_SAMPLE);
    929 
    930             secondFormat = icuServiceBuilder.getDateFormat(calendar, second.toString());
    931             secondFormat.setTimeZone(GMT_ZONE_SAMPLE);
    932             return this;
    933         }
    934     }
    935 
    936     private String handleDurationUnit(String value) {
    937         //            ULocale locale = new ULocale(this.icuServiceBuilder.getCldrFile().getLocaleID());
    938         //            SimpleDateFormat df = new SimpleDateFormat(value.replace('h', 'H'), locale);
    939         DateFormat df = this.icuServiceBuilder.getDateFormat("gregorian", value.replace('h', 'H'));
    940         df.setTimeZone(TimeZone.GMT_ZONE);
    941         long time = ((5 * 60 + 37) * 60 + 23) * 1000;
    942         return df.format(new Date(time));
    943     }
    944 
    945     static final List<FixedDecimal> CURRENCY_SAMPLES = Arrays.asList(
    946         new FixedDecimal(1.23),
    947         new FixedDecimal(0),
    948         new FixedDecimal(2.34),
    949         new FixedDecimal(3.45),
    950         new FixedDecimal(5.67),
    951         new FixedDecimal(1));
    952 
    953     private String formatCountValue(String xpath, XPathParts parts, String value, ExampleContext context,
    954         ExampleType type) {
    955         if (!parts.containsAttribute("count")) { // no examples for items that don't format
    956             return null;
    957         }
    958         final PluralInfo plurals = supplementalDataInfo.getPlurals(cldrFile.getLocaleID());
    959         PluralRules pluralRules = plurals.getPluralRules();
    960 
    961         String unitType = parts.getAttributeValue(-2, "type");
    962         if (unitType == null) {
    963             unitType = "USD"; // sample for currency pattern
    964         }
    965         final boolean isPattern = parts.contains("unitPattern");
    966         final boolean isCurrency = !parts.contains("units");
    967 
    968         Count count = null;
    969         final LinkedHashSet<FixedDecimal> exampleCount = new LinkedHashSet();
    970         exampleCount.addAll(CURRENCY_SAMPLES);
    971         String countString = parts.getAttributeValue(-1, "count");
    972         if (countString == null) {
    973             // count = Count.one;
    974             return null;
    975         } else {
    976             try {
    977                 count = Count.valueOf(countString);
    978             } catch (Exception e) {
    979                 return null; // counts like 0
    980             }
    981         }
    982 
    983         // we used to just get the samples for the given keyword, but that doesn't work well any more.
    984         getStartEndSamples(pluralRules.getDecimalSamples(countString, SampleType.INTEGER), exampleCount);
    985         getStartEndSamples(pluralRules.getDecimalSamples(countString, SampleType.DECIMAL), exampleCount);
    986 
    987         if (context != null) {
    988             context.setExampleCount(exampleCount);
    989         }
    990         String result = "";
    991         DecimalFormat currencyFormat = icuServiceBuilder.getCurrencyFormat(unitType);
    992         int decimalCount = currencyFormat.getMinimumFractionDigits();
    993 
    994         // we will cycle until we have (at most) two examples.
    995         Set<FixedDecimal> examplesSeen = new HashSet<FixedDecimal>();
    996         int maxCount = 2;
    997         main:
    998             // If we are a currency, we will try to see if we can set the decimals to match.
    999             // but if nothing works, we will just use a plain sample.
   1000             for (int phase = 0; phase < 2; ++phase) {
   1001                 int check = 0;
   1002                 for (FixedDecimal example : exampleCount) {
   1003                     // we have to first see whether we have a currency. If so, we have to see if the count works.
   1004 
   1005                     if (isCurrency && phase == 0) {
   1006                         example = new FixedDecimal(example.getSource(), decimalCount);
   1007                     }
   1008                     // skip if we've done before (can happen because of the currency reset)
   1009                     if (examplesSeen.contains(example)) {
   1010                         continue;
   1011                     }
   1012                     examplesSeen.add(example);
   1013                     // skip if the count isn't appropriate
   1014                     if (!pluralRules.select(example).equals(count.toString())) {
   1015                         continue;
   1016                     }
   1017 
   1018                     if (value == null) {
   1019                         String fallbackPath = cldrFile.getCountPathWithFallback(xpath, count, true);
   1020                         value = cldrFile.getStringValue(fallbackPath);
   1021                     }
   1022                     String resultItem;
   1023 
   1024                     resultItem = formatCurrency(value, type, unitType, isPattern, isCurrency, count, example);
   1025                     // now add to list
   1026                     result = addExampleResult(resultItem, result);
   1027                     if (isPattern) {
   1028                         String territory = getDefaultTerritory(type);
   1029                         String currency = supplementalDataInfo.getDefaultCurrency(territory);
   1030                         if (currency.equals(unitType)) {
   1031                             currency = "EUR";
   1032                             if (currency.equals(unitType)) {
   1033                                 currency = "JAY";
   1034                             }
   1035                         }
   1036                         resultItem = formatCurrency(value, type, currency, isPattern, isCurrency, count, example);
   1037                         // now add to list
   1038                         result = addExampleResult(resultItem, result);
   1039 
   1040                     }
   1041                     if (--maxCount < 1) {
   1042                         break main;
   1043                     }
   1044                 }
   1045             }
   1046         return result.isEmpty() ? null : result;
   1047     }
   1048 
   1049     static public void getStartEndSamples(PluralRules.FixedDecimalSamples samples, Set<FixedDecimal> target) {
   1050         if (samples != null) {
   1051             for (FixedDecimalRange item : samples.getSamples()) {
   1052                 target.add(item.start);
   1053                 target.add(item.end);
   1054             }
   1055         }
   1056     }
   1057 
   1058     private String formatCurrency(String value, ExampleType type, String unitType, final boolean isPattern, final boolean isCurrency, Count count,
   1059         FixedDecimal example) {
   1060         String resultItem;
   1061         {
   1062             // If we have a pattern, get the unit from the count
   1063             // If we have a unit, get the pattern from the count
   1064             // English is special; both values are retrieved based on the count.
   1065             String unitPattern;
   1066             String unitName;
   1067             if (isPattern) {
   1068                 // //ldml/numbers/currencies/currency[@type="USD"]/displayName
   1069                 unitName = getUnitName(unitType, isCurrency, count);
   1070                 unitPattern = type != ExampleType.ENGLISH ? value : getUnitPattern(unitType, isCurrency, count);
   1071             } else {
   1072                 unitPattern = getUnitPattern(unitType, isCurrency, count);
   1073                 unitName = type != ExampleType.ENGLISH ? value : getUnitName(unitType, isCurrency, count);
   1074             }
   1075 
   1076             if (isPattern) {
   1077                 unitPattern = setBackground(unitPattern);
   1078             } else {
   1079                 unitPattern = setBackgroundExceptMatch(unitPattern, PARAMETER_SKIP0);
   1080             }
   1081 
   1082             MessageFormat unitPatternFormat = new MessageFormat(unitPattern);
   1083 
   1084             // get the format for the currency
   1085             // TODO fix this for special currency overrides
   1086 
   1087             DecimalFormat unitDecimalFormat = icuServiceBuilder.getNumberFormat(1); // decimal
   1088             unitDecimalFormat.setMaximumFractionDigits(example.getVisibleDecimalDigitCount());
   1089             unitDecimalFormat.setMinimumFractionDigits(example.getVisibleDecimalDigitCount());
   1090 
   1091             String formattedNumber = unitDecimalFormat.format(example.getSource());
   1092             unitPatternFormat.setFormatByArgumentIndex(0, unitDecimalFormat);
   1093             resultItem = unitPattern.replace("{0}", formattedNumber).replace("{1}", unitName);
   1094 
   1095             if (isPattern) {
   1096                 resultItem = invertBackground(resultItem);
   1097             }
   1098         }
   1099         return resultItem;
   1100     }
   1101 
   1102     private String addExampleResult(String resultItem, String resultToAddTo) {
   1103         if (resultToAddTo.length() != 0) {
   1104             resultToAddTo += exampleSeparatorSymbol;
   1105         }
   1106         resultToAddTo += resultItem;
   1107         return resultToAddTo;
   1108     }
   1109 
   1110     private String getUnitPattern(String unitType, final boolean isCurrency, Count count) {
   1111         String unitPattern;
   1112         String unitPatternPath = cldrFile.getCountPathWithFallback(isCurrency
   1113             ? "//ldml/numbers/currencyFormats/unitPattern"
   1114                 : "//ldml/units/unit[@type=\"" + unitType + "\"]/unitPattern",
   1115                 count, true);
   1116         unitPattern = cldrFile.getWinningValue(unitPatternPath);
   1117         return unitPattern;
   1118     }
   1119 
   1120     private String getUnitName(String unitType, final boolean isCurrency, Count count) {
   1121         String unitNamePath = cldrFile.getCountPathWithFallback(isCurrency
   1122             ? "//ldml/numbers/currencies/currency[@type=\"" + unitType + "\"]/displayName"
   1123                 : "//ldml/units/unit[@type=\"" + unitType + "\"]/unitPattern",
   1124                 count, true);
   1125         return unitNamePath == null ? unitType : cldrFile.getWinningValue(unitNamePath);
   1126     }
   1127 
   1128     private String handleNumberSymbol(XPathParts parts, String value) {
   1129         String symbolType = parts.getElement(-1);
   1130         String numberSystem = parts.getAttributeValue(2, "numberSystem"); // null if not present
   1131         int index = 1;// dec/percent/sci
   1132         double numberSample = NUMBER_SAMPLE;
   1133         String originalValue = cldrFile.getWinningValue(parts.toString());
   1134         boolean isSuperscripting = false;
   1135         if (symbolType.equals("decimal") || symbolType.equals("group")) {
   1136             index = 1;
   1137         } else if (symbolType.equals("minusSign")) {
   1138             index = 1;
   1139             numberSample = -numberSample;
   1140         } else if (symbolType.equals("percentSign")) {
   1141             // For the perMille symbol, we reuse the percent example.
   1142             index = 2;
   1143             numberSample = 0.23;
   1144         } else if (symbolType.equals("perMille")) {
   1145             // For the perMille symbol, we reuse the percent example.
   1146             index = 2;
   1147             numberSample = 0.023;
   1148             originalValue = cldrFile.getWinningValue(parts.addRelative("../percentSign").toString());
   1149         } else if (symbolType.equals("exponential") || symbolType.equals("plusSign")) {
   1150             index = 3;
   1151         } else if (symbolType.equals("superscriptingExponent")) {
   1152             index = 3;
   1153             isSuperscripting = true;
   1154         } else {
   1155             // We don't need examples for standalone symbols, i.e. infinity and nan.
   1156             // We don't have an example for the list symbol either.
   1157             return null;
   1158         }
   1159         DecimalFormat x = icuServiceBuilder.getNumberFormat(index, numberSystem);
   1160         String example;
   1161         String formattedValue;
   1162         if (isSuperscripting) {
   1163             DecimalFormatSymbols symbols = x.getDecimalFormatSymbols();
   1164             char[] digits = symbols.getDigits();
   1165             x.setDecimalFormatSymbols(symbols);
   1166             x.setNegativeSuffix(endSupSymbol + x.getNegativeSuffix());
   1167             x.setPositiveSuffix(endSupSymbol + x.getPositiveSuffix());
   1168             x.setExponentSignAlwaysShown(false);
   1169 
   1170             // Don't set the exponent directly because future examples for items
   1171             // will be affected as well.
   1172             originalValue = symbols.getExponentSeparator();
   1173             formattedValue = backgroundEndSymbol + value + digits[1] + digits[0] + backgroundStartSymbol + startSupSymbol;
   1174             example = x.format(numberSample);
   1175         } else {
   1176             x.setExponentSignAlwaysShown(true);
   1177             formattedValue = backgroundEndSymbol + value + backgroundStartSymbol;
   1178         }
   1179         example = x.format(numberSample);
   1180         example = example.replace(originalValue, formattedValue);
   1181         return backgroundStartSymbol + example + backgroundEndSymbol;
   1182     }
   1183 
   1184     private String handleNumberingSystem(String value) {
   1185         NumberFormat x = icuServiceBuilder.getGenericNumberFormat(value);
   1186         x.setGroupingUsed(false);
   1187         return x.format(NUMBER_SAMPLE_WHOLE);
   1188     }
   1189 
   1190     private String handleTimeZoneName(String xpath, String value) {
   1191 
   1192         String result = null;
   1193         if (parts.contains("exemplarCity")) {
   1194             // ldml/dates/timeZoneNames/zone[@type="America/Los_Angeles"]/exemplarCity
   1195             String timezone = parts.getAttributeValue(3, "type");
   1196             String countryCode = supplementalDataInfo.getZone_territory(timezone);
   1197             if (countryCode == null) {
   1198                 if (value == null) {
   1199                     result = timezone.substring(timezone.lastIndexOf('/') + 1).replace('_', ' ');
   1200                 } else {
   1201                     result = value;
   1202                 }
   1203                 return result;
   1204             }
   1205             if (countryCode.equals("001")) {
   1206                 // GMT code, so format.
   1207                 try {
   1208                     String hourOffset = timezone.substring(timezone.contains("+") ? 8 : 7);
   1209                     int hours = Integer.parseInt(hourOffset);
   1210                     result = getGMTFormat(null, null, hours);
   1211                 } catch (RuntimeException e) {
   1212                     return result; // fail, skip
   1213                 }
   1214             } else {
   1215                 String countryName = setBackground(cldrFile.getName(CLDRFile.TERRITORY_NAME, countryCode));
   1216                 boolean singleZone = !supplementalDataInfo.getMultizones().contains(countryCode);
   1217                 // we show just country for singlezone countries
   1218                 if (singleZone) {
   1219                     result = countryName;
   1220                 } else {
   1221                     if (value == null) {
   1222                         value = TimezoneFormatter.getFallbackName(timezone);
   1223                     }
   1224                     // otherwise we show the fallback with exemplar
   1225                     String fallback = setBackground(cldrFile
   1226                         .getWinningValue("//ldml/dates/timeZoneNames/fallbackFormat"));
   1227                     // ldml/dates/timeZoneNames/zone[@type="America/Los_Angeles"]/exemplarCity
   1228 
   1229                     result = format(fallback, value, countryName);
   1230                 }
   1231                 // format with "{0} Time" or equivalent.
   1232                 String timeFormat = setBackground(cldrFile.getWinningValue("//ldml/dates/timeZoneNames/regionFormat"));
   1233                 result = format(timeFormat, result);
   1234             }
   1235         } else if (parts.contains("zone")) { // {0} Time
   1236             result = value;
   1237         } else if (parts.contains("regionFormat")) { // {0} Time
   1238             result = format(value, setBackground(cldrFile.getName(CLDRFile.TERRITORY_NAME, "JP")));
   1239             result = addExampleResult(
   1240                 format(value, setBackground(cldrFile.getWinningValue(EXEMPLAR_CITY_LOS_ANGELES))), result);
   1241         } else if (parts.contains("fallbackFormat")) { // {1} ({0})
   1242             String central = setBackground(cldrFile.getWinningValue("//ldml/dates/timeZoneNames/metazone[@type=\"America_Central\"]/long/generic"));
   1243             String cancun = setBackground(cldrFile.getWinningValue("//ldml/dates/timeZoneNames/zone[@type=\"America/Cancun\"]/exemplarCity"));
   1244             result = format(value, cancun, central);
   1245         } else if (parts.contains("gmtFormat")) { // GMT{0}
   1246             result = getGMTFormat(null, value, -8);
   1247         } else if (parts.contains("hourFormat")) { // +HH:mm;-HH:mm
   1248             result = getGMTFormat(value, null, -8);
   1249         } else if (parts.contains("metazone") && !parts.contains("commonlyUsed")) { // Metazone string
   1250             if (value != null && value.length() > 0) {
   1251                 result = getMZTimeFormat() + " " + value;
   1252             } else {
   1253                 // TODO check for value
   1254                 if (parts.contains("generic")) {
   1255                     String metazone_name = parts.getAttributeValue(3, "type");
   1256                     String timezone = supplementalDataInfo.getZoneForMetazoneByRegion(metazone_name, "001");
   1257                     String countryCode = supplementalDataInfo.getZone_territory(timezone);
   1258                     String regionFormat = cldrFile.getWinningValue("//ldml/dates/timeZoneNames/regionFormat");
   1259                     String fallbackFormat = cldrFile.getWinningValue("//ldml/dates/timeZoneNames/fallbackFormat");
   1260                     String exemplarCity = cldrFile.getWinningValue("//ldml/dates/timeZoneNames/zone[@type=\""
   1261                         + timezone + "\"]/exemplarCity");
   1262                     if (exemplarCity == null) {
   1263                         exemplarCity = timezone.substring(timezone.lastIndexOf('/') + 1).replace('_', ' ');
   1264                     }
   1265                     String countryName = cldrFile
   1266                         .getWinningValue("//ldml/localeDisplayNames/territories/territory[@type=\"" + countryCode
   1267                             + "\"]");
   1268                     boolean singleZone = !(supplementalDataInfo.getMultizones().contains(countryCode));
   1269 
   1270                     if (singleZone) {
   1271                         result = setBackground(getMZTimeFormat() + " " +
   1272                             format(regionFormat, countryName));
   1273                     } else {
   1274                         result = setBackground(getMZTimeFormat() + " " +
   1275                             format(fallbackFormat, exemplarCity, countryName));
   1276                     }
   1277                 } else {
   1278                     String gmtFormat = cldrFile.getWinningValue("//ldml/dates/timeZoneNames/gmtFormat");
   1279                     String hourFormat = cldrFile.getWinningValue("//ldml/dates/timeZoneNames/hourFormat");
   1280                     String metazone_name = parts.getAttributeValue(3, "type");
   1281                     // String tz_string = supplementalData.resolveParsedMetazone(metazone_name,"001");
   1282                     String tz_string = supplementalDataInfo.getZoneForMetazoneByRegion(metazone_name, "001");
   1283                     TimeZone currentZone = TimeZone.getTimeZone(tz_string);
   1284                     int tzOffset = currentZone.getRawOffset();
   1285                     if (parts.contains("daylight")) {
   1286                         tzOffset += currentZone.getDSTSavings();
   1287                     }
   1288                     int MILLIS_PER_MINUTE = 1000 * 60;
   1289                     int MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60;
   1290                     int tm_hrs = tzOffset / MILLIS_PER_HOUR;
   1291                     int tm_mins = (tzOffset % MILLIS_PER_HOUR) / 60000; // millis per minute
   1292                     result = setBackground(getMZTimeFormat() + " "
   1293                         + getGMTFormat(hourFormat, gmtFormat, tm_hrs, tm_mins));
   1294                 }
   1295             }
   1296         }
   1297         return result;
   1298     }
   1299 
   1300     private String handleDateFormatItem(String xpath, String value) {
   1301 
   1302         String fullpath = cldrFile.getFullXPath(xpath);
   1303         parts.set(fullpath);
   1304 
   1305         String calendar = parts.findAttributeValue("calendar", "type");
   1306 
   1307         if (parts.contains("dateTimeFormat")) {
   1308             String dateFormatXPath = cldrFile.getWinningPath(xpath.replaceAll("dateTimeFormat", "dateFormat"));
   1309             String timeFormatXPath = cldrFile.getWinningPath(xpath.replaceAll("dateTimeFormat", "timeFormat"));
   1310             String dateFormatValue = cldrFile.getWinningValue(dateFormatXPath);
   1311             String timeFormatValue = cldrFile.getWinningValue(timeFormatXPath);
   1312             parts.set(cldrFile.getFullXPath(dateFormatXPath));
   1313             String dateNumbersOverride = parts.findAttributeValue("pattern", "numbers");
   1314             parts.set(cldrFile.getFullXPath(timeFormatXPath));
   1315             String timeNumbersOverride = parts.findAttributeValue("pattern", "numbers");
   1316             SimpleDateFormat df = icuServiceBuilder.getDateFormat(calendar, dateFormatValue, dateNumbersOverride);
   1317             SimpleDateFormat tf = icuServiceBuilder.getDateFormat(calendar, timeFormatValue, timeNumbersOverride);
   1318             df.setTimeZone(ZONE_SAMPLE);
   1319             tf.setTimeZone(ZONE_SAMPLE);
   1320             String dfResult = "'" + df.format(DATE_SAMPLE) + "'";
   1321             String tfResult = "'" + tf.format(DATE_SAMPLE) + "'";
   1322             SimpleDateFormat dtf = icuServiceBuilder.getDateFormat(calendar,
   1323                 MessageFormat.format(value, (Object[]) new String[] { setBackground(tfResult), setBackground(dfResult) }));
   1324             return dtf.format(DATE_SAMPLE);
   1325         } else {
   1326             String id = parts.findAttributeValue("dateFormatItem", "id");
   1327             if ("NEW".equals(id) || value == null) {
   1328                 return startItalicSymbol + "n/a" + endItalicSymbol;
   1329             } else {
   1330                 String numbersOverride = parts.findAttributeValue("pattern", "numbers");
   1331                 SimpleDateFormat sdf = icuServiceBuilder.getDateFormat(calendar, value, numbersOverride);
   1332                 sdf.setTimeZone(ZONE_SAMPLE);
   1333                 String defaultNumberingSystem = cldrFile.getWinningValue("//ldml/numbers/defaultNumberingSystem");
   1334                 String timeSeparator = cldrFile.getWinningValue("//ldml/numbers/symbols[@numberSystem='" + defaultNumberingSystem + "']/timeSeparator");
   1335                 DateFormatSymbols dfs = sdf.getDateFormatSymbols();
   1336                 dfs.setTimeSeparatorString(timeSeparator);
   1337                 sdf.setDateFormatSymbols(dfs);
   1338                 if (id == null || id.indexOf('B') < 0) {
   1339                     return sdf.format(DATE_SAMPLE);
   1340                 } else {
   1341                     List<String> examples = new ArrayList<String>();
   1342                     examples.add(sdf.format(DATE_SAMPLE3));
   1343                     examples.add(sdf.format(DATE_SAMPLE));
   1344                     examples.add(sdf.format(DATE_SAMPLE4));
   1345                     return formatExampleList(examples.toArray(new String[examples.size()]));
   1346                 }
   1347             }
   1348         }
   1349     }
   1350 
   1351     /**
   1352      * Creates examples for currency formats.
   1353      *
   1354      * @param value
   1355      * @return
   1356      */
   1357     private String handleCurrencyFormat(XPathParts parts, String value, ExampleType type) {
   1358 
   1359         String territory = getDefaultTerritory(type);
   1360 
   1361         String currency = supplementalDataInfo.getDefaultCurrency(territory);
   1362         String checkPath = "//ldml/numbers/currencies/currency[@type=\"" + currency + "\"]/symbol";
   1363         String currencySymbol = cldrFile.getWinningValue(checkPath);
   1364         String numberSystem = parts.getAttributeValue(2, "numberSystem"); // null if not present
   1365 
   1366         DecimalFormat df = icuServiceBuilder.getCurrencyFormat(currency, currencySymbol, numberSystem);
   1367         df.applyPattern(value);
   1368 
   1369         String countValue = parts.getAttributeValue(-1, "count");
   1370         if (countValue != null) {
   1371             return formatCountDecimal(df, countValue);
   1372         }
   1373 
   1374         double sampleAmount = 1295.00;
   1375         String example = formatNumber(df, sampleAmount);
   1376         example = addExampleResult(formatNumber(df, -sampleAmount), example);
   1377 
   1378         return example;
   1379     }
   1380 
   1381     private String getDefaultTerritory(ExampleType type) {
   1382         CLDRLocale loc;
   1383         String territory = "US";
   1384         if (ExampleType.NATIVE.equals(type)) {
   1385             loc = CLDRLocale.getInstance(cldrFile.getLocaleID());
   1386             territory = loc.getCountry();
   1387             if (territory == null || territory.length() == 0) {
   1388                 loc = supplementalDataInfo.getDefaultContentFromBase(loc);
   1389                 territory = loc.getCountry();
   1390                 if (territory.equals("001") && loc.getLanguage().equals("ar")) {
   1391                     territory = "EG"; // Use Egypt as territory for examples in ar locale, since its default content is ar_001.
   1392                 }
   1393             }
   1394             if (territory == null || territory.length() == 0) {
   1395                 territory = "US";
   1396             }
   1397         }
   1398         return territory;
   1399     }
   1400 
   1401     /**
   1402      * Creates examples for decimal formats.
   1403      *
   1404      * @param value
   1405      * @return
   1406      */
   1407     private String handleDecimalFormat(XPathParts parts, String value, ExampleType type) {
   1408         String numberSystem = parts.getAttributeValue(2, "numberSystem"); // null if not present
   1409         DecimalFormat numberFormat = icuServiceBuilder.getNumberFormat(value, numberSystem);
   1410         String countValue = parts.getAttributeValue(-1, "count");
   1411         if (countValue != null) {
   1412             return formatCountDecimal(numberFormat, countValue);
   1413         }
   1414 
   1415         double sampleNum1 = 5.43;
   1416         double sampleNum2 = NUMBER_SAMPLE;
   1417         if (parts.getElement(4).equals("percentFormat")) {
   1418             sampleNum1 = 0.0543;
   1419         }
   1420         String example = formatNumber(numberFormat, sampleNum1);
   1421         example = addExampleResult(formatNumber(numberFormat, sampleNum2), example);
   1422         // have positive and negative
   1423         example = addExampleResult(formatNumber(numberFormat, -sampleNum2), example);
   1424         return example;
   1425     }
   1426 
   1427     private String formatCountDecimal(DecimalFormat numberFormat, String countValue) {
   1428         Count count = Count.valueOf(countValue);
   1429         Double numberSample = getExampleForPattern(numberFormat, count);
   1430         if (numberSample == null) {
   1431             // Ideally, we would suppress the value in the survey tool.
   1432             // However, until we switch over to the ICU samples, we are not guaranteed
   1433             // that "no samples" means "can't occur". So we manufacture something.
   1434             int digits = numberFormat.getMinimumIntegerDigits();
   1435             numberSample = (double) Math.round(1.2345678901234 * Math.pow(10, digits - 1));
   1436         }
   1437         String temp = String.valueOf(numberSample);
   1438         int fractionLength = temp.endsWith(".0") ? 0 : temp.length() - temp.indexOf('.') - 1;
   1439         if (fractionLength != numberFormat.getMaximumFractionDigits()) {
   1440             numberFormat = (DecimalFormat) numberFormat.clone(); // for safety
   1441             numberFormat.setMinimumFractionDigits(fractionLength);
   1442             numberFormat.setMaximumFractionDigits(fractionLength);
   1443         }
   1444         return formatNumber(numberFormat, numberSample);
   1445     }
   1446 
   1447     private String formatNumber(DecimalFormat format, double value) {
   1448         String example = format.format(value);
   1449         return setBackgroundOnMatch(example, ALL_DIGITS);
   1450     }
   1451 
   1452     /**
   1453      * Calculates a numerical example to use for the specified pattern using
   1454      * brute force (TODO: there should be a more elegant way to do this).
   1455      *
   1456      * @param format
   1457      * @param count
   1458      * @return
   1459      */
   1460     private Double getExampleForPattern(DecimalFormat format, Count count) {
   1461         if (patternExamples == null) {
   1462             patternExamples = PluralSamples.getInstance(cldrFile.getLocaleID());
   1463         }
   1464         int numDigits = format.getMinimumIntegerDigits();
   1465         Map<Count, Double> samples = patternExamples.getSamples(numDigits);
   1466         //        int min = (int) Math.pow(10, numDigits - 1);
   1467         //        int max = min * 10;
   1468         //        Map<Count, Integer> examples = patternExamples.get(numDigits);
   1469         //        if (examples == null) {
   1470         //            patternExamples.put(numDigits, examples = new HashMap<Count, Integer>());
   1471         //            Set<Count> typesLeft = new HashSet<Count>(pluralInfo.getCountToExamplesMap().keySet());
   1472         //            // Add at most one example of each type.
   1473         //            for (int i = min; i < max; ++i) {
   1474         //                if (typesLeft.isEmpty()) break;
   1475         //                Count type = Count.valueOf(pluralInfo.getPluralRules().select(i));
   1476         //                if (!typesLeft.contains(type)) continue;
   1477         //                examples.put(type, i);
   1478         //                typesLeft.remove(type);
   1479         //            }
   1480         //            // Add zero as an example only if there is no other option.
   1481         //            if (min == 1) {
   1482         //                Count type = Count.valueOf(pluralInfo.getPluralRules().select(0));
   1483         //                if (!examples.containsKey(type)) examples.put(type, 0);
   1484         //            }
   1485         //        }
   1486         return samples.get(count);
   1487     }
   1488 
   1489     private String handleCurrency(String xpath, String value, ExampleContext context, ExampleType type) {
   1490         String currency = parts.getAttributeValue(-2, "type");
   1491         String fullPath = cldrFile.getFullXPath(xpath, false);
   1492         if (parts.contains("symbol")) {
   1493             if (fullPath != null && fullPath.contains("[@choice=\"true\"]")) {
   1494                 ChoiceFormat cf = new ChoiceFormat(value);
   1495                 value = cf.format(NUMBER_SAMPLE);
   1496             }
   1497             String result;
   1498             DecimalFormat x = icuServiceBuilder.getCurrencyFormat(currency, value);
   1499             result = x.format(NUMBER_SAMPLE);
   1500             result = setBackground(result).replace(value, backgroundEndSymbol + value + backgroundStartSymbol);
   1501             return result;
   1502         } else if (parts.contains("displayName")) {
   1503             return formatCountValue(xpath, parts, value, context, type);
   1504         }
   1505         return null;
   1506     }
   1507 
   1508     private String handleDateRangePattern(String value, String xpath) {
   1509         String result;
   1510         SimpleDateFormat dateFormat = icuServiceBuilder.getDateFormat("gregorian", 2, 0);
   1511         result = format(value, setBackground(dateFormat.format(DATE_SAMPLE)),
   1512             setBackground(dateFormat.format(DATE_SAMPLE2)));
   1513         return result;
   1514     }
   1515 
   1516     /**
   1517      * @param elementToOverride the element that is to be overridden
   1518      * @param element the overriding element
   1519      * @param value the value to override element with
   1520      * @return
   1521      */
   1522     private String getLocaleDisplayPattern(String elementToOverride, String element, String value) {
   1523         final String localeDisplayPatternPath = "//ldml/localeDisplayNames/localeDisplayPattern/";
   1524         if (elementToOverride.equals(element)) {
   1525             return value;
   1526         } else {
   1527             return cldrFile.getWinningValue(localeDisplayPatternPath + elementToOverride);
   1528         }
   1529     }
   1530 
   1531     private String handleDisplayNames(String xpath, XPathParts parts, String value) {
   1532         String result = null;
   1533         if (parts.contains("codePatterns")) {
   1534             //ldml/localeDisplayNames/codePatterns/codePattern[@type="language"]
   1535             //ldml/localeDisplayNames/codePatterns/codePattern[@type="script"]
   1536             //ldml/localeDisplayNames/codePatterns/codePattern[@type="territory"]
   1537             String type = parts.getAttributeValue(-1, "type");
   1538             result = format(value, setBackground(
   1539                 type.equals("language") ? "ace"
   1540                     : type.equals("script") ? "Avst"
   1541                         : type.equals("territory") ? "057" : "CODE"));
   1542         } else if (parts.contains("localeDisplayPattern")) {
   1543             //ldml/localeDisplayNames/localeDisplayPattern/localePattern
   1544             //ldml/localeDisplayNames/localeDisplayPattern/localeSeparator
   1545             //ldml/localeDisplayNames/localeDisplayPattern/localeKeyTypePattern
   1546             String element = parts.getElement(-1);
   1547             value = setBackground(value);
   1548             String localeKeyTypePattern = getLocaleDisplayPattern("localeKeyTypePattern", element, value);
   1549             String localePattern = getLocaleDisplayPattern("localePattern", element, value);
   1550             String localeSeparator = getLocaleDisplayPattern("localeSeparator", element, value);
   1551 
   1552             List<String> locales = new ArrayList<String>();
   1553             if (element.equals("localePattern")) {
   1554                 locales.add("uz-AF");
   1555             }
   1556             locales.add(element.equals("localeKeyTypePattern") ? "uz-Arab@timezone=Africa/Addis_Ababa" : "uz-Arab-AF");
   1557             locales.add("uz-Arab-AF@timezone=Africa/Addis_Ababa;numbers=arab");
   1558             String[] examples = new String[locales.size()];
   1559             for (int i = 0; i < locales.size(); i++) {
   1560                 examples[i] = invertBackground(cldrFile.getName(locales.get(i), false,
   1561                     localeKeyTypePattern, localePattern, localeSeparator));
   1562             }
   1563             result = formatExampleList(examples);
   1564         } else if (parts.contains("languages") || parts.contains("scripts") || parts.contains("territories")) {
   1565             //ldml/localeDisplayNames/languages/language[@type="ar"]
   1566             //ldml/localeDisplayNames/scripts/script[@type="Arab"]
   1567             //ldml/localeDisplayNames/territories/territory[@type="CA"]
   1568             String type = parts.getAttributeValue(-1, "type");
   1569             if (type.contains("_")) {
   1570                 if (value != null && !value.equals(type)) {
   1571                     result = value;
   1572                 } else {
   1573                     result = cldrFile.getConstructedBaileyValue(xpath, null, null);
   1574                 }
   1575             } else {
   1576                 value = setBackground(value);
   1577                 List<String> examples = new ArrayList<String>();
   1578                 String nameType = parts.getElement(3);
   1579 
   1580                 Map<String, String> likely = supplementalDataInfo.getLikelySubtags();
   1581                 String alt = parts.getAttributeValue(-1, "alt");
   1582                 boolean isStandAloneValue = "stand-alone".equals(alt);
   1583                 if (!isStandAloneValue) {
   1584                     // only do this if the value is not a stand-alone form
   1585                     String tag = "language".equals(nameType) ? type : "und_" + type;
   1586                     String max = LikelySubtags.maximize(tag, likely);
   1587                     if (max == null) {
   1588                         return null;
   1589                     }
   1590                     LanguageTagParser ltp = new LanguageTagParser().set(max);
   1591                     String languageName = null;
   1592                     String scriptName = null;
   1593                     String territoryName = null;
   1594                     if (nameType.equals("language")) {
   1595                         languageName = value;
   1596                     } else if (nameType.equals("script")) {
   1597                         scriptName = value;
   1598                     } else {
   1599                         territoryName = value;
   1600                     }
   1601                     if (languageName == null) {
   1602                         languageName = cldrFile.getStringValueWithBailey(CLDRFile.getKey(CLDRFile.LANGUAGE_NAME, ltp.getLanguage()));
   1603                         if (languageName == null) {
   1604                             languageName = cldrFile.getStringValueWithBailey(CLDRFile.getKey(CLDRFile.LANGUAGE_NAME, "en"));
   1605                         }
   1606                         if (languageName == null) {
   1607                             languageName = ltp.getLanguage();
   1608                         }
   1609                     }
   1610                     if (scriptName == null) {
   1611                         scriptName = cldrFile.getStringValueWithBailey(CLDRFile.getKey(CLDRFile.SCRIPT_NAME, ltp.getScript()));
   1612                         if (scriptName == null) {
   1613                             scriptName = cldrFile.getStringValueWithBailey(CLDRFile.getKey(CLDRFile.SCRIPT_NAME, "Latn"));
   1614                         }
   1615                         if (scriptName == null) {
   1616                             scriptName = ltp.getScript();
   1617                         }
   1618                     }
   1619                     if (territoryName == null) {
   1620                         territoryName = cldrFile.getStringValueWithBailey(CLDRFile.getKey(CLDRFile.TERRITORY_NAME, ltp.getRegion()));
   1621                         if (territoryName == null) {
   1622                             territoryName = cldrFile.getStringValueWithBailey(CLDRFile.getKey(CLDRFile.TERRITORY_NAME, "US"));
   1623                         }
   1624                         if (territoryName == null) {
   1625                             territoryName = ltp.getRegion();
   1626                         }
   1627                     }
   1628                     languageName = languageName.replace('(', '[').replace(')', ']').replace('', '').replace('', '');
   1629                     scriptName = scriptName.replace('(', '[').replace(')', ']').replace('', '').replace('', '');
   1630                     territoryName = territoryName.replace('(', '[').replace(')', ']').replace('', '').replace('', '');
   1631 
   1632                     String localePattern = cldrFile.getStringValueWithBailey("//ldml/localeDisplayNames/localeDisplayPattern/localePattern");
   1633                     String localeSeparator = cldrFile.getStringValueWithBailey("//ldml/localeDisplayNames/localeDisplayPattern/localeSeparator");
   1634                     String scriptTerritory = format(localeSeparator, scriptName, territoryName);
   1635                     if (!nameType.equals("script")) {
   1636                         examples.add(invertBackground(format(localePattern, languageName, territoryName)));
   1637                     }
   1638                     if (!nameType.equals("territory")) {
   1639                         examples.add(invertBackground(format(localePattern, languageName, scriptName)));
   1640                     }
   1641                     examples.add(invertBackground(format(localePattern, languageName, scriptTerritory)));
   1642                 } else {
   1643                     int x = 0; // debugging
   1644                 }
   1645                 if (isStandAloneValue || cldrFile.getStringValueWithBailey(xpath + ALT_STAND_ALONE) == null) {
   1646                     // only do this if either it is a stand-alone form,
   1647                     // or it isn't and there is no separate stand-alone form
   1648                     String codePattern = cldrFile.getStringValueWithBailey("//ldml/localeDisplayNames/codePatterns/codePattern[@type=\"" + nameType + "\"]");
   1649                     examples.add(invertBackground(format(codePattern, value)));
   1650                 } else {
   1651                     int x = 0; // debugging
   1652                 }
   1653                 result = formatExampleList(examples.toArray(new String[examples.size()]));
   1654             }
   1655         }
   1656         return result;
   1657     }
   1658 
   1659     private String formatExampleList(String[] examples) {
   1660         String result = examples[0];
   1661         for (int i = 1, len = examples.length; i < len; i++) {
   1662             result = addExampleResult(examples[i], result);
   1663         }
   1664         return result;
   1665     }
   1666 
   1667     /**
   1668      * Return examples formatted as string, with null returned for null or empty examples.
   1669      * @param examples
   1670      * @return
   1671      */
   1672     private String formatExampleList(Collection<String> examples) {
   1673         if (examples == null || examples.isEmpty()) {
   1674             return null;
   1675         }
   1676         String result = "";
   1677         boolean first = true;
   1678         for (String example : examples) {
   1679             if (first) {
   1680                 result = example;
   1681                 first = false;
   1682             } else {
   1683                 result = addExampleResult(example, result);
   1684             }
   1685         }
   1686         return result;
   1687     }
   1688 
   1689     public String format(String format, Object... objects) {
   1690         if (format == null) return null;
   1691         return MessageFormat.format(format, objects);
   1692     }
   1693 
   1694     public static final String unchainException(Exception e) {
   1695         String stackStr = "[unknown stack]<br>";
   1696         try {
   1697             StringWriter asString = new StringWriter();
   1698             e.printStackTrace(new PrintWriter(asString));
   1699             stackStr = "<pre>" + asString.toString() + "</pre>";
   1700         } catch (Throwable tt) {
   1701             // ...
   1702         }
   1703         return stackStr;
   1704     }
   1705 
   1706     /**
   1707      * Put a background on an item, skipping enclosed patterns.
   1708      * @param sampleTerritory
   1709      * @return
   1710      */
   1711     private String setBackground(String inputPattern) {
   1712         Matcher m = PARAMETER.matcher(inputPattern);
   1713         return backgroundStartSymbol + m.replaceAll(backgroundEndSymbol + "$1" + backgroundStartSymbol)
   1714         + backgroundEndSymbol;
   1715     }
   1716 
   1717     /**
   1718      * Put a background on an item, skipping enclosed patterns, except for {0}
   1719      * @param patternToEmbed
   1720      *            TODO
   1721      * @param sampleTerritory
   1722      *
   1723      * @return
   1724      */
   1725     private String setBackgroundExceptMatch(String input, Pattern patternToEmbed) {
   1726         Matcher m = patternToEmbed.matcher(input);
   1727         return backgroundStartSymbol + m.replaceAll(backgroundEndSymbol + "$1" + backgroundStartSymbol)
   1728         + backgroundEndSymbol;
   1729     }
   1730 
   1731     /**
   1732      * Put a background on an item, skipping enclosed patterns, except for {0}
   1733      *
   1734      * @param patternToEmbed
   1735      *            TODO
   1736      * @param sampleTerritory
   1737      *
   1738      * @return
   1739      */
   1740     private String setBackgroundOnMatch(String inputPattern, Pattern patternToEmbed) {
   1741         Matcher m = patternToEmbed.matcher(inputPattern);
   1742         return m.replaceAll(backgroundStartSymbol + "$1" + backgroundEndSymbol);
   1743     }
   1744 
   1745     /**
   1746      * This adds the transliteration of a result in case it has one (i.e. sr_Cyrl -> sr_Latn).
   1747      *
   1748      * @param input
   1749      *            string with special characters from setBackground.
   1750      * @param value
   1751      *            value to be transliterated
   1752      * @return string with attached transliteration if there is one.
   1753      */
   1754     private String addTransliteration(String input, String value) {
   1755         if (value == null) {
   1756             return input;
   1757         }
   1758         for (LocaleTransform localeTransform : LocaleTransform.values()) {
   1759 
   1760             String locale = cldrFile.getLocaleID();
   1761 
   1762             if (!(localeTransform.getInputLocale().equals(locale))) {
   1763                 continue;
   1764             }
   1765 
   1766             Factory factory = CONFIG.getCldrFactory();
   1767             CLDRFileTransformer transformer = new CLDRFileTransformer(factory, CLDRPaths.COMMON_DIRECTORY + "transforms/");
   1768             Transliterator transliterator = transformer.loadTransliterator(localeTransform);
   1769             final String transliterated = transliterator.transliterate(value);
   1770             if (!transliterated.equals(value)) {
   1771                 return backgroundStartSymbol + "[ " + transliterated + " ]" + backgroundEndSymbol + exampleSeparatorSymbol + input;
   1772             }
   1773         }
   1774         return input;
   1775     }
   1776 
   1777     /**
   1778      * This is called just before we return a result. It fixes the special characters that were added by setBackground.
   1779      *
   1780      * @param input
   1781      *            string with special characters from setBackground.
   1782      * @param invert
   1783      *            TODO
   1784      * @return string with HTML for the background.
   1785      */
   1786     private String finalizeBackground(String input) {
   1787         return input == null
   1788             ? input
   1789                 : exampleStart +
   1790                 TransliteratorUtilities.toHTML.transliterate(input)
   1791                 .replace(backgroundStartSymbol + backgroundEndSymbol, "")
   1792                 // remove null runs
   1793                 .replace(backgroundEndSymbol + backgroundStartSymbol, "")
   1794                 // remove null runs
   1795                 .replace(backgroundStartSymbol, backgroundStart)
   1796                 .replace(backgroundEndSymbol, backgroundEnd)
   1797                 .replace(exampleSeparatorSymbol, exampleEnd + exampleStart)
   1798                 .replace(startItalicSymbol, startItalic)
   1799                 .replace(endItalicSymbol, endItalic)
   1800                 .replace(startSupSymbol, startSup)
   1801                 .replace(endSupSymbol, endSup)
   1802                 + exampleEnd;
   1803     }
   1804 
   1805     private String invertBackground(String input) {
   1806         if (input == null) {
   1807             return null;
   1808         }
   1809         input = input.replace(backgroundStartSymbol, backgroundTempSymbol)
   1810             .replace(backgroundEndSymbol, backgroundStartSymbol)
   1811             .replace(backgroundTempSymbol, backgroundEndSymbol);
   1812 
   1813         return backgroundStartSymbol + input + backgroundEndSymbol;
   1814     }
   1815 
   1816     public static final Pattern PARAMETER = PatternCache.get("(\\{[0-9]\\})");
   1817     public static final Pattern PARAMETER_SKIP0 = PatternCache.get("(\\{[1-9]\\})");
   1818     public static final Pattern ALL_DIGITS = PatternCache.get("(\\p{Nd}+(.\\p{Nd}+)?)");
   1819 
   1820     /**
   1821      * Utility to format using a gmtHourString, gmtFormat, and an integer hours. We only need the hours because that's
   1822      * all
   1823      * the TZDB IDs need. Should merge this eventually into TimeZoneFormatter and call there.
   1824      *
   1825      * @param gmtHourString
   1826      * @param gmtFormat
   1827      * @param hours
   1828      * @return
   1829      */
   1830     private String getGMTFormat(String gmtHourString, String gmtFormat, int hours) {
   1831         return getGMTFormat(gmtHourString, gmtFormat, hours, 0);
   1832     }
   1833 
   1834     private String getGMTFormat(String gmtHourString, String gmtFormat, int hours, int minutes) {
   1835         boolean hoursBackground = false;
   1836         if (gmtHourString == null) {
   1837             hoursBackground = true;
   1838             gmtHourString = cldrFile.getWinningValue("//ldml/dates/timeZoneNames/hourFormat");
   1839         }
   1840         if (gmtFormat == null) {
   1841             hoursBackground = false; // for the hours case
   1842             gmtFormat = setBackground(cldrFile.getWinningValue("//ldml/dates/timeZoneNames/gmtFormat"));
   1843         }
   1844         String[] plusMinus = gmtHourString.split(";");
   1845 
   1846         SimpleDateFormat dateFormat = icuServiceBuilder.getDateFormat("gregorian", plusMinus[hours >= 0 ? 0 : 1]);
   1847         dateFormat.setTimeZone(ZONE_SAMPLE);
   1848         calendar.set(1999, 9, 27, Math.abs(hours), minutes, 0); // 1999-09-13 13:25:59
   1849         Date sample = calendar.getTime();
   1850         String hourString = dateFormat.format(sample);
   1851         if (hoursBackground) {
   1852             hourString = setBackground(hourString);
   1853         }
   1854         String result = format(gmtFormat, hourString);
   1855         return result;
   1856     }
   1857 
   1858     private String getMZTimeFormat() {
   1859         String timeFormat = cldrFile
   1860             .getWinningValue(
   1861                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/timeFormats/timeFormatLength[@type=\"short\"]/timeFormat[@type=\"standard\"]/pattern[@type=\"standard\"]");
   1862         if (timeFormat == null) {
   1863             timeFormat = "HH:mm";
   1864         }
   1865         // the following is <= because the TZDB inverts the hours
   1866         SimpleDateFormat dateFormat = icuServiceBuilder.getDateFormat("gregorian", timeFormat);
   1867         dateFormat.setTimeZone(ZONE_SAMPLE);
   1868         calendar.set(1999, 9, 13, 13, 25, 59); // 1999-09-13 13:25:59
   1869         Date sample = calendar.getTime();
   1870         String result = dateFormat.format(sample);
   1871         return result;
   1872     }
   1873 
   1874     public static final char TEXT_VARIANT = '\uFE0E';
   1875 
   1876     /**
   1877      * Return a help string, in html, that should be shown in the Zoomed view.
   1878      * Presumably at the end of each help section is something like: <br>
   1879      * &lt;br&gt;For more information, see <a
   1880      * href='http://unicode.org/cldr/wiki?SurveyToolHelp/characters'>help</a>. <br>
   1881      * The result is valid HTML. Set listPlaceholders to true to include a
   1882      * HTML-formatted table of all placeholders required in the value.<br>
   1883      * TODO: add more help, and modify to get from property or xml file for easy
   1884      * modification.
   1885      *
   1886      * @return null if none available.
   1887      */
   1888     public synchronized String getHelpHtml(String xpath, String value, boolean listPlaceholders) {
   1889 
   1890         // lazy initialization
   1891 
   1892         if (pathDescription == null) {
   1893             Map<String, List<Set<String>>> starredPaths = new HashMap<String, List<Set<String>>>();
   1894             Map<String, String> extras = new HashMap<String, String>();
   1895 
   1896             this.pathDescription = new PathDescription(supplementalDataInfo, englishFile, extras, starredPaths,
   1897                 PathDescription.ErrorHandling.CONTINUE);
   1898 
   1899             this.pathDescription = new PathDescription(supplementalDataInfo, englishFile, extras, starredPaths,
   1900                 PathDescription.ErrorHandling.CONTINUE);
   1901             if (helpMessages == null) {
   1902                 helpMessages = new HelpMessages("test_help_messages.html");
   1903             }
   1904         }
   1905 
   1906         // now get the description
   1907 
   1908         Level level = CONFIG.getCoverageInfo().getCoverageLevel(xpath, cldrFile.getLocaleID());
   1909         String description = pathDescription.getDescription(xpath, value, level, null);
   1910         if (description == null || description.equals("SKIP")) {
   1911             return null;
   1912         }
   1913         // http://cldr.org/translation/timezones
   1914         int start = 0;
   1915         StringBuilder buffer = new StringBuilder();
   1916         while (URLMatcher.reset(description).find(start)) {
   1917             final String url = URLMatcher.group();
   1918             buffer
   1919             .append(TransliteratorUtilities.toHTML.transliterate(description.substring(start, URLMatcher.start())))
   1920             .append("<a target='CLDR-ST-DOCS' href='")
   1921             .append(url)
   1922             .append("'>")
   1923             .append(url)
   1924             .append("</a>");
   1925             start = URLMatcher.end();
   1926         }
   1927         buffer.append(TransliteratorUtilities.toHTML.transliterate(description.substring(start)));
   1928 
   1929         if (listPlaceholders) {
   1930             buffer.append(pathDescription.getPlaceholderDescription(xpath));
   1931         }
   1932         if (xpath.startsWith("//ldml/annotations/annotation")) {
   1933             XPathParts emoji = XPathParts.getFrozenInstance(xpath);
   1934             String cp = emoji.getAttributeValue(-1, "cp");
   1935             String minimal = Utility.hex(cp.replace("", "")).replace(',', '_').toLowerCase(Locale.ROOT);
   1936             buffer.append("<br><img height='64px' width='auto' src='images/emoji/emoji_" + minimal + ".png'>");
   1937         }
   1938 
   1939         return buffer.toString();
   1940         // return helpMessages.find(xpath);
   1941         // if (xpath.contains("/exemplarCharacters")) {
   1942         // result = "The standard exemplar characters are those used in customary writing ([a-z] for English; "
   1943         // + "the auxiliary characters are used in foreign words found in typical magazines, newspapers, &c.; "
   1944         // + "currency auxilliary characters are those used in currency symbols, like 'US$ 1,234'. ";
   1945         // }
   1946         // return result == null ? null : TransliteratorUtilities.toHTML.transliterate(result);
   1947     }
   1948 
   1949     public synchronized String getHelpHtml(String xpath, String value) {
   1950         return getHelpHtml(xpath, value, false);
   1951     }
   1952 
   1953     public static String simplify(String exampleHtml) {
   1954         return simplify(exampleHtml, false);
   1955     }
   1956 
   1957     public static String simplify(String exampleHtml, boolean internal) {
   1958         return exampleHtml == null ? null
   1959             : internal ? "" + exampleHtml
   1960                 .replace("", "")
   1961             .replace("", "") + ""
   1962             : exampleHtml
   1963             .replace("<div class='cldr_example'>", "")
   1964             .replace("</div>", "")
   1965             .replace("<span class='cldr_substituted'>", "")
   1966             .replace("</span>", "");
   1967     }
   1968 
   1969     HelpMessages helpMessages;
   1970 }
   1971