• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.unicode.cldr.unittest;
2 
3 import java.io.IOException;
4 import java.util.ArrayList;
5 import java.util.Arrays;
6 import java.util.Collection;
7 import java.util.HashMap;
8 import java.util.HashSet;
9 import java.util.LinkedHashMap;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Map.Entry;
13 import java.util.Set;
14 import java.util.TreeSet;
15 import java.util.stream.Collectors;
16 
17 import org.unicode.cldr.test.ExampleGenerator;
18 import org.unicode.cldr.test.ExampleGenerator.UnitLength;
19 import org.unicode.cldr.util.CLDRConfig;
20 import org.unicode.cldr.util.CLDRFile;
21 import org.unicode.cldr.util.CLDRPaths;
22 import org.unicode.cldr.util.CldrUtility;
23 import org.unicode.cldr.util.Factory;
24 import org.unicode.cldr.util.GrammarInfo;
25 import org.unicode.cldr.util.GrammarInfo.CaseValues;
26 import org.unicode.cldr.util.GrammarInfo.GrammaticalFeature;
27 import org.unicode.cldr.util.GrammarInfo.GrammaticalScope;
28 import org.unicode.cldr.util.GrammarInfo.GrammaticalTarget;
29 import org.unicode.cldr.util.Pair;
30 import org.unicode.cldr.util.PathStarrer;
31 import org.unicode.cldr.util.SupplementalDataInfo;
32 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo;
33 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count;
34 import org.unicode.cldr.util.SupplementalDataInfo.PluralType;
35 import org.unicode.cldr.util.UnitPathType;
36 import org.unicode.cldr.util.With;
37 
38 import com.google.common.collect.ImmutableSet;
39 import com.ibm.icu.dev.test.TestFmwk;
40 
41 public class TestExampleGenerator extends TestFmwk {
42 
43     boolean showTranslationPaths = CldrUtility.getProperty("TestExampleGenerator:showTranslationPaths", false);
44 
45     private static final SupplementalDataInfo SDI = SupplementalDataInfo.getInstance();
46     CLDRConfig info = CLDRConfig.getInstance();
47 
main(String[] args)48     public static void main(String[] args) {
49         new TestExampleGenerator().run(args);
50     }
51 
testCurrency()52     public void testCurrency() {
53         String[][] tests = {
54             {
55                 "fr",
56                 "one",
57                 "〖❬1,23 ❭value-one〗〖❬0,00 ❭value-one〗",
58             "〖❬1,23❭_❬dollar des États-Unis❭〗〖❬1,23❭_❬euro❭〗〖❬0,00❭_❬dollar des États-Unis❭〗〖❬0,00❭_❬euro❭〗" },
59             {
60                 "fr",
61                 "other",
62                 "〖❬2,34 ❭value-other〗〖❬3,45 ❭value-other〗",
63             "〖❬2,34❭_❬dollars des États-Unis❭〗〖❬2,34❭_❬euros❭〗〖❬3,45❭_❬dollars des États-Unis❭〗〖❬3,45❭_❬euros❭〗" },
64             { "en", "one", "〖❬1 ❭Bermudan dollar〗",
65             "〖❬1❭ ❬US dollar❭〗〖❬1❭ ❬euro❭〗" },
66             { "en", "other",
67                 "〖❬1.23 ❭Bermudan dollars〗〖❬0.00 ❭Bermudan dollars〗",
68             "〖❬1.23❭ ❬US dollars❭〗〖❬1.23❭ ❬euros❭〗〖❬0.00❭ ❬US dollars❭〗〖❬0.00❭ ❬euros❭〗" }, };
69         String sampleCurrencyPatternPrefix = "//ldml/numbers/currencyFormats[@numberSystem=\"latn\"]/unitPattern[@count=\"";
70         String sampleCurrencyPrefix = "//ldml/numbers/currencies/currency[@type=\"BMD\"]/displayName[@count=\"";
71         String sampleTemplateSuffix = "\"]";
72 
73         for (String[] row : tests) {
74             ExampleGenerator exampleGenerator = getExampleGenerator(row[0]);
75             String value = "value-" + row[1];
76 
77             String path = sampleCurrencyPrefix + row[1] + sampleTemplateSuffix;
78             String result = ExampleGenerator
79                 .simplify(exampleGenerator.getExampleHtml(path, value), false);
80             assertEquals(row[0] + "-" + row[1] + "-BMD", row[2], result);
81 
82             value = "{0}_{1}";
83             path = sampleCurrencyPatternPrefix + row[1] + sampleTemplateSuffix;
84             result = ExampleGenerator
85                 .simplify(exampleGenerator.getExampleHtml(path, value), false);
86             assertEquals(row[0] + "-" + row[1] + "-pat", row[3], result);
87         }
88     }
89 
90     /**
91      * Only add to this if the example should NEVER appear.
92      * <br>WARNING - do not disable the test by putting in too broad a match. Make sure the paths are reasonably granular.
93      */
94     static final Set<String> DELIBERATE_EXCLUDED_EXAMPLES = ImmutableSet.of(
95         "//ldml/layout/orientation/characterOrder",
96         "//ldml/layout/orientation/lineOrder",
97         "//ldml/characters/moreInformation",
98         "//ldml/numbers/symbols[@numberSystem=\"([^\"]*+)\"]/infinity",
99         "//ldml/numbers/symbols[@numberSystem=\"([^\"]*+)\"]/list",
100         "//ldml/numbers/symbols[@numberSystem=\"([^\"]*+)\"]/nan",
101         "//ldml/numbers/currencies/currency[@type=\"([^\"]*+)\"]/displayName",
102         "//ldml/localeDisplayNames/measurementSystemNames/measurementSystemName[@type=\"([^\"]*+)\"]",
103         // old format
104         "//ldml/numbers/symbols/infinity",
105         "//ldml/numbers/symbols/list",
106         "//ldml/numbers/symbols/nan",
107         "//ldml/posix/messages/nostr",
108         "//ldml/posix/messages/yesstr",
109         "//ldml/contextTransforms/contextTransformUsage[@type=\"([^\"]*+)\"]/contextTransform[@type=\"([^\"]*+)\"]",
110         "//ldml/characters/exemplarCharacters",
111         "//ldml/characters/exemplarCharacters[@type=\"([^\"]*+)\"]",
112         "//ldml/characters/parseLenients.*",
113         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/months/monthContext[@type=\"([^\"]*+)\"]/monthWidth[@type=\"([^\"]*+)\"]/month[@type=\"([^\"]*+)\"]",
114         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/days/dayContext[@type=\"([^\"]*+)\"]/dayWidth[@type=\"([^\"]*+)\"]/day[@type=\"([^\"]*+)\"]",
115         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/quarters/quarterContext[@type=\"([^\"]*+)\"]/quarterWidth[@type=\"([^\"]*+)\"]/quarter[@type=\"([^\"]*+)\"]",
116         "//ldml/dates/fields/field[@type=\"([^\"]*+)\"]/displayName",
117         "//ldml/dates/fields/field[@type=\"([^\"]*+)\"]/relative[@type=\"([^\"]*+)\"]",
118         "//ldml/dates/fields/field[@type=\"([^\"]*+)\"]/relativeTime[@type=\"([^\"]*+)\"]/relativeTimePattern[@count=\"([^\"]*+)\"]",
119         "//ldml/dates/fields/field[@type=\"([^\"]*+)\"]/relativePeriod",
120         "//ldml/dates/fields/field[@type=\"([^\"]*+)\"]/displayName[@alt=\"([^\"]*+)\"]",
121         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/cyclicNameSets/cyclicNameSet[@type=\"([^\"]*+)\"]/cyclicNameContext[@type=\"([^\"]*+)\"]/cyclicNameWidth[@type=\"([^\"]*+)\"]/cyclicName[@type=\"([^\"]*+)\"]",
122 
123         "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"([^\"]*+)\"]",
124         "//ldml/numbers/minimalPairs/ordinalMinimalPairs[@ordinal=\"([^\"]*+)\"]",
125         "//ldml/characters/parseLenients[@scope=\"([^\"]*+)\"][@level=\"([^\"]*+)\"]/parseLenient[@sample=\"([^\"]*+)\"]"
126         );
127     // Only add to above if the example should NEVER appear.
128 
129     /**
130      * Add to this if the example SHOULD appear, but we don't have it yet.
131      * <br>TODO Add later
132      */
133     static final Set<String> TEMPORARY_EXCLUDED_EXAMPLES = ImmutableSet.of(
134 
135         "//ldml/numbers/currencyFormats/currencySpacing/beforeCurrency/currencyMatch",
136         "//ldml/numbers/currencyFormats/currencySpacing/beforeCurrency/surroundingMatch",
137         "//ldml/numbers/currencyFormats/currencySpacing/beforeCurrency/insertBetween",
138         "//ldml/numbers/currencyFormats/currencySpacing/afterCurrency/currencyMatch",
139         "//ldml/numbers/currencyFormats/currencySpacing/afterCurrency/surroundingMatch",
140         "//ldml/numbers/currencyFormats/currencySpacing/afterCurrency/insertBetween",
141         "//ldml/numbers/currencyFormats/currencyPatternAppendISO", // TODO see CLDR-14831
142         "//ldml/numbers/currencyFormats[@numberSystem=\"([^\"]*+)\"]/currencySpacing/beforeCurrency/currencyMatch",
143         "//ldml/numbers/currencyFormats[@numberSystem=\"([^\"]*+)\"]/currencySpacing/beforeCurrency/surroundingMatch",
144         "//ldml/numbers/currencyFormats[@numberSystem=\"([^\"]*+)\"]/currencySpacing/beforeCurrency/insertBetween",
145         "//ldml/numbers/currencyFormats[@numberSystem=\"([^\"]*+)\"]/currencySpacing/afterCurrency/currencyMatch",
146         "//ldml/numbers/currencyFormats[@numberSystem=\"([^\"]*+)\"]/currencySpacing/afterCurrency/surroundingMatch",
147         "//ldml/numbers/currencyFormats[@numberSystem=\"([^\"]*+)\"]/currencySpacing/afterCurrency/insertBetween",
148         "//ldml/numbers/currencyFormats[@numberSystem=\"([^\"]*+)\"]/currencyPatternAppendISO", // TODO see CLDR-14831
149 
150         "//ldml/localeDisplayNames/variants/variant[@type=\"([^\"]*+)\"]",
151         "//ldml/localeDisplayNames/keys/key[@type=\"([^\"]*+)\"]",
152         "//ldml/localeDisplayNames/types/type[@key=\"([^\"]*+)\"][@type=\"([^\"]*+)\"]",
153         "//ldml/localeDisplayNames/types/type[@key=\"([^\"]*+)\"][@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]",
154 
155         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/eras/eraNames/era[@type=\"([^\"]*+)\"]",
156         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/eras/eraAbbr/era[@type=\"([^\"]*+)\"]",
157         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/eras/eraNarrow/era[@type=\"([^\"]*+)\"]",
158 
159         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateFormats/dateFormatLength[@type=\"([^\"]*+)\"]/dateFormat[@type=\"([^\"]*+)\"]/datetimeSkeleton",
160         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/timeFormats/timeFormatLength[@type=\"([^\"]*+)\"]/timeFormat[@type=\"([^\"]*+)\"]/datetimeSkeleton",
161         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateFormats/dateFormatLength[@type=\"([^\"]*+)\"]/datetimeSkeleton",
162         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/timeFormats/timeFormatLength[@type=\"([^\"]*+)\"]/datetimeSkeleton",
163         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateTimeFormats/appendItems/appendItem[@request=\"([^\"]*+)\"]",
164         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateTimeFormats/intervalFormats/intervalFormatFallback",
165         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateTimeFormats/intervalFormats/intervalFormatItem[@id=\"([^\"]*+)\"]/greatestDifference[@id=\"([^\"]*+)\"]",
166         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/eras/eraNames/era[@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]",
167         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/eras/eraAbbr/era[@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]",
168         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/eras/eraNarrow/era[@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]",
169         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/months/monthContext[@type=\"([^\"]*+)\"]/monthWidth[@type=\"([^\"]*+)\"]/month[@type=\"([^\"]*+)\"][@yeartype=\"([^\"]*+)\"]",
170         "//ldml/dates/timeZoneNames/gmtZeroFormat",
171 
172         "//ldml/numbers/minimumGroupingDigits",
173         "//ldml/numbers/symbols/timeSeparator",
174         "//ldml/numbers/symbols[@numberSystem=\"([^\"]*+)\"]/timeSeparator",
175 
176         "//ldml/units/unitLength[@type=\"([^\"]*+)\"]/unit[@type=\"([^\"]*+)\"]/displayName",
177         "//ldml/units/unitLength[@type=\"([^\"]*+)\"]/unit[@type=\"([^\"]*+)\"]/perUnitPattern",
178         "//ldml/units/unitLength[@type=\"([^\"]*+)\"]/coordinateUnit/coordinateUnitPattern[@type=\"([^\"]*+)\"]",
179         "//ldml/units/unitLength[@type=\"([^\"]*+)\"]/coordinateUnit/displayName",
180 
181         "//ldml/characterLabels/characterLabelPattern[@type=\"([^\"]*+)\"]",
182         "//ldml/characterLabels/characterLabelPattern[@type=\"([^\"]*+)\"][@count=\"([^\"]*+)\"]",
183         "//ldml/characterLabels/characterLabel[@type=\"([^\"]*+)\"]",
184         "//ldml/typographicNames/axisName[@type=\"([^\"]*+)\"]",
185         "//ldml/typographicNames/styleName[@type=\"([^\"]*+)\"][@subtype=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]",
186         "//ldml/typographicNames/styleName[@type=\"([^\"]*+)\"][@subtype=\"([^\"]*+)\"]",
187         "//ldml/typographicNames/featureName[@type=\"([^\"]*+)\"]",
188 
189         "//ldml/localeDisplayNames/subdivisions/subdivision[@type=\"([^\"]*+)\"]",
190 
191         "//ldml/dates/timeZoneNames/zone[@type=\"([^\"]*+)\"]/long/standard", // Error: (TestExampleGenerator.java:245) No background:   <Coordinated Universal Time>    〖Coordinated Universal Time〗
192 
193         "//ldml/personNames/nameOrderLocales[@order=\"([^\"]*+)\"]", // TODO CLDR-15384
194         "//ldml/personNames/foreignSpaceReplacement[@xml:space=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]", // TODO CLDR-15384
195         "//ldml/personNames/foreignSpaceReplacement[@xml:space=\"([^\"]*+)\"]", // TODO CLDR-15384
196         "//ldml/personNames/foreignSpaceReplacement[@alt=\"([^\"]*+)\"]", // TODO CLDR-15384
197         "//ldml/personNames/foreignSpaceReplacement", // TODO CLDR-15384
198         "//ldml/personNames/initialPattern[@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]", // TODO CLDR-15384
199         "//ldml/personNames/initialPattern[@type=\"([^\"]*+)\"]", // TODO CLDR-15384
200         "//ldml/personNames/personName[@order=\"([^\"]*+)\"][@length=\"([^\"]*+)\"][@usage=\"([^\"]*+)\"][@formality=\"([^\"]*+)\"]/namePattern[@alt=\"([^\"]*+)\"]", // TODO CLDR-15384
201         "//ldml/personNames/sampleName[@item=\"([^\"]*+)\"]/nameField[@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]", // TODO CLDR-15384
202         "//ldml/personNames/sampleName[@item=\"([^\"]*+)\"]/nameField[@type=\"([^\"]*+)\"]" // TODO CLDR-15384
203 
204         );
205     // Add to above if the example SHOULD appear, but we don't have it yet. TODO Add later
206 
207 
208     /**
209      * Only add to this if the background should NEVER appear.
210      * <br>The background is used when the element is used as part of another format.
211      * <br>WARNING - do not disable the test by putting in too broad a match. Make sure the paths are reasonably granular.
212      */
213     static final Set<String> DELIBERATE_OK_TO_MISS_BACKGROUND = ImmutableSet.of(
214         "//ldml/numbers/defaultNumberingSystem",
215         "//ldml/numbers/otherNumberingSystems/native",
216         // TODO fix formatting
217         "//ldml/characters/exemplarCharacters",
218         "//ldml/characters/exemplarCharacters[@type=\"([^\"]*+)\"]",
219         // TODO Add background
220         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateFormats/dateFormatLength[@type=\"([^\"]*+)\"]/dateFormat[@type=\"([^\"]*+)\"]/pattern[@type=\"([^\"]*+)\"]",
221         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/timeFormats/timeFormatLength[@type=\"([^\"]*+)\"]/timeFormat[@type=\"([^\"]*+)\"]/pattern[@type=\"([^\"]*+)\"]",
222         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateTimeFormats/availableFormats/dateFormatItem[@id=\"([^\"]*+)\"]",
223         "//ldml/dates/timeZoneNames/zone[@type=\"([^\"]*+)\"]/exemplarCity",
224         "//ldml/dates/timeZoneNames/zone[@type=\"([^\"]*+)\"]/exemplarCity[@alt=\"([^\"]*+)\"]",
225         "//ldml/dates/timeZoneNames/zone[@type=\"([^\"]*+)\"]/long/daylight",
226         "//ldml/dates/timeZoneNames/zone[@type=\"([^\"]*+)\"]/short/generic",
227         "//ldml/dates/timeZoneNames/zone[@type=\"([^\"]*+)\"]/short/standard",
228         "//ldml/dates/timeZoneNames/zone[@type=\"([^\"]*+)\"]/short/daylight",
229         "//ldml/dates/timeZoneNames/metazone[@type=\"([^\"]*+)\"]/long/generic",
230         "//ldml/dates/timeZoneNames/metazone[@type=\"([^\"]*+)\"]/long/standard",
231         "//ldml/dates/timeZoneNames/metazone[@type=\"([^\"]*+)\"]/long/daylight",
232         "//ldml/units/durationUnit[@type=\"([^\"]*+)\"]/durationUnitPattern");
233     // Only add to above if the background should NEVER appear.
234 
235 
236     /**
237      * Add to this if the background SHOULD appear, but we don't have them yet.
238      * <br> The background is used when the element is used as part of another format.
239      * <br> TODO Add later
240      */
241     static final Set<String> TEMPORARY_OK_TO_MISS_BACKGROUND = ImmutableSet.of(
242         "//ldml/numbers/defaultNumberingSystem",
243         "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateTimeFormats/availableFormats/dateFormatItem[@id=\"([^\"]*+)\"][@count=\"([^\"]*+)\"]",
244         "//ldml/dates/timeZoneNames/zone[@type=\"([^\"]*+)\"]/long/standard",
245         "//ldml/dates/timeZoneNames/metazone[@type=\"([^\"]*+)\"]/short/generic",
246         "//ldml/dates/timeZoneNames/metazone[@type=\"([^\"]*+)\"]/short/standard",
247         "//ldml/dates/timeZoneNames/metazone[@type=\"([^\"]*+)\"]/short/daylight",
248         "//ldml/personNames/personName[@order=\"([^\"]*+)\"][@length=\"([^\"]*+)\"][@formality=\"([^\"]*+)\"]/namePattern",
249         "//ldml/personNames/personName[@order=\"([^\"]*+)\"][@length=\"([^\"]*+)\"][@usage=\"([^\"]*+)\"][@formality=\"([^\"]*+)\"]/namePattern"); // CLDR-15384
250     // Add to above if the background SHOULD appear, but we don't have them yet. TODO Add later
251 
TestAllPaths()252     public void TestAllPaths() {
253         ExampleGenerator exampleGenerator = getExampleGenerator("en");
254         PathStarrer ps = new PathStarrer();
255         Set<String> seen = new HashSet<>();
256         CLDRFile cldrFile = exampleGenerator.getCldrFile();
257         TreeSet<String> target = new TreeSet<>(cldrFile.getComparator());
258         cldrFile.fullIterable().forEach(target::add);
259         for (String path : target) {
260             String plainStarred = ps.set(path);
261             String value = cldrFile.getStringValue(path);
262             if (value == null || path.endsWith("/alias")
263                 || path.startsWith("//ldml/identity")
264                 || DELIBERATE_EXCLUDED_EXAMPLES.contains(plainStarred)) {
265                 continue;
266             }
267             if (TEMPORARY_EXCLUDED_EXAMPLES.contains(plainStarred)) {
268                 if (logKnownIssue(
269                     "Cldrbug:6342",
270                     "Need an example for each path used in context: " + plainStarred)) {
271                     continue;
272                 }
273                 continue;
274             }
275             String example = exampleGenerator.getExampleHtml(path, value);
276             String javaEscapedStarred = "\""
277                 + plainStarred.replace("\"", "\\\"") + "\",";
278             if (example == null) {
279                 if (!seen.contains(javaEscapedStarred)) {
280                     errln("No example:\t<" + value + ">\t" + javaEscapedStarred);
281                 }
282             } else {
283                 String simplified = ExampleGenerator.simplify(example, false);
284 
285                 if (simplified.contains("null")) {
286                     if (true || !seen.contains(javaEscapedStarred)) {
287                         // debug
288                         exampleGenerator.getExampleHtml(path, value);
289                         ExampleGenerator.simplify(example, false);
290 
291                         errln("'null' in message:\t<" + value + ">\t"
292                             + simplified + "\t" + javaEscapedStarred);
293                         // String example2 =
294                         // exampleGenerator.getExampleHtml(path, value); // for
295                         // debugging
296                     }
297                 } else if (!simplified.startsWith("〖")) {
298                     if (!seen.contains(javaEscapedStarred)) {
299                         errln("Funny HTML:\t<" + value + ">\t" + simplified
300                             + "\t" + javaEscapedStarred);
301                     }
302                 } else if (!simplified.contains("❬")
303                     && !DELIBERATE_OK_TO_MISS_BACKGROUND.contains(plainStarred)) {
304                     if (!seen.contains(javaEscapedStarred)) {
305 
306                         if (TEMPORARY_OK_TO_MISS_BACKGROUND.contains(plainStarred)
307                             && logKnownIssue(
308                                 "Cldrbug:6342",
309                                 "Make sure that background appears: " + simplified + "; " + plainStarred)) {
310                             continue;
311                         }
312 
313                         errln("No background:\t<" + value + ">\t" + simplified
314                             + "\t" + javaEscapedStarred);
315                     }
316                 }
317             }
318             seen.add(javaEscapedStarred);
319         }
320     }
321 
TestUnits()322     public void TestUnits() {
323         ExampleGenerator exampleGenerator = getExampleGenerator("en");
324         checkValue("Duration hm", "〖5:37〗", exampleGenerator,
325             "//ldml/units/durationUnit[@type=\"hm\"]/durationUnitPattern");
326         checkValue(
327             "Length m",
328             "〖❬1❭ meter〗",
329             exampleGenerator,
330             "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"length-meter\"]/unitPattern[@count=\"one\"]");
331         checkValue(
332             "Length m",
333             "〖❬1.5❭ meters〗",
334             exampleGenerator,
335             "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"length-meter\"]/unitPattern[@count=\"other\"]");
336         checkValue(
337             "Length m",
338             "〖❬1.5❭ m〗",
339             exampleGenerator,
340             "//ldml/units/unitLength[@type=\"short\"]/unit[@type=\"length-meter\"]/unitPattern[@count=\"other\"]");
341         checkValue(
342             "Length m",
343             "〖❬1.5❭m〗",
344             exampleGenerator,
345             "//ldml/units/unitLength[@type=\"narrow\"]/unit[@type=\"length-meter\"]/unitPattern[@count=\"other\"]");
346 
347         // The following are to ensure that we properly generate an example when we have a non-winning value
348         checkValue(
349             "Length m",
350             "〖❬1.5❭ badmeter〗",
351             exampleGenerator,
352             "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"length-meter\"]/unitPattern[@count=\"other\"]",
353             "{0} badmeter");
354 
355         ExampleGenerator exampleGeneratorDe = getExampleGenerator("de");
356         checkValue(
357             "Length m",
358             "〖❬1,5❭ badmeter〗〖❬Anstatt 1,5❭ badmeter❬ …❭〗〖❌  ❬… für 1,5❭ badmeter❬ …❭〗",
359             exampleGeneratorDe,
360             "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"length-meter\"]/unitPattern[@count=\"other\"][@case=\"genitive\"]",
361             "{0} badmeter");
362     }
363 
364     /**
365      * Check that the expected exampleGenerator example is produced for the parameters, with the value coming from the file.
366      */
checkValue(String message, String expected, ExampleGenerator exampleGenerator, String path)367     private void checkValue(String message, String expected,
368         ExampleGenerator exampleGenerator, String path) {
369         checkValue(message, expected, exampleGenerator, path, null);
370     }
371 
372     /**
373      * Check that the expected exampleGenerator example is produced for the parameters
374      */
checkValue(String message, String expected, ExampleGenerator exampleGenerator, String path, String value)375     private void checkValue(String message, String expected,
376         ExampleGenerator exampleGenerator, String path, String value) {
377         final CLDRFile cldrFile = exampleGenerator.getCldrFile();
378         value = value != null ? value : cldrFile.getStringValue(path);
379         String actual = exampleGenerator.getExampleHtml(path, value);
380         assertEquals(cldrFile.getLocaleID() + ": " + message, expected,
381             ExampleGenerator.simplify(actual, false));
382     }
383 
TestCompoundUnit()384     public void TestCompoundUnit() {
385         String[][] tests = {
386             { "per", "LONG", "one", "〖❬1 meter❭ per ❬second❭〗" },
387             { "per", "SHORT", "one", "〖❬1 m❭/❬sec❭〗" },
388             { "per", "NARROW", "one", "〖❬1m❭/❬s❭〗" },
389             { "per", "LONG", "other", "〖❬1.5 meters❭ per ❬second❭〗" },
390             { "per", "SHORT", "other", "〖❬1.5 m❭/❬sec❭〗" },
391             { "per", "NARROW", "other", "〖❬1.5m❭/❬s❭〗" },
392             { "times", "LONG", "one", "〖❬1 newton❭-❬meter❭〗" },
393             { "times", "SHORT", "one", "〖❬1 N❭⋅❬m❭〗" },
394             { "times", "NARROW", "one", "〖❬1N❭⋅❬m❭〗" },
395             { "times", "LONG", "other", "〖❬1.5 newton❭-❬meters❭〗" },
396             { "times", "SHORT", "other", "〖❬1.5 N❭⋅❬m❭〗" },
397             { "times", "NARROW", "other", "〖❬1.5N❭⋅❬m❭〗" },
398         };
399         checkCompoundUnits("en", tests);
400         // reenable these after Arabic has meter translated
401         // String[][] tests2 = {
402         // {"LONG", "few", "〖❬1 meter❭ per ❬second❭〗"},
403         // };
404         // checkCompoundUnits("ar", tests2);
405     }
406 
checkCompoundUnits(String locale, String[][] tests)407     private void checkCompoundUnits(String locale, String[][] tests) {
408         ExampleGenerator exampleGenerator = getExampleGenerator(locale);
409         for (String[] test : tests) {
410             String actual = exampleGenerator.handleCompoundUnit(
411                 UnitLength.valueOf(test[1]),
412                 test[0],
413                 Count.valueOf(test[2]));
414             assertEquals("CompoundUnit", test[3],
415                 ExampleGenerator.simplify(actual, true));
416         }
417     }
418 
TestTranslationPaths()419     public void TestTranslationPaths() {
420         for (String locale : Arrays.asList("en", "el", "ru")) {
421             CLDRFile cldrFile = CLDRConfig.getInstance().getCldrFactory().make(locale, true);
422             ExampleGenerator exampleGenerator = getExampleGenerator(locale);
423 
424             for (UnitPathType pathType : UnitPathType.values()) {
425                 for (String width : Arrays.asList("long", "short", "narrow")) {
426                     if (pathType == UnitPathType.gender && !width.equals("long")) {
427                         continue;
428                     }
429                     for (String unit : pathType.sampleShortUnitType) {
430                         String path = pathType.getTranslationPath(cldrFile, width, unit, "one", "nominative", null);
431                         String value = cldrFile.getStringValue(path);
432                         if (value != null) {
433                             String example = exampleGenerator.getExampleHtml(path, value);
434                             if (assertNotNull(locale + "/" + path, example)) {
435                                 String simplified = ExampleGenerator.simplify(example, false);
436                                 if (showTranslationPaths) {
437                                     warnln(locale + ", " + width + ", " + pathType.toString() + " ==>" + simplified);
438                                 }
439                             } else {
440                                 // for debugging
441                                 example = exampleGenerator.getExampleHtml(path, value);
442                             }
443                         }
444                     }
445                 }
446             }
447         }
448     }
449 
TestCompoundUnit2()450     public void TestCompoundUnit2() {
451         String[][] tests = {
452             { "de", "LONG", "other", "Quadrat{0}", "〖❬1,5 ❭Quadrat❬meter❭〗" },
453 
454             { "en", "SHORT", "one", "z{0}", "〖❬1 ❭z❬m❭〗" },
455             { "en", "LONG", "other", "zetta{0}", "〖❬1.5 ❭zetta❬meters❭〗" },
456 
457             { "en", "SHORT", "one", "{0}²", "〖❬1 m❭²〗" },
458             { "en", "LONG", "other", "square {0}", "〖❬1.5 ❭square ❬meters❭〗" },
459 
460             { "de", "SHORT", "one", "z{0}", "〖❬1 ❭z❬m❭〗" },
461             { "de", "LONG", "other", "Zetta{0}", "〖❬1,5 ❭Zetta❬meter❭〗" },
462 
463             { "de", "SHORT", "one", "{0}²", "〖❬1 m❭²〗" },
464             { "de", "LONG", "other", "Quadrat{0}", "〖❬1,5 ❭Quadrat❬meter❭〗" },
465         };
466         for (String[] test : tests) {
467 
468             ExampleGenerator exampleGenerator = getExampleGenerator(test[0]);
469 
470             String actual = exampleGenerator.handleCompoundUnit1(
471                 UnitLength.valueOf(test[1]),
472                 Count.valueOf(test[2]),
473                 test[3]);
474             assertEquals("CompoundUnit", test[4],
475                 ExampleGenerator.simplify(actual, true));
476         }
477     }
478 
TestCompoundUnit3()479     public void TestCompoundUnit3() {
480         final Factory cldrFactory = CLDRConfig.getInstance().getCldrFactory();
481         String[][] tests = {
482             // locale, path, value, expected-example
483             { "en", "//ldml/units/unitLength[@type=\"long\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1",
484                 "LOCALE", "〖square ❬meters❭〗" }, //
485             { "en", "//ldml/units/unitLength[@type=\"long\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1[@count=\"one\"]",
486                     "LOCALE", "〖❬1 ❭square ❬meter❭〗" }, //
487             { "en", "//ldml/units/unitLength[@type=\"long\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1[@count=\"other\"]",
488                         "LOCALE", "〖❬1.5 ❭square ❬meters❭〗" }, //
489 
490             { "en", "//ldml/units/unitLength[@type=\"narrow\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1",
491                             "LOCALE", "〖❬m❭²〗" },
492             { "en", "//ldml/units/unitLength[@type=\"narrow\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1[@count=\"one\"]",
493                                 "LOCALE", "〖❬1m❭²〗" },
494             { "en", "//ldml/units/unitLength[@type=\"narrow\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1[@count=\"other\"]",
495                                     "LOCALE", "〖❬1.5m❭²〗" },
496 
497             // warning, french patterns has U+00A0 in them
498             { "fr", "//ldml/units/unitLength[@type=\"long\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1",
499                                         "Square {0}", "〖Square ❬mètres❭〗" },
500             { "fr", "//ldml/units/unitLength[@type=\"long\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1[@count=\"one\"]",
501                                             "square {0}", "〖❬1,5 ❭square ❬mètre❭〗" },
502             { "fr", "//ldml/units/unitLength[@type=\"long\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1[@count=\"other\"]",
503                                                 "squares {0}", "〖❬3,5 ❭squares ❬mètres❭〗" },
504 
505             { "fr", "//ldml/units/unitLength[@type=\"narrow\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1",
506                                                     "LOCALE", "〖❬m❭²〗" },
507             { "fr", "//ldml/units/unitLength[@type=\"narrow\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1[@count=\"one\"]",
508                                                         "LOCALE", "〖❬1,5m❭²〗" },
509             { "fr", "//ldml/units/unitLength[@type=\"narrow\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1[@count=\"other\"]",
510                                                             "LOCALE", "〖❬3,5m❭²〗" },
511         };
512 
513         int lineCount = 0;
514         for (String[] test : tests) {
515 
516             final String localeID = test[0];
517             final String xpath = test[1];
518             String value = test[2];
519             String expected = test[3];
520 
521             ExampleGenerator exampleGenerator = getExampleGenerator(localeID);
522 
523             if (value.equals("LOCALE")) {
524                 value = cldrFactory.make(localeID, true).getStringValue(xpath);
525             }
526             String actual = exampleGenerator.getExampleHtml(xpath, value);
527             assertEquals(++lineCount + ") "
528                 + localeID
529                 + ", CompoundUnit3", expected,
530                 ExampleGenerator.simplify(actual, false));
531         }
532 
533     }
534 
535     HashMap<String, ExampleGenerator> ExampleGeneratorCache = new HashMap<>();
536 
getExampleGenerator(String locale)537     private ExampleGenerator getExampleGenerator(String locale) {
538         ExampleGenerator result = ExampleGeneratorCache.get(locale);
539         if (result == null) {
540             final CLDRFile nativeCldrFile = info.getCLDRFile(locale,
541                 true);
542             result = new ExampleGenerator(nativeCldrFile, info.getEnglish(),
543                 CLDRPaths.DEFAULT_SUPPLEMENTAL_DIRECTORY);
544             ExampleGeneratorCache.put(locale, result);
545         }
546         return result;
547     }
548 
TestEllipsis()549     public void TestEllipsis() {
550         ExampleGenerator exampleGenerator = getExampleGenerator("it");
551         String[][] tests = { { "initial", "〖…❬iappone❭〗" },
552             { "medial", "〖❬Svizzer❭…❬iappone❭〗" },
553             { "final", "〖❬Svizzer❭…〗" },
554             { "word-initial", "〖… ❬Giappone❭〗" },
555             { "word-medial", "〖❬Svizzera❭ … ❬Giappone❭〗" },
556             { "word-final", "〖❬Svizzera❭ …〗" }, };
557         for (String[] pair : tests) {
558             checkValue(exampleGenerator, "//ldml/characters/ellipsis[@type=\""
559                 + pair[0] + "\"]", pair[1]);
560         }
561     }
562 
checkValue(ExampleGenerator exampleGenerator, String path, String expected)563     private void checkValue(ExampleGenerator exampleGenerator, String path,
564         String expected) {
565         String value = exampleGenerator.getCldrFile().getStringValue(path);
566         String result = ExampleGenerator.simplify(
567             exampleGenerator.getExampleHtml(path, value), false);
568         assertEquals("Ellipsis", expected, result);
569     }
570 
simplify(String exampleHtml)571     public static String simplify(String exampleHtml) {
572         return ExampleGenerator.simplify(exampleHtml, false);
573     }
574 
TestClip()575     public void TestClip() {
576         assertEquals("Clipping", "bc", ExampleGenerator.clip("abc", 1, 0));
577         assertEquals("Clipping", "ab", ExampleGenerator.clip("abc", 0, 1));
578         assertEquals("Clipping", "b\u0308c\u0308",
579             ExampleGenerator.clip("a\u0308b\u0308c\u0308", 1, 0));
580         assertEquals("Clipping", "a\u0308b\u0308",
581             ExampleGenerator.clip("a\u0308b\u0308c\u0308", 0, 1));
582     }
583 
TestPaths()584     public void TestPaths() {
585         showCldrFile(info.getEnglish());
586         showCldrFile(info.getCLDRFile("fr", true));
587     }
588 
TestMiscPatterns()589     public void TestMiscPatterns() {
590         ExampleGenerator exampleGenerator = getExampleGenerator("it");
591         checkValue(
592             "At least",
593             "〖≥❬99❭〗",
594             exampleGenerator,
595             "//ldml/numbers/miscPatterns[@numberSystem=\"latn\"]/pattern[@type=\"atLeast\"]");
596         checkValue("Range", "〖❬99❭-❬144❭〗", exampleGenerator,
597             "//ldml/numbers/miscPatterns[@numberSystem=\"latn\"]/pattern[@type=\"range\"]");
598         // String actual = exampleGenerator.getExampleHtml(
599         // "//ldml/numbers/miscPatterns[@type=\"arab\"]/pattern[@type=\"atLeast\"]",
600         // "at least {0}", Zoomed.IN);
601         // assertEquals("Invalid format",
602         // "<div class='cldr_example'>at least 99</div>", actual);
603     }
604 
TestPluralSamples()605     public void TestPluralSamples() {
606         ExampleGenerator exampleGenerator = getExampleGenerator("sv");
607         String[][] tests = {
608             {"//ldml/units/unitLength[@type=\"short\"]/unit[@type=\"length-centimeter\"]/unitPattern[@count=\"one\"]",
609                 "Number should be one",
610             "〖❬1❭ cm〗〖❬Jag tror att 1❭ cm❬ är tillräckligt.❭〗"},
611             {"//ldml/numbers/minimalPairs/ordinalMinimalPairs[@ordinal=\"one\"]",
612                 "Ordinal one",
613             "〖Ta ❬1❭:a svängen till höger〗〖❌  Ta ❬3❭:a svängen till höger〗"},
614             {"//ldml/numbers/minimalPairs/ordinalMinimalPairs[@ordinal=\"other\"]",
615                 "Ordinal other",
616             "〖Ta ❬3❭:e svängen till höger〗〖❌  Ta ❬1❭:e svängen till höger〗"},
617         };
618         for (String[] row : tests) {
619             String path = row[0];
620             String message = row[1];
621             String expected = row[2];
622             checkValue(message, expected, exampleGenerator, path);
623         }
624 
625     }
626 
TestLocaleDisplayPatterns()627     public void TestLocaleDisplayPatterns() {
628         ExampleGenerator exampleGenerator = getExampleGenerator("it");
629         String actual = exampleGenerator.getExampleHtml(
630             "//ldml/localeDisplayNames/localeDisplayPattern/localePattern",
631             "{0} [{1}]");
632         assertEquals(
633             "localePattern example faulty",
634             "<div class='cldr_example'><span class='cldr_substituted'>uzbeco</span> [<span class='cldr_substituted'>Afghanistan</span>]</div>"
635                 + "<div class='cldr_example'><span class='cldr_substituted'>uzbeco</span> [<span class='cldr_substituted'>arabo, Afghanistan</span>]</div>"
636                 + "<div class='cldr_example'><span class='cldr_substituted'>uzbeco</span> [<span class='cldr_substituted'>arabo, Afghanistan, Cifre indo-arabe, Fuso orario: Ora Etiopia</span>]</div>",
637                 actual);
638         actual = exampleGenerator
639             .getExampleHtml(
640                 "//ldml/localeDisplayNames/localeDisplayPattern/localeSeparator",
641                 "{0}. {1}");
642         assertEquals(
643             "localeSeparator example faulty",
644             "<div class='cldr_example'><span class='cldr_substituted'>uzbeco (arabo</span>. <span class='cldr_substituted'>Afghanistan)</span></div>"
645                 + "<div class='cldr_example'><span class='cldr_substituted'>uzbeco (arabo</span>. <span class='cldr_substituted'>Afghanistan</span>. <span class='cldr_substituted'>Cifre indo-arabe</span>. <span class='cldr_substituted'>Fuso orario: Ora Etiopia)</span></div>",
646                 actual);
647     }
648 
TestCurrencyFormats()649     public void TestCurrencyFormats() {
650         ExampleGenerator exampleGenerator = getExampleGenerator("it");
651         String actual = simplify(exampleGenerator
652             .getExampleHtml(
653                 "//ldml/numbers/currencyFormats[@numberSystem=\"latn\"]/currencyFormatLength/currencyFormat[@type=\"standard\"]/pattern[@type=\"standard\"]",
654                 "¤ #0.00"));
655         assertEquals("Currency format example faulty",
656             "〖€ ❬1295,00❭〗〖-€ ❬1295,00❭〗", actual);
657     }
658 
TestCurrencyFormatsWithContext()659     public void TestCurrencyFormatsWithContext() {
660         ExampleGenerator exampleGenerator = getExampleGenerator("he");
661         String actual = simplify(exampleGenerator
662             .getExampleHtml(
663                 "//ldml/numbers/currencyFormats[@numberSystem=\"latn\"]/currencyFormatLength/currencyFormat[@type=\"standard\"]/pattern[@type=\"standard\"]",
664                 "‏#,##0.00 ¤;‏-#,##0.00 ¤"));
665         assertEquals("Currency format example faulty",
666             "【‏❬1,295❭.❬00❭ ₪〗【⃪‏❬1,295❭.❬00❭ ₪〗【‏‎-❬1,295❭.❬00❭ ₪〗【⃪‏‎-❬1,295❭.❬00❭ ₪〗【‏❬1,295❭.❬00❭ ILS〗【⃪‏❬1,295❭.❬00❭ ILS〗【‏‎-❬1,295❭.❬00❭ ILS〗【⃪‏‎-❬1,295❭.❬00❭ ILS〗", actual);
667     }
668 
TestDateFormatsWithContext()669     public void TestDateFormatsWithContext() {
670         ExampleGenerator exampleGenerator = getExampleGenerator("ar");
671         String actual = simplify(exampleGenerator
672             .getExampleHtml(
673                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/dateFormats/dateFormatLength[@type=\"short\"]/dateFormat[@type=\"standard\"]/pattern[@type=\"standard\"]",
674                 "d‏/M‏/y"));
675         assertEquals("Currency format example faulty",
676             "【٥‏/٩‏/١٩٩٩〗【⃪٥‏/٩‏/١٩٩٩〗", actual);
677     }
678 
TestSymbols()679     public void TestSymbols() {
680         CLDRFile english = info.getEnglish();
681         ExampleGenerator exampleGenerator = new ExampleGenerator(english,
682             english, CLDRPaths.DEFAULT_SUPPLEMENTAL_DIRECTORY);
683         String actual = exampleGenerator
684             .getExampleHtml(
685                 "//ldml/numbers/symbols[@numberSystem=\"latn\"]/superscriptingExponent",
686                 "x");
687 
688         assertEquals(
689             "superscriptingExponent faulty",
690             "<div class='cldr_example'><span class='cldr_substituted'>1.23456789</span>x10<span class='cldr_substituted'><sup>5</sup></span></div>",
691             actual);
692     }
693 
TestFallbackFormat()694     public void TestFallbackFormat() {
695         ExampleGenerator exampleGenerator = new ExampleGenerator(
696             info.getEnglish(), info.getEnglish(),
697             CLDRPaths.DEFAULT_SUPPLEMENTAL_DIRECTORY);
698         String actual = exampleGenerator.getExampleHtml(
699             "//ldml/dates/timeZoneNames/fallbackFormat", "{1} [{0}]");
700         assertEquals("fallbackFormat faulty", "〖❬Central Time❭ [❬Cancún❭]〗",
701             ExampleGenerator.simplify(actual, false));
702     }
703 
Test4897()704     public void Test4897() {
705         ExampleGenerator exampleGenerator = getExampleGenerator("it");
706         for (String xpath : With.in(exampleGenerator.getCldrFile().iterator(
707             "//ldml/dates/timeZoneNames",
708             exampleGenerator.getCldrFile().getComparator()))) {
709             String value = exampleGenerator.getCldrFile().getStringValue(xpath);
710             String actual = exampleGenerator.getExampleHtml(xpath, value);
711             if (actual == null) {
712                 if (!xpath.contains("singleCountries")
713                     && !xpath.contains("gmtZeroFormat")) {
714                     errln("Null value for " + value + "\t" + xpath);
715                     // for debugging
716                     exampleGenerator.getExampleHtml(xpath, value);
717                 }
718             } else {
719                 logln(actual + "\t" + value + "\t" + xpath);
720             }
721         }
722     }
723 
Test4528()724     public void Test4528() {
725         String[][] testPairs = {
726             {
727                 "//ldml/numbers/currencies/currency[@type=\"BMD\"]/displayName[@count=\"other\"]",
728             "〖❬1,23 ❭dollari delle Bermuda〗〖❬0,00 ❭dollari delle Bermuda〗" },
729             {
730                 "//ldml/numbers/currencyFormats[@numberSystem=\"latn\"]/unitPattern[@count=\"other\"]",
731             "〖❬1,23❭ ❬dollari statunitensi❭〗〖❬1,23❭ ❬euro❭〗〖❬0,00❭ ❬dollari statunitensi❭〗〖❬0,00❭ ❬euro❭〗" },
732             { "//ldml/numbers/currencies/currency[@type=\"BMD\"]/symbol",
733             "〖❬123.456,79 ❭BMD〗" }, };
734 
735         ExampleGenerator exampleGenerator = getExampleGenerator("it");
736         for (String[] testPair : testPairs) {
737             String xpath = testPair[0];
738             String expected = testPair[1];
739             String value = exampleGenerator.getCldrFile().getStringValue(xpath);
740             String actual = simplify(exampleGenerator.getExampleHtml(xpath, value));
741             assertEquals("specifics", expected, actual);
742         }
743     }
744 
Test4607()745     public void Test4607() {
746         String[][] testPairs = {
747             {
748                 "//ldml/numbers/decimalFormats[@numberSystem=\"latn\"]/decimalFormatLength[@type=\"long\"]/decimalFormat[@type=\"standard\"]/pattern[@type=\"1000\"][@count=\"one\"]",
749             "<div class='cldr_example'><span class='cldr_substituted'>1</span> thousand</div>" },
750             {
751                 "//ldml/numbers/percentFormats[@numberSystem=\"latn\"]/percentFormatLength/percentFormat[@type=\"standard\"]/pattern[@type=\"standard\"]",
752                 "<div class='cldr_example'><span class='cldr_substituted'>5</span>%</div>"
753                     + "<div class='cldr_example'><span class='cldr_substituted'>12,345</span>,<span class='cldr_substituted'>679</span>%</div>"
754                     + "<div class='cldr_example'>-<span class='cldr_substituted'>12,345</span>,<span class='cldr_substituted'>679</span>%</div>" } };
755         final CLDRFile nativeCldrFile = info.getEnglish();
756         ExampleGenerator exampleGenerator = new ExampleGenerator(
757             info.getEnglish(), info.getEnglish(),
758             CLDRPaths.DEFAULT_SUPPLEMENTAL_DIRECTORY);
759         for (String[] testPair : testPairs) {
760             String xpath = testPair[0];
761             String expected = testPair[1];
762             String value = nativeCldrFile.getStringValue(xpath);
763             String actual = exampleGenerator.getExampleHtml(xpath, value);
764             assertEquals("specifics", expected, actual);
765         }
766     }
767 
showCldrFile(final CLDRFile cldrFile)768     private void showCldrFile(final CLDRFile cldrFile) {
769         ExampleGenerator exampleGenerator = new ExampleGenerator(cldrFile,
770             info.getEnglish(), CLDRPaths.DEFAULT_SUPPLEMENTAL_DIRECTORY);
771         checkPathValue(
772             exampleGenerator,
773             "//ldml/dates/calendars/calendar[@type=\"chinese\"]/dateFormats/dateFormatLength[@type=\"full\"]/dateFormat[@type=\"standard\"]/pattern[@type=\"standard\"][@draft=\"unconfirmed\"]",
774             "EEEE d MMMMl y'x'G", null);
775 
776         for (String xpath : cldrFile.fullIterable()) {
777             if (xpath.endsWith("/alias")) {
778                 continue;
779             }
780             String value = cldrFile.getStringValue(xpath);
781             checkPathValue(exampleGenerator, xpath, value, null);
782             // remove this, no longer used
783 //            if (xpath.contains("count=\"one\"")) {
784 //                String xpath2 = xpath.replace("count=\"one\"", "count=\"1\"");
785 //                checkPathValue(exampleGenerator, xpath2, value, null);
786 //            }
787         }
788     }
789 
checkPathValue(ExampleGenerator exampleGenerator, String xpath, String value, String expected)790     private void checkPathValue(ExampleGenerator exampleGenerator,
791         String xpath, String value, String expected) {
792         Set<String> alreadySeen = new HashSet<>();
793         try {
794             String text = exampleGenerator.getExampleHtml(xpath, value);
795             if (text == null) {
796                 // skip
797             } else if (text.contains("Exception")) {
798                 errln("getExampleHtml\t" + text);
799             } else if (!alreadySeen.contains(text)) {
800                 if (text.contains("n/a")) {
801                     if (text.contains("&lt;")) {
802                         errln("Text not quoted correctly:" + "\t" + text
803                             + "\t" + xpath);
804                     }
805                 }
806                 boolean skipLog = false;
807                 if (expected != null) {
808                     String simplified = ExampleGenerator.simplify(text, false);
809                     // redo for debugging
810                     text = exampleGenerator.getExampleHtml(xpath, value);
811                     skipLog = !assertEquals("Example text for «" + value + "»", expected, simplified);
812                 }
813                 if (!skipLog) {
814                     logln("getExampleHtml\t" + text + "\t"
815                         + xpath);
816                 }
817                 alreadySeen.add(text);
818             }
819         } catch (Exception e) {
820             errln("getExampleHtml\t" + e.getMessage());
821         }
822 
823         try {
824             String text = exampleGenerator.getHelpHtml(xpath, value);
825             if (text == null) {
826                 // skip
827             } else if (text.contains("Exception")) {
828                 errln("getHelpHtml\t" + text);
829             } else {
830                 logln("getExampleHtml(help)\t" + "\t" + text + "\t" + xpath);
831             }
832         } catch (Exception e) {
833             if (false) {
834                 e.printStackTrace();
835             }
836             errln("getHelpHtml\t" + e.getMessage());
837         }
838     }
839 
TestCompactPlurals()840     public void TestCompactPlurals() {
841         checkCompactExampleFor("de", Count.one, "〖❬1❭ Mio. €〗", "short", "currency", "000000");
842         checkCompactExampleFor("de", Count.other, "〖❬2❭ Mio. €〗", "short", "currency", "000000");
843         checkCompactExampleFor("de", Count.one, "〖❬12❭ Mio. €〗", "short", "currency", "0000000");
844         checkCompactExampleFor("de", Count.other, "〖❬10❭ Mio. €〗", "short", "currency", "0000000");
845 
846         checkCompactExampleFor("cs", Count.many, "〖❬1,1❭ milionu〗", "long", "decimal", "000000");
847         checkCompactExampleFor("pl", Count.other, "〖❬1,1❭ miliona〗", "long", "decimal", "000000");
848     }
849 
checkCompactExampleFor(String localeID, Count many, String expected, String longVsShort, String decimalVsCurrency, String zeros)850     private void checkCompactExampleFor(String localeID, Count many,
851         String expected, String longVsShort, String decimalVsCurrency, String zeros) {
852         CLDRFile cldrFile = info.getCLDRFile(localeID, true);
853         ExampleGenerator exampleGenerator = new ExampleGenerator(cldrFile,
854             info.getEnglish(), CLDRPaths.DEFAULT_SUPPLEMENTAL_DIRECTORY);
855         String path = "//ldml/numbers/"
856             + decimalVsCurrency + "Formats[@numberSystem=\"latn\"]"
857             + "/" + decimalVsCurrency + "FormatLength[@type=\"" + longVsShort + "\"]"
858             + "/" + decimalVsCurrency + "Format[@type=\"standard\"]"
859             + "/pattern[@type=\"1" + zeros + "\"][@count=\"" + many + "\"]";
860         checkPathValue(exampleGenerator, path, cldrFile.getStringValue(path),
861             expected);
862     }
863 
864 //ldml/numbers/currencyFormats[@numberSystem="latn"]/currencyFormatLength[@type="short"]/currencyFormat[@type="standard"]/pattern[@type="1000"][@count="one"]
865 
TestDayPeriods()866     public void TestDayPeriods() {
867         //checkDayPeriod("da", "format", "morning1", "〖05:00 – 10:00〗〖❬7:30❭ morgens〗");
868         checkDayPeriod("zh", "format", "morning1", "〖05:00 – 08:00⁻〗〖清晨❬6:30❭〗");
869 
870         checkDayPeriod("de", "format", "morning1", "〖05:00 – 10:00⁻〗〖❬7:30 ❭morgens〗");
871         checkDayPeriod("de", "stand-alone", "morning1", "〖05:00 – 10:00⁻〗");
872         checkDayPeriod("de", "format", "morning2", "〖10:00 – 12:00⁻〗〖❬11:00 ❭vormittags〗");
873         checkDayPeriod("de", "stand-alone", "morning2", "〖10:00 – 12:00⁻〗");
874         checkDayPeriod("pl", "format", "morning1", "〖06:00 – 10:00⁻〗〖❬8:00 ❭rano〗");
875         checkDayPeriod("pl", "stand-alone", "morning1", "〖06:00 – 10:00⁻〗");
876 
877         checkDayPeriod("en", "format", "night1", "〖00:00 – 06:00⁻; 21:00 – 24:00⁻〗〖❬3:00 ❭at night〗");
878         checkDayPeriod("en", "stand-alone", "night1", "〖00:00 – 06:00⁻; 21:00 – 24:00⁻〗");
879 
880         checkDayPeriod("en", "format", "noon", "〖12:00〗〖❬12:00 ❭noon〗");
881         checkDayPeriod("en", "format", "midnight", "〖00:00〗〖❬12:00 ❭midnight〗");
882         checkDayPeriod("en", "format", "am", "〖00:00 – 12:00⁻〗〖❬6:00 ❭AM〗");
883         checkDayPeriod("en", "format", "pm", "〖12:00 – 24:00⁻〗〖❬6:00 ❭PM〗");
884     }
885 
checkDayPeriod(String localeId, String type, String dayPeriodCode, String expected)886     private void checkDayPeriod(String localeId, String type, String dayPeriodCode, String expected) {
887         CLDRFile cldrFile = info.getCLDRFile(localeId, true);
888         ExampleGenerator exampleGenerator = new ExampleGenerator(cldrFile, info.getEnglish(), CLDRPaths.DEFAULT_SUPPLEMENTAL_DIRECTORY);
889         String prefix = "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/dayPeriods/dayPeriodContext[@type=\"";
890         String suffix = "\"]/dayPeriodWidth[@type=\"wide\"]/dayPeriod[@type=\""
891             + dayPeriodCode
892             + "\"]";
893         String path = prefix + type + suffix;
894         checkPathValue(exampleGenerator, path, cldrFile.getStringValue(path), expected);
895     }
896 
897     /**
898      * Test that getExampleHtml returns same output for same input regardless of
899      * order in which it is called with different inputs.
900      *
901      * Calling getExampleHtml with a particular path and value presumably should NOT depend on the
902      * history of paths and/or values it has been called with previously.
903      *
904      * We formerly got different examples for SPECIAL_PATH depending on whether an example was
905      * first gotten for USE_EVIL_PATH.
906      *
907      * Without EVIL_PATH, got right value for SPECIAL_PATH:
908      * <div class='cldr_example'><span class='cldr_substituted'>123 456,79 </span>€</div>
909      *
910      * With EVIL_PATH, got wrong value for SPECIAL_PATH:
911      * <div class='cldr_example'><span class='cldr_substituted'>123457 k </span>€</div>
912      *
913      * This was fixed by doing clone() before returning a DecimalFormat in ICUServiceBuilder.
914      * Reference: https://unicode-org.atlassian.net/browse/CLDR-13375.
915      *
916      * @throws IOException
917      */
TestExampleGeneratorConsistency()918     public void TestExampleGeneratorConsistency() throws IOException {
919         final String EVIL_PATH = "//ldml/numbers/currencyFormats/currencyFormatLength[@type=\"short\"]/currencyFormat[@type=\"standard\"]/pattern[@type=\"10000\"][@count=\"one\"]";
920         final String SPECIAL_PATH = "//ldml/numbers/currencies/currency[@type=\"EUR\"]/symbol";
921         final String EXPECTED = "123 456,79";
922 
923         final CLDRFile cldrFile = info.getCLDRFile("fr", true);
924         final ExampleGenerator eg = new ExampleGenerator(cldrFile, info.getEnglish(), CLDRPaths.DEFAULT_SUPPLEMENTAL_DIRECTORY);
925 
926         final String evilValue = cldrFile.getStringValue(EVIL_PATH);
927         final String specialValue = cldrFile.getStringValue(SPECIAL_PATH);
928 
929         eg.getExampleHtml(EVIL_PATH, evilValue);
930         final String specialExample = eg.getExampleHtml(SPECIAL_PATH, specialValue);
931 
932         if (!specialExample.contains(EXPECTED)) {
933             errln("Expected example to contain " + EXPECTED + "; got " + specialExample);
934         }
935     }
936 
TestInflectedUnitExamples()937     public void TestInflectedUnitExamples() {
938         String[][] deTests = {
939             {"one", "accusative", "〖❬1❭ Tag〗〖❬… für 1❭ Tag❬ …❭〗〖❌  ❬Anstatt 1❭ Tag❬ …❭〗"},
940             {"one", "dative", "〖❬1❭ Tag〗〖❬… mit 1❭ Tag❬ …❭〗〖❌  ❬Anstatt 1❭ Tag❬ …❭〗"},
941             {"one", "genitive", "〖❬1❭ Tages〗〖❬Anstatt 1❭ Tages❬ …❭〗〖❌  ❬… für 1❭ Tages❬ …❭〗"},
942             {"one", "nominative", "〖❬1❭ Tag〗〖❬1❭ Tag❬ kostet (kosten) € 3,50.❭〗〖❌  ❬Anstatt 1❭ Tag❬ …❭〗"},
943 
944             {"other", "accusative", "〖❬1,5❭ Tage〗〖❬… für 1,5❭ Tage❬ …❭〗〖❌  ❬… mit 1,5❭ Tage❬ …❭〗"},
945             {"other", "dative", "〖❬1,5❭ Tagen〗〖❬… mit 1,5❭ Tagen❬ …❭〗〖❌  ❬… für 1,5❭ Tagen❬ …❭〗"},
946             {"other", "genitive", "〖❬1,5❭ Tage〗〖❬Anstatt 1,5❭ Tage❬ …❭〗〖❌  ❬… mit 1,5❭ Tage❬ …❭〗"},
947             {"other", "nominative", "〖❬1,5❭ Tage〗〖❬1,5❭ Tage❬ kostet (kosten) € 3,50.❭〗〖❌  ❬… mit 1,5❭ Tage❬ …❭〗"},
948         };
949         checkInflectedUnitExamples("de", deTests);
950         String[][] elTests = {
951             {"one", "accusative", "〖❬1❭ ημέρα〗〖❬… ανά 1❭ ημέρα❬ …❭〗〖❌  ❬… αξίας 1❭ ημέρα❬ …❭〗"},
952             {"one", "genitive", "〖❬1❭ ημέρας〗〖❬… αξίας 1❭ ημέρας❬ …❭〗〖❌  ❬… ανά 1❭ ημέρας❬ …❭〗"},
953             {"one", "nominative", "〖❬1❭ ημέρα〗〖❬Η απόσταση είναι 1❭ ημέρα❬ …❭〗〖❌  ❬… αξίας 1❭ ημέρα❬ …❭〗"},
954             {"other", "accusative", "〖❬0,9❭ ημέρες〗〖❬… ανά 0,9❭ ημέρες❬ …❭〗〖❌  ❬… αξίας 0,9❭ ημέρες❬ …❭〗"},
955             {"other", "genitive", "〖❬0,9❭ ημερών〗〖❬… αξίας 0,9❭ ημερών❬ …❭〗〖❌  ❬… ανά 0,9❭ ημερών❬ …❭〗"},
956             {"other", "nominative", "〖❬0,9❭ ημέρες〗〖❬Η απόσταση είναι 0,9❭ ημέρες❬ …❭〗〖❌  ❬… αξίας 0,9❭ ημέρες❬ …❭〗"},
957         };
958         checkInflectedUnitExamples("el", elTests);
959     }
960 
checkInflectedUnitExamples(final String locale, String[][] tests)961     private void checkInflectedUnitExamples(final String locale, String[][] tests) {
962         final CLDRFile cldrFile = info.getCLDRFile(locale, true);
963         ExampleGenerator exampleGenerator = getExampleGenerator(locale);
964         String pattern = "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"duration-day\"]/unitPattern[@count=\"COUNT\"][@case=\"CASE\"]";
965         boolean showWorkingExamples = false;
966         for (String[] row : tests) {
967             String path = pattern.replace("COUNT", row[0]).replace("CASE", row[1]);
968             String expected = row[2];
969             String value = cldrFile.getStringValue(path);
970             String actualRaw = exampleGenerator.getExampleHtml(path, value);
971             String actual = ExampleGenerator.simplify(actualRaw, false);
972             showWorkingExamples |= !assertEquals(row[0] + ", " + row[1], expected, actual);
973         }
974 
975         // If a test fails, verbose will regenerate what the code thinks they should be.
976         // Review for correctness, and then replace the test cases
977 
978         if (showWorkingExamples) {
979             System.out.println("## The following would satisfy the test, but check to make sure the expected values are all correct!");
980             PluralInfo pluralInfo = SDI.getPlurals(PluralType.cardinal, locale);
981             GrammarInfo grammarInfo = SDI.getGrammarInfo(locale);
982             final Collection<String> grammaticalValues2 = grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalCase, GrammaticalScope.units);
983 
984             for (Count plural : pluralInfo.getCounts()) {
985                 for (String grammaticalCase : grammaticalValues2) {
986                     String path = pattern.replace("COUNT", plural.toString()).replace("CASE", grammaticalCase);
987                     String value = cldrFile.getStringValue(path);
988                     String actualRaw = exampleGenerator.getExampleHtml(path, value);
989                     String actual = ExampleGenerator.simplify(actualRaw, false);
990                     System.out.println(
991                         "{\"" + plural + "\", "
992                             + "\"" + grammaticalCase + "\", "
993                             + "\"" + actual + "\"},");
994                 }
995             }
996         }
997     }
998 
TestMinimalPairExamples()999     public void TestMinimalPairExamples() {
1000         String[][] tests = {
1001             {"//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"one\"]", "〖❬1❭ Tag〗〖❌  ❬2❭ Tag〗"},
1002             {"//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"other\"]", "〖❬2❭ Tage〗〖❌  ❬1❭ Tage〗"},
1003             {"//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"accusative\"]", "〖… für ❬1 metrische Pint❭ …〗〖❌  … für ❬1 metrischen Pint❭ …〗"},
1004             {"//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"dative\"]", "〖… mit ❬1 metrischen Pint❭ …〗〖❌  … mit ❬1 metrische Pint❭ …〗"},
1005             {"//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"genitive\"]", "〖Anstatt ❬1 metrischen Pints❭ …〗〖❌  Anstatt ❬1 metrische Pint❭ …〗"},
1006             {"//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"nominative\"]", "〖❬2 metrische Pints❭ kostet (kosten) € 3,50.〗〖❌  ❬1 metrische Pint❭ kostet (kosten) € 3,50.〗"},
1007             {"//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"feminine\"]", "〖Die ❬Stunde❭ ist …〗〖❌  Die ❬Zentimeter❭ ist …〗"},
1008             {"//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"masculine\"]", "〖Der ❬Zentimeter❭ ist …〗〖❌  Der ❬Stunde❭ ist …〗"},
1009             {"//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"neuter\"]", "〖Das ❬Jahrhundert❭ ist …〗〖❌  Das ❬Stunde❭ ist …〗"},
1010         };
1011         checkMinimalPairExamples("de", tests);
1012 
1013         String[][] elTests = {
1014             {"//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"one\"]", "〖❬1❭ ημέρα〗〖❌  ❬2❭ ημέρα〗"},
1015             {"//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"other\"]", "〖❬2❭ ημέρες〗〖❌  ❬1❭ ημέρες〗"},
1016 
1017             {"//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"accusative\"]", "〖… ανά ❬1 τόνο❭ …〗〖❌  … ανά ❬1 τόνου❭ …〗"},
1018             {"//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"genitive\"]", "〖… αξίας ❬1 τόνου❭ …〗〖❌  … αξίας ❬1 τόνο❭ …〗"},
1019             {"//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"nominative\"]", "〖Η απόσταση είναι ❬2 τόνοι❭ …〗〖❌  Η απόσταση είναι ❬1 τόνο❭ …〗"},
1020 
1021             {"//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"feminine\"]", "〖Η ❬ημέρα❭ είναι〗〖❌  Η ❬αιώνας❭ είναι〗"},
1022             {"//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"masculine\"]", "〖Ο ❬αιώνας❭ θα είναι〗〖❌  Ο ❬ημέρα❭ θα είναι〗"},
1023             {"//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"neuter\"]", "〖Το ❬εκατοστό❭ ήταν〗〖❌  Το ❬ημέρα❭ ήταν〗"},
1024         };
1025         checkMinimalPairExamples("el", elTests);
1026     }
1027 
checkMinimalPairExamples(final String locale, String[][] tests)1028     private void checkMinimalPairExamples(final String locale, String[][] tests) {
1029         final CLDRFile cldrFile = info.getCLDRFile(locale, true);
1030         ExampleGenerator exampleGenerator = getExampleGenerator(locale);
1031         boolean showWorkingExamples = false;
1032         for (String[] row : tests) {
1033             String path = row[0];
1034             String expected = row[1];
1035             String value = cldrFile.getStringValue(path);
1036             String actualRaw = exampleGenerator.getExampleHtml(path, value);
1037             String actual = ExampleGenerator.simplify(actualRaw, false);
1038             showWorkingExamples |= !assertEquals(row[0] + ", " + row[1], expected, actual);
1039         }
1040 
1041         // If a test fails, verbose will regenerate what the code thinks they should be.
1042         // Review for correctness, and then replace the test cases
1043 
1044         if (showWorkingExamples) {
1045             System.out.println("## The following would satisfy the test, but check to make sure the expected values are all correct!");
1046             PluralInfo pluralInfo = SDI.getPlurals(PluralType.cardinal, locale);
1047             ArrayList<String> paths = new ArrayList<>();
1048 
1049             for (Count plural : pluralInfo.getCounts()) {
1050                 paths.add("//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"" + plural +  "\"]");
1051             }
1052             GrammarInfo grammarInfo = SDI.getGrammarInfo(locale);
1053             for (String grammaticalValues : grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalCase, GrammaticalScope.units)) {
1054                 paths.add("//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"" + grammaticalValues +  "\"]");
1055             }
1056             for (String grammaticalValues : grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalGender, GrammaticalScope.units)) {
1057                 paths.add("//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"" + grammaticalValues +  "\"]");
1058             }
1059             for (String path : paths) {
1060                 String value = cldrFile.getStringValue(path);
1061                 String actualRaw = exampleGenerator.getExampleHtml(path, value);
1062                 String actual = ExampleGenerator.simplify(actualRaw, false);
1063                 System.out.println("{\"" + path.replace("\"", "\\\"") + "\", \"" + actual + "\"},");
1064             }
1065         }
1066     }
1067 
1068     /** Test the production of minimal pair examples, to make sure we get no exceptions.
1069      * If -v, then generates lines for spreadsheet survey
1070      */
TestListMinimalPairExamples()1071     public void TestListMinimalPairExamples() {
1072         Set<String> localesWithGrammar = SDI.hasGrammarInfo();
1073         if (isVerbose()) {
1074             System.out.println("\nLC\tLocale\tType\tCode\tCurrent Pattern\tVerify this is correct!\tVerify this is wrong!");
1075         }
1076         final String unused = "∅";
1077         List<String> pluralSheet = new ArrayList();
1078         for (String locale : localesWithGrammar) {
1079             final CLDRFile cldrFile = info.getCLDRFile(locale, true);
1080             ExampleGenerator exampleGenerator = getExampleGenerator(locale);
1081 
1082             PluralInfo pluralInfo = SDI.getPlurals(PluralType.cardinal, cldrFile.getLocaleID());
1083             Map<String, Pair<String,String>> paths = new LinkedHashMap<>();
1084 
1085             Set<Count> counts = pluralInfo.getCounts();
1086             if (counts.size() > 1) {
1087                 for (Count plural : counts) {
1088                     paths.put("//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"" + plural +  "\"]", Pair.of("plural",plural.toString()));
1089                 }
1090             }
1091             GrammarInfo grammarInfo = SDI.getGrammarInfo(locale);
1092             Collection<String> unitCases = grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalCase, GrammaticalScope.units);
1093             Collection<String> generalCasesRaw = grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalCase, GrammaticalScope.general);
1094             Collection<CaseValues> generalCases = generalCasesRaw.stream().map(x -> CaseValues.valueOf(x)).collect(Collectors.toCollection(TreeSet::new));
1095             for (CaseValues unitCase0 : generalCases) {
1096                 String unitCase = unitCase0.toString();
1097                 paths.put((unitCases.contains(unitCase) ? "" : unused) + "//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"" + unitCase +  "\"]",
1098                     Pair.of("case",unitCase));
1099             }
1100             Collection<String> unitGenders = grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalGender, GrammaticalScope.units);
1101             Collection<String> generalGenders = grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalGender, GrammaticalScope.general);
1102             for (String unitGender : generalGenders) {
1103                 paths.put((unitGenders.contains(unitGender) ? "" : unused) + "//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"" + unitGender +  "\"]",
1104                     Pair.of("gender",unitGender));
1105             }
1106             String localeName = CLDRConfig.getInstance().getEnglish().getName(locale);
1107             boolean pluralOnly = true;
1108             if (paths.isEmpty()) {
1109                 pluralSheet.add(locale
1110                     + "\t" + localeName
1111                     + "\t" + "N/A"
1112                     + "\t" + "N/A"
1113                     + "\t" + "N/A"
1114                     );
1115             } else {
1116                 for (Entry<String, Pair<String, String>> pathAndLabel : paths.entrySet()) {
1117                     String path = pathAndLabel.getKey();
1118                     String label = pathAndLabel.getValue().getFirst();
1119                     String code = pathAndLabel.getValue().getSecond();
1120                     if (!label.equals("plural")) {
1121                         pluralOnly = false;
1122                     }
1123                 }
1124                 String lastLabel = "";
1125                 for (Entry<String, Pair<String, String>> pathAndLabel : paths.entrySet()) {
1126                     String path = pathAndLabel.getKey();
1127                     String label = pathAndLabel.getValue().getFirst();
1128                     String code = pathAndLabel.getValue().getSecond();
1129                     String pattern = "";
1130                     String examples = "";
1131                     if (!label.equals(lastLabel)) {
1132                         lastLabel = label;
1133                         if (!pluralOnly) {
1134                             if (isVerbose()) {
1135                                 System.out.println();
1136                             }
1137                         }
1138                     }
1139                     if (path.startsWith(unused)) {
1140                         pattern = "��  Not used with formatted units";
1141                     } else {
1142                         pattern = cldrFile.getStringValue(path);
1143                         if (pattern == null) {
1144                             warnln("Missing ExampleGenerator html example for " + locale + "(" + localeName + "): " + path);
1145                             continue;
1146                         }
1147                         String actualRaw = exampleGenerator.getExampleHtml(path, pattern);
1148                         String actualSimplified = ExampleGenerator.simplify(actualRaw, false);
1149                         examples = actualSimplified
1150                             .replace("〗〖", "\t")
1151                             .replace("〗", "")
1152                             .replace("〖", "")
1153                             ;
1154                         List<String> exampleList = com.google.common.base.Splitter.on('\t').trimResults().splitToList(examples);
1155                         final int exampleListSize = exampleList.size();
1156                         switch(exampleListSize) {
1157                         case 2: // ok
1158                             break;
1159                         case 1:
1160                             warnln("Expecting exactly 2 examples: " + exampleList + ", but got " + exampleListSize);
1161                             break;
1162                         default:
1163                             errln("Expecting exactly 2 examples: " + exampleList + ", but got " + exampleListSize);
1164                             break;
1165                         }
1166                         StringBuilder exampleBuffer = new StringBuilder();
1167                         for (String exampleItem : exampleList) {
1168                             if (exampleItem.contains("❬null❭") || exampleItem.contains("❬n/a❭")) {
1169                                 boolean bad = (exampleItem.contains("❌"));
1170                                 exampleItem = "��  No unit available";
1171                                 if (bad) {
1172                                     exampleItem = "❌  " + exampleItem;
1173                                 }
1174                             }
1175                             if (exampleBuffer.length() != 0) {
1176                                 exampleBuffer.append('\t');
1177                             }
1178                             exampleBuffer.append(exampleItem);
1179                         }
1180                         examples = exampleBuffer.toString();
1181                     }
1182                     String line = (locale
1183                         + "\t" + localeName
1184                         + "\t" + label
1185                         + "\t" + code
1186                         + "\t" + pattern
1187                         + "\t" + examples);
1188                     if (pluralOnly) {
1189                         pluralSheet.add(line);
1190                     } else {
1191                         if (isVerbose()) {
1192                             System.out.println(line);
1193                         }
1194                     }
1195                 }
1196             }
1197             if (pluralOnly) {
1198                 pluralSheet.add("");
1199             } else if (isVerbose()) {
1200                 System.out.println();
1201             }
1202         }
1203         if (isVerbose()) {
1204             System.out.println("#################### Plural Only ###################");
1205             for (String line : pluralSheet) {
1206                 System.out.println(line);
1207             }
1208         }
1209     }
1210 }
1211