• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
getCldrFile()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      */
getBackgroundEnd()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      */
setBackgroundEnd(String backgroundEnd)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      */
getBackgroundStart()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      */
setBackgroundStart(String backgroundStart)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      */
setVerboseErrors(boolean verbosity)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      */
ExampleGenerator(CLDRFile resolvedCldrFile, CLDRFile englishFile, String supplementalDataDirectory)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 
setExampleCount(Collection<FixedDecimal> exampleCount2)255         public void setExampleCount(Collection<FixedDecimal> exampleCount2) {
256             this.exampleCount = exampleCount2;
257         }
258 
getExampleCount()259         public Collection<FixedDecimal> getExampleCount() {
260             return exampleCount;
261         }
262     }
263 
getExampleHtml(String xpath, String value)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      */
getExampleHtml(String xpath, String value, ExampleContext context, ExampleType type)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 
handleLabelPattern(XPathParts parts2, String value)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 
handleLabel(XPathParts parts2, String value)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 
addFlag(String value2, String isoRegionCode, CLDRFile cfile, SimpleFormatter initialPattern, List<String> examples)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 
addSubdivisionFlag(String value2, String isoSubdivisionCode, CLDRFile cfile, SimpleFormatter initialPattern, List<String> examples)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 
handleAnnotationName(XPathParts parts, String value)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 
getEmojiName(CLDRFile cfile, String skin)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"]
formatGroup(XPathParts parts, String value, String sourceEmoji, String... components)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 
formatPeople(CLDRFile cfile, int first, boolean isSkin, String value2, String person, String skin, String skinName, String hair, String hairName, SimpleFormatter initialPattern, SimpleFormatter listPattern, Collection<String> examples)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 
handleDayPeriod(String xpath, String value, ExampleContext context, ExampleType type)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 
getUnitLength()570     private UnitLength getUnitLength() {
571         return UnitLength.valueOf(parts.getAttributeValue(-3, "type").toUpperCase(Locale.ENGLISH));
572     }
573 
handleFormatUnit(UnitLength unitLength, Count count, String value)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 
handleCompoundUnit(UnitLength unitLength, Count count, String value)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 
getBest(Count count)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 
handleMiscPatterns(XPathParts parts2, String value)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 
getDate(int year, int month, int date, int hour, int minute, int second, TimeZone zone)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 
handleIntervalFormats(XPathParts parts, String xpath, String value, ExampleContext context, ExampleType type)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 
handleDelimiters(XPathParts parts, String xpath, String value)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 
handleListPatterns(XPathParts parts, String value)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 
handleRegularListPatterns(XPathParts parts, String value, ListTypeLength listTypeLength)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 
handleDurationListPatterns(XPathParts parts, String value, UnitLength unitWidth)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 
UnitLength(ListTypeLength listTypeLength)751         UnitLength(ListTypeLength listTypeLength) {
752             typeString = "[@type=\"" + name().toLowerCase(Locale.ENGLISH) + "\"]";
753             this.listTypeLength = listTypeLength;
754         }
755 
from(String listPatternType)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 
getFormattedUnit(String unitType, UnitLength unitWidth, FixedDecimal unitAmount)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 
getFormattedUnit(String unitType, UnitLength unitWidth, double unitAmount)774     private String getFormattedUnit(String unitType, UnitLength unitWidth, double unitAmount) {
775         return getFormattedUnit(unitType, unitWidth, new FixedDecimal(unitAmount));
776     }
777 
getFormattedUnit(String unitType, UnitLength unitWidth, FixedDecimal unitAmount, String formattedUnitAmount)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 
longListPatternExample(String listPathFormat, String patternType, String value, String... items)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      */
getPattern(String pathFormat, String pathPatternType, String valuePatternType, String value)813     private String getPattern(String pathFormat, String pathPatternType, String valuePatternType, String value) {
814         return valuePatternType.equals(pathPatternType) ? setBackground(value) : getValueFromFormat(pathFormat, pathPatternType);
815     }
816 
getValueFromFormat(String format, Object... arguments)817     private String getValueFromFormat(String format, Object... arguments) {
818         return cldrFile.getWinningValue(format(format, arguments));
819     }
820 
handleEllipsis(String type, String value)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 
clip(String text, int clipStart, int clipEnd)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      */
handleMonthPatterns(XPathParts parts, String value)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 
handleAppendItems(XPathParts parts, String value)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 
format(Date earlier, Date later)894         public String format(Date earlier, Date later) {
895             return firstFormat.format(earlier) + secondFormat.format(later);
896         }
897 
setPattern(String pattern)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 
handleDurationUnit(String value)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 
formatCountValue(String xpath, XPathParts parts, String value, ExampleContext context, ExampleType type)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 
getStartEndSamples(PluralRules.FixedDecimalSamples samples, Set<FixedDecimal> target)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 
formatCurrency(String value, ExampleType type, String unitType, final boolean isPattern, final boolean isCurrency, Count count, FixedDecimal example)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 
addExampleResult(String resultItem, String resultToAddTo)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 
getUnitPattern(String unitType, final boolean isCurrency, Count count)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 
getUnitName(String unitType, final boolean isCurrency, Count count)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 
handleNumberSymbol(XPathParts parts, String value)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 
handleNumberingSystem(String value)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 
handleTimeZoneName(String xpath, String value)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 
handleDateFormatItem(String xpath, String value)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      */
handleCurrencyFormat(XPathParts parts, String value, ExampleType type)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 
getDefaultTerritory(ExampleType type)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      */
handleDecimalFormat(XPathParts parts, String value, ExampleType type)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 
formatCountDecimal(DecimalFormat numberFormat, String countValue)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 
formatNumber(DecimalFormat format, double value)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      */
getExampleForPattern(DecimalFormat format, Count count)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 
handleCurrency(String xpath, String value, ExampleContext context, ExampleType type)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 
handleDateRangePattern(String value, String xpath)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      */
getLocaleDisplayPattern(String elementToOverride, String element, String value)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 
handleDisplayNames(String xpath, XPathParts parts, String value)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 
formatExampleList(String[] examples)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      */
formatExampleList(Collection<String> examples)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 
format(String format, Object... objects)1689     public String format(String format, Object... objects) {
1690         if (format == null) return null;
1691         return MessageFormat.format(format, objects);
1692     }
1693 
unchainException(Exception e)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      */
setBackground(String inputPattern)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      */
setBackgroundExceptMatch(String input, Pattern patternToEmbed)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      */
setBackgroundOnMatch(String inputPattern, Pattern patternToEmbed)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      */
addTransliteration(String input, String value)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      */
finalizeBackground(String input)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 
invertBackground(String input)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      */
getGMTFormat(String gmtHourString, String gmtFormat, int hours)1830     private String getGMTFormat(String gmtHourString, String gmtFormat, int hours) {
1831         return getGMTFormat(gmtHourString, gmtFormat, hours, 0);
1832     }
1833 
getGMTFormat(String gmtHourString, String gmtFormat, int hours, int minutes)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 
getMZTimeFormat()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      */
getHelpHtml(String xpath, String value, boolean listPlaceholders)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 
getHelpHtml(String xpath, String value)1949     public synchronized String getHelpHtml(String xpath, String value) {
1950         return getHelpHtml(xpath, value, false);
1951     }
1952 
simplify(String exampleHtml)1953     public static String simplify(String exampleHtml) {
1954         return simplify(exampleHtml, false);
1955     }
1956 
simplify(String exampleHtml, boolean internal)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