• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.unicode.cldr.unittest;
2 
3 import com.google.common.base.Joiner;
4 import com.google.common.base.Objects;
5 import com.google.common.base.Splitter;
6 import com.google.common.collect.ComparisonChain;
7 import com.google.common.collect.ImmutableSet;
8 import com.google.common.collect.Iterables;
9 import com.google.common.collect.Multimap;
10 import com.google.common.collect.TreeMultimap;
11 import com.ibm.icu.dev.test.TestFmwk;
12 import com.ibm.icu.impl.Relation;
13 import java.io.IOException;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.Date;
18 import java.util.HashMap;
19 import java.util.HashSet;
20 import java.util.LinkedHashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Map.Entry;
24 import java.util.Set;
25 import java.util.TreeMap;
26 import java.util.TreeSet;
27 import java.util.stream.Collectors;
28 import org.unicode.cldr.test.CheckCLDR.InputMethod;
29 import org.unicode.cldr.test.CheckCLDR.Phase;
30 import org.unicode.cldr.test.CheckCLDR.StatusAction;
31 import org.unicode.cldr.test.ExampleGenerator;
32 import org.unicode.cldr.test.ExampleGenerator.UnitLength;
33 import org.unicode.cldr.unittest.TestCheckCLDR.DummyPathValueInfo;
34 import org.unicode.cldr.util.CLDRConfig;
35 import org.unicode.cldr.util.CLDRFile;
36 import org.unicode.cldr.util.CLDRInfo.UserInfo;
37 import org.unicode.cldr.util.CLDRLocale;
38 import org.unicode.cldr.util.CldrUtility;
39 import org.unicode.cldr.util.Counter;
40 import org.unicode.cldr.util.DtdData;
41 import org.unicode.cldr.util.DtdType;
42 import org.unicode.cldr.util.Factory;
43 import org.unicode.cldr.util.GrammarInfo;
44 import org.unicode.cldr.util.GrammarInfo.CaseValues;
45 import org.unicode.cldr.util.GrammarInfo.GrammaticalFeature;
46 import org.unicode.cldr.util.GrammarInfo.GrammaticalScope;
47 import org.unicode.cldr.util.GrammarInfo.GrammaticalTarget;
48 import org.unicode.cldr.util.Level;
49 import org.unicode.cldr.util.Organization;
50 import org.unicode.cldr.util.Pair;
51 import org.unicode.cldr.util.PathHeader;
52 import org.unicode.cldr.util.PathHeader.BaseUrl;
53 import org.unicode.cldr.util.PathHeader.PageId;
54 import org.unicode.cldr.util.PathHeader.SectionId;
55 import org.unicode.cldr.util.PathStarrer;
56 import org.unicode.cldr.util.SupplementalDataInfo;
57 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo;
58 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count;
59 import org.unicode.cldr.util.SupplementalDataInfo.PluralType;
60 import org.unicode.cldr.util.UnitPathType;
61 import org.unicode.cldr.util.VoteResolver.VoterInfo;
62 import org.unicode.cldr.util.With;
63 import org.unicode.cldr.util.XPathParts;
64 
65 public class TestExampleGenerator extends TestFmwk {
66 
67     private static final String SKIP = "SKIP";
68 
69     private static final Joiner CR_TAB2_JOINER = Joiner.on("\n\t\t");
70 
71     private static final boolean CHECK_ROW_ACTION = false;
72 
73     private static final Splitter SLASH2_SPLITTER =
74             Splitter.on("//").omitEmptyStrings().trimResults();
75 
76     private static final Joiner TAB_JOINER = Joiner.on('\t');
77 
78     boolean showTranslationPaths =
79             CldrUtility.getProperty("TestExampleGenerator:showTranslationPaths", false);
80 
81     private static final SupplementalDataInfo SDI = SupplementalDataInfo.getInstance();
82     CLDRConfig info = CLDRConfig.getInstance();
83 
main(String[] args)84     public static void main(String[] args) {
85         new TestExampleGenerator().run(args);
86     }
87 
testCurrency()88     public void testCurrency() {
89         String[][] tests = {
90             {
91                 "fr",
92                 "one",
93                 "〖❬1,23 ❭value-one〗〖❬0,00 ❭value-one〗",
94                 "〖❬1,23❭_❬dollar des États-Unis❭〗〖❬1,23❭_❬euro❭〗〖❬0,00❭_❬dollar des États-Unis❭〗〖❬0,00❭_❬euro❭〗"
95             },
96             {
97                 "fr",
98                 "other",
99                 "〖❬2,34 ❭value-other〗〖❬3,45 ❭value-other〗",
100                 "〖❬2,34❭_❬dollars des États-Unis❭〗〖❬2,34❭_❬euros❭〗〖❬3,45❭_❬dollars des États-Unis❭〗〖❬3,45❭_❬euros❭〗"
101             },
102             {"en", "one", "〖❬1 ❭Bermudan dollar〗", "〖❬1❭ ❬US dollar❭〗〖❬1❭ ❬euro❭〗"},
103             {
104                 "en",
105                 "other",
106                 "〖❬1.23 ❭Bermudan dollars〗〖❬0.00 ❭Bermudan dollars〗",
107                 "〖❬1.23❭ ❬US dollars❭〗〖❬1.23❭ ❬euros❭〗〖❬0.00❭ ❬US dollars❭〗〖❬0.00❭ ❬euros❭〗"
108             },
109         };
110         String sampleCurrencyPatternPrefix =
111                 "//ldml/numbers/currencyFormats[@numberSystem=\"latn\"]/unitPattern[@count=\"";
112         String sampleCurrencyPrefix =
113                 "//ldml/numbers/currencies/currency[@type=\"BMD\"]/displayName[@count=\"";
114         String sampleTemplateSuffix = "\"]";
115 
116         for (String[] row : tests) {
117             ExampleGenerator exampleGenerator = getExampleGenerator(row[0]);
118             String value = "value-" + row[1];
119 
120             String path = sampleCurrencyPrefix + row[1] + sampleTemplateSuffix;
121             String result =
122                     ExampleGenerator.simplify(exampleGenerator.getExampleHtml(path, value), false);
123             assertEquals(row[0] + "-" + row[1] + "-BMD", row[2], result);
124 
125             value = "{0}_{1}";
126             path = sampleCurrencyPatternPrefix + row[1] + sampleTemplateSuffix;
127             result = ExampleGenerator.simplify(exampleGenerator.getExampleHtml(path, value), false);
128             assertEquals(row[0] + "-" + row[1] + "-pat", row[3], result);
129         }
130     }
131 
132     /**
133      * Only add to this if the example should NEVER appear. <br>
134      * WARNING - do not disable the test by putting in too broad a match. Make sure the paths are
135      * reasonably granular.
136      */
137     static final Set<String> DELIBERATE_EXCLUDED_EXAMPLES =
138             ImmutableSet.of(
139                     "//ldml/layout/orientation/characterOrder",
140                     "//ldml/layout/orientation/lineOrder",
141                     "//ldml/characters/moreInformation",
142                     "//ldml/numbers/symbols[@numberSystem=\"([^\"]*+)\"]/infinity",
143                     "//ldml/numbers/symbols[@numberSystem=\"([^\"]*+)\"]/list",
144                     "//ldml/numbers/symbols[@numberSystem=\"([^\"]*+)\"]/nan",
145                     "//ldml/numbers/currencies/currency[@type=\"([^\"]*+)\"]/displayName",
146                     "//ldml/localeDisplayNames/measurementSystemNames/measurementSystemName[@type=\"([^\"]*+)\"]",
147                     // old format
148                     "//ldml/numbers/symbols/infinity",
149                     "//ldml/numbers/symbols/list",
150                     "//ldml/numbers/symbols/nan",
151                     "//ldml/posix/messages/nostr",
152                     "//ldml/posix/messages/yesstr",
153                     "//ldml/contextTransforms/contextTransformUsage[@type=\"([^\"]*+)\"]/contextTransform[@type=\"([^\"]*+)\"]",
154                     "//ldml/characters/exemplarCharacters",
155                     "//ldml/characters/exemplarCharacters[@type=\"([^\"]*+)\"]",
156                     "//ldml/characters/parseLenients.*",
157                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/months/monthContext[@type=\"([^\"]*+)\"]/monthWidth[@type=\"([^\"]*+)\"]/month[@type=\"([^\"]*+)\"]",
158                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/days/dayContext[@type=\"([^\"]*+)\"]/dayWidth[@type=\"([^\"]*+)\"]/day[@type=\"([^\"]*+)\"]",
159                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/quarters/quarterContext[@type=\"([^\"]*+)\"]/quarterWidth[@type=\"([^\"]*+)\"]/quarter[@type=\"([^\"]*+)\"]", // examples only for gregorian
160                     "//ldml/dates/fields/field[@type=\"([^\"]*+)\"]/displayName",
161                     "//ldml/dates/fields/field[@type=\"([^\"]*+)\"]/relative[@type=\"([^\"]*+)\"]",
162                     "//ldml/dates/fields/field[@type=\"([^\"]*+)\"]/relativeTime[@type=\"([^\"]*+)\"]/relativeTimePattern[@count=\"([^\"]*+)\"]",
163                     "//ldml/dates/fields/field[@type=\"([^\"]*+)\"]/relativePeriod",
164                     "//ldml/dates/fields/field[@type=\"([^\"]*+)\"]/displayName[@alt=\"([^\"]*+)\"]",
165                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/cyclicNameSets/cyclicNameSet[@type=\"([^\"]*+)\"]/cyclicNameContext[@type=\"([^\"]*+)\"]/cyclicNameWidth[@type=\"([^\"]*+)\"]/cyclicName[@type=\"([^\"]*+)\"]",
166                     "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"([^\"]*+)\"]",
167                     "//ldml/numbers/minimalPairs/ordinalMinimalPairs[@ordinal=\"([^\"]*+)\"]",
168                     "//ldml/characters/parseLenients[@scope=\"([^\"]*+)\"][@level=\"([^\"]*+)\"]/parseLenient[@sample=\"([^\"]*+)\"]");
169     // Only add to above if the example should NEVER appear.
170 
171     /**
172      * Add to this if the example SHOULD appear, but we don't have it yet. <br>
173      * TODO Add later
174      */
175     static final Set<String> TEMPORARY_EXCLUDED_EXAMPLES =
176             ImmutableSet.of(
177                     "//ldml/numbers/currencyFormats/currencySpacing/beforeCurrency/currencyMatch",
178                     "//ldml/numbers/currencyFormats/currencySpacing/beforeCurrency/surroundingMatch",
179                     "//ldml/numbers/currencyFormats/currencySpacing/beforeCurrency/insertBetween",
180                     "//ldml/numbers/currencyFormats/currencySpacing/afterCurrency/currencyMatch",
181                     "//ldml/numbers/currencyFormats/currencySpacing/afterCurrency/surroundingMatch",
182                     "//ldml/numbers/currencyFormats/currencySpacing/afterCurrency/insertBetween",
183                     "//ldml/numbers/currencyFormats/currencyPatternAppendISO", // TODO see
184                     // CLDR-14831
185                     "//ldml/numbers/currencyFormats[@numberSystem=\"([^\"]*+)\"]/currencySpacing/beforeCurrency/currencyMatch",
186                     "//ldml/numbers/currencyFormats[@numberSystem=\"([^\"]*+)\"]/currencySpacing/beforeCurrency/surroundingMatch",
187                     "//ldml/numbers/currencyFormats[@numberSystem=\"([^\"]*+)\"]/currencySpacing/beforeCurrency/insertBetween",
188                     "//ldml/numbers/currencyFormats[@numberSystem=\"([^\"]*+)\"]/currencySpacing/afterCurrency/currencyMatch",
189                     "//ldml/numbers/currencyFormats[@numberSystem=\"([^\"]*+)\"]/currencySpacing/afterCurrency/surroundingMatch",
190                     "//ldml/numbers/currencyFormats[@numberSystem=\"([^\"]*+)\"]/currencySpacing/afterCurrency/insertBetween",
191                     "//ldml/numbers/currencyFormats[@numberSystem=\"([^\"]*+)\"]/currencyPatternAppendISO", // TODO see CLDR-14831
192                     "//ldml/localeDisplayNames/variants/variant[@type=\"([^\"]*+)\"]",
193                     "//ldml/localeDisplayNames/keys/key[@type=\"([^\"]*+)\"]",
194                     "//ldml/localeDisplayNames/types/type[@key=\"([^\"]*+)\"][@type=\"([^\"]*+)\"]",
195                     "//ldml/localeDisplayNames/types/type[@key=\"([^\"]*+)\"][@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]",
196                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/eras/eraNames/era[@type=\"([^\"]*+)\"]",
197                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/eras/eraAbbr/era[@type=\"([^\"]*+)\"]",
198                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/eras/eraNarrow/era[@type=\"([^\"]*+)\"]",
199                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateFormats/dateFormatLength[@type=\"([^\"]*+)\"]/dateFormat[@type=\"([^\"]*+)\"]/datetimeSkeleton",
200                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/timeFormats/timeFormatLength[@type=\"([^\"]*+)\"]/timeFormat[@type=\"([^\"]*+)\"]/datetimeSkeleton",
201                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateFormats/dateFormatLength[@type=\"([^\"]*+)\"]/datetimeSkeleton",
202                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/timeFormats/timeFormatLength[@type=\"([^\"]*+)\"]/datetimeSkeleton",
203                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateTimeFormats/appendItems/appendItem[@request=\"([^\"]*+)\"]",
204                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateTimeFormats/intervalFormats/intervalFormatFallback",
205                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateTimeFormats/intervalFormats/intervalFormatItem[@id=\"([^\"]*+)\"]/greatestDifference[@id=\"([^\"]*+)\"]",
206                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/eras/eraNames/era[@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]", // examples only for two closest eras to 2025
207                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/eras/eraAbbr/era[@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]",
208                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/eras/eraNarrow/era[@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]",
209                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/months/monthContext[@type=\"([^\"]*+)\"]/monthWidth[@type=\"([^\"]*+)\"]/month[@type=\"([^\"]*+)\"][@yeartype=\"([^\"]*+)\"]",
210                     "//ldml/dates/timeZoneNames/gmtZeroFormat",
211                     "//ldml/numbers/minimumGroupingDigits",
212                     "//ldml/numbers/symbols/timeSeparator",
213                     "//ldml/numbers/symbols[@numberSystem=\"([^\"]*+)\"]/timeSeparator",
214                     "//ldml/units/unitLength[@type=\"([^\"]*+)\"]/unit[@type=\"([^\"]*+)\"]/displayName",
215                     "//ldml/units/unitLength[@type=\"([^\"]*+)\"]/unit[@type=\"([^\"]*+)\"]/perUnitPattern",
216                     "//ldml/units/unitLength[@type=\"([^\"]*+)\"]/coordinateUnit/coordinateUnitPattern[@type=\"([^\"]*+)\"]",
217                     "//ldml/units/unitLength[@type=\"([^\"]*+)\"]/coordinateUnit/displayName",
218                     "//ldml/characterLabels/characterLabelPattern[@type=\"([^\"]*+)\"]",
219                     "//ldml/characterLabels/characterLabelPattern[@type=\"([^\"]*+)\"][@count=\"([^\"]*+)\"]",
220                     "//ldml/characterLabels/characterLabel[@type=\"([^\"]*+)\"]",
221                     "//ldml/typographicNames/axisName[@type=\"([^\"]*+)\"]",
222                     "//ldml/typographicNames/styleName[@type=\"([^\"]*+)\"][@subtype=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]",
223                     "//ldml/typographicNames/styleName[@type=\"([^\"]*+)\"][@subtype=\"([^\"]*+)\"]",
224                     "//ldml/typographicNames/featureName[@type=\"([^\"]*+)\"]",
225                     "//ldml/localeDisplayNames/subdivisions/subdivision[@type=\"([^\"]*+)\"]",
226                     "//ldml/dates/timeZoneNames/zone[@type=\"([^\"]*+)\"]/long/standard", // Error:
227                     // (TestExampleGenerator.java:245) No background:   <Coordinated Universal Time>
228                     //    〖Coordinated Universal Time〗
229                     "//ldml/personNames/nameOrderLocales[@order=\"([^\"]*+)\"]", // TODO CLDR-15384
230                     "//ldml/personNames/foreignSpaceReplacement[@xml:space=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]", // TODO CLDR-15384
231                     "//ldml/personNames/foreignSpaceReplacement[@xml:space=\"([^\"]*+)\"]", // TODO
232                     "//ldml/personNames/foreignSpaceReplacement[@alt=\"([^\"]*+)\"]",
233                     "//ldml/personNames/foreignSpaceReplacement", // TODO CLDR-15384
234                     "//ldml/personNames/nativeSpaceReplacement[@xml:space=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]", // TODO CLDR-15384
235                     "//ldml/personNames/nativeSpaceReplacement[@xml:space=\"([^\"]*+)\"]", // TODO
236                     "//ldml/personNames/nativeSpaceReplacement[@alt=\"([^\"]*+)\"]",
237                     "//ldml/personNames/nativeSpaceReplacement", // TODO CLDR-15384
238                     "//ldml/personNames/initialPattern[@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]", // TODO CLDR-15384
239                     "//ldml/personNames/initialPattern[@type=\"([^\"]*+)\"]", // TODO CLDR-15384
240                     "//ldml/personNames/personName[@order=\"([^\"]*+)\"][@length=\"([^\"]*+)\"][@usage=\"([^\"]*+)\"][@formality=\"([^\"]*+)\"]/namePattern[@alt=\"([^\"]*+)\"]", // TODO CLDR-15384
241                     "//ldml/personNames/sampleName[@item=\"([^\"]*+)\"]/nameField[@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]", // TODO CLDR-15384
242                     "//ldml/personNames/sampleName[@item=\"([^\"]*+)\"]/nameField[@type=\"([^\"]*+)\"]", // TODO CLDR-15384
243                     "//ldml/personNames/parameterDefault[@parameter=\"([^\"]*+)\"]" // TODO
244                     // CLDR-15384
245                     );
246     // Add to above if the example SHOULD appear, but we don't have it yet. TODO Add later
247 
248     /**
249      * Only add to this if the background should NEVER appear. <br>
250      * The background is used when the element is used as part of another format. <br>
251      * WARNING - do not disable the test by putting in too broad a match. Make sure the paths are
252      * reasonably granular.
253      */
254     static final Set<String> DELIBERATE_OK_TO_MISS_BACKGROUND =
255             ImmutableSet.of(
256                     "//ldml/numbers/defaultNumberingSystem",
257                     "//ldml/numbers/otherNumberingSystems/native",
258                     // TODO fix formatting
259                     "//ldml/characters/exemplarCharacters",
260                     "//ldml/characters/exemplarCharacters[@type=\"([^\"]*+)\"]",
261                     // TODO Add background
262                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/timeFormats/timeFormatLength[@type=\"([^\"]*+)\"]/timeFormat[@type=\"([^\"]*+)\"]/pattern[@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]", // CLDR-16606
263                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateTimeFormats/availableFormats/dateFormatItem[@id=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]", // CLDR-16606
264                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateFormats/dateFormatLength[@type=\"([^\"]*+)\"]/dateFormat[@type=\"([^\"]*+)\"]/pattern[@type=\"([^\"]*+)\"]",
265                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/timeFormats/timeFormatLength[@type=\"([^\"]*+)\"]/timeFormat[@type=\"([^\"]*+)\"]/pattern[@type=\"([^\"]*+)\"]",
266                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateTimeFormats/availableFormats/dateFormatItem[@id=\"([^\"]*+)\"]",
267                     "//ldml/dates/timeZoneNames/zone[@type=\"([^\"]*+)\"]/exemplarCity",
268                     "//ldml/dates/timeZoneNames/zone[@type=\"([^\"]*+)\"]/exemplarCity[@alt=\"([^\"]*+)\"]",
269                     "//ldml/dates/timeZoneNames/zone[@type=\"([^\"]*+)\"]/long/daylight",
270                     "//ldml/dates/timeZoneNames/zone[@type=\"([^\"]*+)\"]/short/generic",
271                     "//ldml/dates/timeZoneNames/zone[@type=\"([^\"]*+)\"]/short/standard",
272                     "//ldml/dates/timeZoneNames/zone[@type=\"([^\"]*+)\"]/short/daylight",
273                     "//ldml/dates/timeZoneNames/metazone[@type=\"([^\"]*+)\"]/long/generic",
274                     "//ldml/dates/timeZoneNames/metazone[@type=\"([^\"]*+)\"]/long/standard",
275                     "//ldml/dates/timeZoneNames/metazone[@type=\"([^\"]*+)\"]/long/daylight",
276                     "//ldml/units/durationUnit[@type=\"([^\"]*+)\"]/durationUnitPattern");
277     // Only add to above if the background should NEVER appear.
278 
279     /**
280      * Add to this if the background SHOULD appear, but we don't have them yet. <br>
281      * The background is used when the element is used as part of another format. <br>
282      * TODO Add later
283      */
284     static final Set<String> TEMPORARY_OK_TO_MISS_BACKGROUND =
285             ImmutableSet.of(
286                     "//ldml/numbers/defaultNumberingSystem",
287                     "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateTimeFormats/availableFormats/dateFormatItem[@id=\"([^\"]*+)\"][@count=\"([^\"]*+)\"]",
288                     "//ldml/dates/timeZoneNames/zone[@type=\"([^\"]*+)\"]/long/standard",
289                     "//ldml/dates/timeZoneNames/metazone[@type=\"([^\"]*+)\"]/short/generic",
290                     "//ldml/dates/timeZoneNames/metazone[@type=\"([^\"]*+)\"]/short/standard",
291                     "//ldml/dates/timeZoneNames/metazone[@type=\"([^\"]*+)\"]/short/daylight",
292                     "//ldml/personNames/personName[@order=\"([^\"]*+)\"][@length=\"([^\"]*+)\"][@formality=\"([^\"]*+)\"]/namePattern",
293                     "//ldml/personNames/personName[@order=\"([^\"]*+)\"][@length=\"([^\"]*+)\"][@usage=\"([^\"]*+)\"][@formality=\"([^\"]*+)\"]/namePattern"); // CLDR-15384
294 
295     // Add to above if the background SHOULD appear, but we don't have them yet. TODO Add later
296 
TestAllPaths()297     public void TestAllPaths() {
298         ExampleGenerator exampleGenerator = getExampleGenerator("en");
299         PathStarrer ps = new PathStarrer();
300         Set<String> seen = new HashSet<>();
301         CLDRFile cldrFile = exampleGenerator.getCldrFile();
302         TreeSet<String> target = new TreeSet<>(cldrFile.getComparator());
303         cldrFile.fullIterable().forEach(target::add);
304         for (String path : target) {
305             String plainStarred = ps.set(path);
306             String value = cldrFile.getStringValue(path);
307             if (value == null
308                     || path.endsWith("/alias")
309                     || path.startsWith("//ldml/identity")
310                     || DELIBERATE_EXCLUDED_EXAMPLES.contains(plainStarred)) {
311                 continue;
312             }
313             if (TEMPORARY_EXCLUDED_EXAMPLES.contains(plainStarred)) {
314                 if (logKnownIssue(
315                         "Cldrbug:6342",
316                         "Need an example for each path used in context: " + plainStarred)) {
317                     continue;
318                 }
319                 continue;
320             }
321             String example = exampleGenerator.getExampleHtml(path, value);
322             String javaEscapedStarred = "\"" + plainStarred.replace("\"", "\\\"") + "\",";
323             if (example == null) {
324                 if (!seen.contains(javaEscapedStarred)) {
325                     errln("No example:\t<" + value + ">\t" + javaEscapedStarred);
326                 }
327             } else {
328                 String simplified = ExampleGenerator.simplify(example, false);
329 
330                 if (simplified.contains("null")) {
331                     if (true || !seen.contains(javaEscapedStarred)) {
332                         // debug
333                         exampleGenerator.getExampleHtml(path, value);
334                         ExampleGenerator.simplify(example, false);
335 
336                         errln(
337                                 "'null' in message:\t<"
338                                         + value
339                                         + ">\t"
340                                         + simplified
341                                         + "\t"
342                                         + javaEscapedStarred);
343                         // String example2 =
344                         // exampleGenerator.getExampleHtml(path, value); // for
345                         // debugging
346                     }
347                 } else if (!simplified.startsWith("〖")) {
348                     if (!seen.contains(javaEscapedStarred)) {
349                         errln(
350                                 "Funny HTML:\t<"
351                                         + value
352                                         + ">\t"
353                                         + simplified
354                                         + "\t"
355                                         + javaEscapedStarred);
356                     }
357                 } else if (!simplified.contains("❬")
358                         && !DELIBERATE_OK_TO_MISS_BACKGROUND.contains(plainStarred)) {
359                     if (!seen.contains(javaEscapedStarred)) {
360 
361                         if (TEMPORARY_OK_TO_MISS_BACKGROUND.contains(plainStarred)
362                                 && logKnownIssue(
363                                         "Cldrbug:6342",
364                                         "Make sure that background appears: "
365                                                 + simplified
366                                                 + "; "
367                                                 + plainStarred)) {
368                             continue;
369                         }
370 
371                         errln(
372                                 "No background:\t<"
373                                         + value
374                                         + ">\t"
375                                         + simplified
376                                         + "\t"
377                                         + javaEscapedStarred);
378                     }
379                 }
380             }
381             seen.add(javaEscapedStarred);
382         }
383     }
384 
TestUnits()385     public void TestUnits() {
386         ExampleGenerator exampleGenerator = getExampleGenerator("en");
387         String staticMeterExample =
388                 "〖1 meter ≡ 1,000 millimeter〗〖1 meter ≈ 1.0936 yard (US/UK)〗〖1 meter ≡ 1/1000 kilometer〗〖1 meter ≈ 621.37×10ˆ-6 mile (US/UK)〗";
389         String staticMeterExampleJp =
390                 "〖1 meter ≡ 1,000 millimeter〗〖1 meter ≡ 3.025 jo-jp (JP)〗〖1 meter ≈ 1.0936 yard (US/UK)〗〖1 meter ≈ 0.0023341 ri-jp (JP)〗〖1 meter ≡ 1/1000 kilometer〗〖1 meter ≈ 621.37×10ˆ-6 mile (US/UK)〗";
391         String staticMeterExampleRi =
392                 "〖1 ri-jp (JP) ≡ 1,296 jo-jp (JP)〗〖1 ri-jp (JP) ≈ 468.54 yard (US/UK)〗〖1 ri-jp (JP) ≈ 428.43 meter〗〖1 ri-jp (JP) ≈ 0.42843 kilometer〗〖1 ri-jp (JP) ≈ 0.26621 mile (US/UK)〗";
393 
394         checkValue(
395                 "Length m",
396                 staticMeterExample,
397                 exampleGenerator,
398                 "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"length-meter\"]/displayName");
399         checkValue(
400                 "Duration hm",
401                 "〖5:37〗",
402                 exampleGenerator,
403                 "//ldml/units/durationUnit[@type=\"hm\"]/durationUnitPattern");
404         checkValue(
405                 "Length m",
406                 "〖❬1❭ meter〗〖〗" + staticMeterExample,
407                 exampleGenerator,
408                 "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"length-meter\"]/unitPattern[@count=\"one\"]");
409         checkValue(
410                 "Length m",
411                 "〖❬1.5❭ meters〗〖〗" + staticMeterExample,
412                 exampleGenerator,
413                 "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"length-meter\"]/unitPattern[@count=\"other\"]");
414         checkValue(
415                 "Length m",
416                 "〖❬1.5❭ m〗〖〗" + staticMeterExample,
417                 exampleGenerator,
418                 "//ldml/units/unitLength[@type=\"short\"]/unit[@type=\"length-meter\"]/unitPattern[@count=\"other\"]");
419         checkValue(
420                 "Length m",
421                 "〖❬1.5❭m〗〖〗" + staticMeterExample,
422                 exampleGenerator,
423                 "//ldml/units/unitLength[@type=\"narrow\"]/unit[@type=\"length-meter\"]/unitPattern[@count=\"other\"]");
424 
425         // The following are to ensure that we properly generate an example when we have a
426         // non-winning value
427         checkValue(
428                 "Length m",
429                 "〖❬1.5❭ badmeter〗〖〗" + staticMeterExample,
430                 exampleGenerator,
431                 "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"length-meter\"]/unitPattern[@count=\"other\"]",
432                 "{0} badmeter");
433 
434         ExampleGenerator exampleGeneratorDe = getExampleGenerator("de");
435         checkValue(
436                 "Length m",
437                 "〖❬1,5❭ badmeter〗〖❬Anstatt 1,5❭ badmeter❬ …❭〗〖❌  ❬… für 1,5❭ badmeter❬ …❭〗〖〗"
438                         + staticMeterExample,
439                 exampleGeneratorDe,
440                 "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"length-meter\"]/unitPattern[@count=\"other\"][@case=\"genitive\"]",
441                 "{0} badmeter");
442 
443         ExampleGenerator exampleGeneratorJa = getExampleGenerator("ja");
444         checkValue(
445                 "Length m",
446                 "〖❬1.5❭m〗〖〗" + staticMeterExampleJp,
447                 exampleGeneratorJa,
448                 "//ldml/units/unitLength[@type=\"narrow\"]/unit[@type=\"length-meter\"]/unitPattern[@count=\"other\"]");
449         checkValue(
450                 "Length ri",
451                 "〖❬1.5❭ 里〗〖〗" + staticMeterExampleRi,
452                 exampleGeneratorJa,
453                 "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"length-ri-jp\"]/unitPattern[@count=\"other\"]");
454     }
455 
456     /**
457      * Check that the expected exampleGenerator example is produced for the parameters, with the
458      * value coming from the file.
459      */
checkValue( String message, String expected, ExampleGenerator exampleGenerator, String path)460     private void checkValue(
461             String message, String expected, ExampleGenerator exampleGenerator, String path) {
462         checkValue(message, expected, exampleGenerator, path, null);
463     }
464 
465     /** Check that the expected exampleGenerator example is produced for the parameters */
checkValue( String message, String expected, ExampleGenerator exampleGenerator, String path, String value)466     private void checkValue(
467             String message,
468             String expected,
469             ExampleGenerator exampleGenerator,
470             String path,
471             String value) {
472         final CLDRFile cldrFile = exampleGenerator.getCldrFile();
473         value = value != null ? value : cldrFile.getStringValue(path);
474         String actual = exampleGenerator.getExampleHtml(path, value);
475         assertEquals(
476                 cldrFile.getLocaleID() + ": " + message,
477                 expected,
478                 ExampleGenerator.simplify(actual, false));
479     }
480 
TestCompoundUnit()481     public void TestCompoundUnit() {
482         String[][] tests = {
483             {"per", "LONG", "one", "〖❬1 meter❭ per ❬second❭〗"},
484             {"per", "SHORT", "one", "〖❬1 m❭/❬sec❭〗"},
485             {"per", "NARROW", "one", "〖❬1m❭/❬s❭〗"},
486             {"per", "LONG", "other", "〖❬1.5 meters❭ per ❬second❭〗"},
487             {"per", "SHORT", "other", "〖❬1.5 m❭/❬sec❭〗"},
488             {"per", "NARROW", "other", "〖❬1.5m❭/❬s❭〗"},
489             {"times", "LONG", "one", "〖❬1 newton❭-❬meter❭〗"},
490             {"times", "SHORT", "one", "〖❬1 N❭⋅❬m❭〗"},
491             {"times", "NARROW", "one", "〖❬1N❭⋅❬m❭〗"},
492             {"times", "LONG", "other", "〖❬1.5 newton❭-❬meters❭〗"},
493             {"times", "SHORT", "other", "〖❬1.5 N❭⋅❬m❭〗"},
494             {"times", "NARROW", "other", "〖❬1.5N❭⋅❬m❭〗"},
495         };
496         checkCompoundUnits("en", tests);
497         // reenable these after Arabic has meter translated
498         // String[][] tests2 = {
499         // {"LONG", "few", "〖❬1 meter❭ per ❬second❭〗"},
500         // };
501         // checkCompoundUnits("ar", tests2);
502     }
503 
checkCompoundUnits(String locale, String[][] tests)504     private void checkCompoundUnits(String locale, String[][] tests) {
505         ExampleGenerator exampleGenerator = getExampleGenerator(locale);
506         for (String[] test : tests) {
507             List<String> examples = new ArrayList<>();
508             exampleGenerator.handleCompoundUnit(
509                     UnitLength.valueOf(test[1]), test[0], Count.valueOf(test[2]), examples);
510             String actual = exampleGenerator.formatExampleList(examples);
511             assertEquals("CompoundUnit", test[3], ExampleGenerator.simplify(actual, true));
512         }
513     }
514 
TestTranslationPaths()515     public void TestTranslationPaths() {
516         for (String locale : Arrays.asList("en", "el", "ru")) {
517             CLDRFile cldrFile = CLDRConfig.getInstance().getCldrFactory().make(locale, true);
518             ExampleGenerator exampleGenerator = getExampleGenerator(locale);
519 
520             for (UnitPathType pathType : UnitPathType.values()) {
521                 for (String width : Arrays.asList("long", "short", "narrow")) {
522                     if (pathType == UnitPathType.gender && !width.equals("long")) {
523                         continue;
524                     }
525                     for (String unit : pathType.sampleShortUnitType) {
526                         String path =
527                                 pathType.getTranslationPath(
528                                         cldrFile, width, unit, "one", "nominative", null);
529                         String value = cldrFile.getStringValue(path);
530                         if (value != null) {
531                             String example = exampleGenerator.getExampleHtml(path, value);
532                             if (assertNotNull(locale + "/" + path, example)) {
533                                 String simplified = ExampleGenerator.simplify(example, false);
534                                 if (showTranslationPaths) {
535                                     warnln(
536                                             locale
537                                                     + ", "
538                                                     + width
539                                                     + ", "
540                                                     + pathType.toString()
541                                                     + " ==>"
542                                                     + simplified);
543                                 }
544                             } else {
545                                 // for debugging
546                                 example = exampleGenerator.getExampleHtml(path, value);
547                             }
548                         }
549                     }
550                 }
551             }
552         }
553     }
554 
TestCompoundUnit2()555     public void TestCompoundUnit2() {
556         String[][] tests = {
557             {"de", "LONG", "other", "Quadrat{0}", "〖❬1,5 ❭Quadrat❬meter❭〗"},
558             {"en", "SHORT", "one", "z{0}", "〖❬1 ❭z❬m❭〗"},
559             {"en", "LONG", "other", "zetta{0}", "〖❬1.5 ❭zetta❬meters❭〗"},
560             {"en", "SHORT", "one", "{0}²", "〖❬1 m❭²〗"},
561             {"en", "LONG", "other", "square {0}", "〖❬1.5 ❭square ❬meters❭〗"},
562             {"de", "SHORT", "one", "z{0}", "〖❬1 ❭z❬m❭〗"},
563             {"de", "LONG", "other", "Zetta{0}", "〖❬1,5 ❭Zetta❬meter❭〗"},
564             {"de", "SHORT", "one", "{0}²", "〖❬1 m❭²〗"},
565             {"de", "LONG", "other", "Quadrat{0}", "〖❬1,5 ❭Quadrat❬meter❭〗"},
566         };
567         for (String[] test : tests) {
568 
569             ExampleGenerator exampleGenerator = getExampleGenerator(test[0]);
570             List<String> examples = new ArrayList<>();
571             exampleGenerator.handleCompoundUnit1(
572                     UnitLength.valueOf(test[1]), Count.valueOf(test[2]), test[3], examples);
573             String actual = exampleGenerator.formatExampleList(examples);
574             assertEquals("CompoundUnit", test[4], ExampleGenerator.simplify(actual, true));
575         }
576     }
577 
TestCompoundUnit3()578     public void TestCompoundUnit3() {
579         final Factory cldrFactory = CLDRConfig.getInstance().getCldrFactory();
580         String[][] tests = {
581             // locale, path, value, expected-example
582             {
583                 "en",
584                 "//ldml/units/unitLength[@type=\"long\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1",
585                 "LOCALE",
586                 "〖square ❬meters❭〗"
587             }, //
588             {
589                 "en",
590                 "//ldml/units/unitLength[@type=\"long\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1[@count=\"one\"]",
591                 "LOCALE",
592                 "〖❬1 ❭square ❬meter❭〗"
593             }, //
594             {
595                 "en",
596                 "//ldml/units/unitLength[@type=\"long\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1[@count=\"other\"]",
597                 "LOCALE",
598                 "〖❬1.5 ❭square ❬meters❭〗"
599             }, //
600             {
601                 "en",
602                 "//ldml/units/unitLength[@type=\"narrow\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1",
603                 "LOCALE",
604                 "〖❬m❭²〗"
605             },
606             {
607                 "en",
608                 "//ldml/units/unitLength[@type=\"narrow\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1[@count=\"one\"]",
609                 "LOCALE",
610                 "〖❬1m❭²〗"
611             },
612             {
613                 "en",
614                 "//ldml/units/unitLength[@type=\"narrow\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1[@count=\"other\"]",
615                 "LOCALE",
616                 "〖❬1.5m❭²〗"
617             },
618 
619             // warning, french patterns has U+00A0 in them
620             {
621                 "fr",
622                 "//ldml/units/unitLength[@type=\"long\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1",
623                 "Square {0}",
624                 "〖Square ❬mètres❭〗"
625             },
626             {
627                 "fr",
628                 "//ldml/units/unitLength[@type=\"long\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1[@count=\"one\"]",
629                 "square {0}",
630                 "〖❬1,5 ❭square ❬mètre❭〗"
631             },
632             {
633                 "fr",
634                 "//ldml/units/unitLength[@type=\"long\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1[@count=\"other\"]",
635                 "squares {0}",
636                 "〖❬3,5 ❭squares ❬mètres❭〗"
637             },
638             {
639                 "fr",
640                 "//ldml/units/unitLength[@type=\"narrow\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1",
641                 "LOCALE",
642                 "〖❬m❭²〗"
643             },
644             {
645                 "fr",
646                 "//ldml/units/unitLength[@type=\"narrow\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1[@count=\"one\"]",
647                 "LOCALE",
648                 "〖❬1,5m❭²〗"
649             },
650             {
651                 "fr",
652                 "//ldml/units/unitLength[@type=\"narrow\"]/compoundUnit[@type=\"power2\"]/compoundUnitPattern1[@count=\"other\"]",
653                 "LOCALE",
654                 "〖❬3,5m❭²〗"
655             },
656         };
657 
658         int lineCount = 0;
659         for (String[] test : tests) {
660 
661             final String localeID = test[0];
662             final String xpath = test[1];
663             String value = test[2];
664             String expected = test[3];
665 
666             ExampleGenerator exampleGenerator = getExampleGenerator(localeID);
667 
668             if (value.equals("LOCALE")) {
669                 value = cldrFactory.make(localeID, true).getStringValue(xpath);
670             }
671             String actual = exampleGenerator.getExampleHtml(xpath, value);
672             assertEquals(
673                     ++lineCount + ") " + localeID + ", CompoundUnit3",
674                     expected,
675                     ExampleGenerator.simplify(actual, false));
676         }
677     }
678 
679     HashMap<String, ExampleGenerator> ExampleGeneratorCache = new HashMap<>();
680 
getExampleGenerator(String locale)681     private ExampleGenerator getExampleGenerator(String locale) {
682         ExampleGenerator result = ExampleGeneratorCache.get(locale);
683         if (result == null) {
684             final CLDRFile nativeCldrFile = info.getCLDRFile(locale, true);
685             result = new ExampleGenerator(nativeCldrFile, info.getEnglish());
686             ExampleGeneratorCache.put(locale, result);
687         }
688         return result;
689     }
690 
TestEllipsis()691     public void TestEllipsis() {
692         ExampleGenerator exampleGenerator = getExampleGenerator("it");
693         String[][] tests = {
694             {"initial", "〖…❬iappone❭〗"},
695             {"medial", "〖❬Svizzer❭…❬iappone❭〗"},
696             {"final", "〖❬Svizzer❭…〗"},
697             {"word-initial", "〖… ❬Giappone❭〗"},
698             {"word-medial", "〖❬Svizzera❭ … ❬Giappone❭〗"},
699             {"word-final", "〖❬Svizzera❭ …〗"},
700         };
701         for (String[] pair : tests) {
702             checkValue(
703                     exampleGenerator,
704                     "//ldml/characters/ellipsis[@type=\"" + pair[0] + "\"]",
705                     pair[1]);
706         }
707     }
708 
checkValue(ExampleGenerator exampleGenerator, String path, String expected)709     private void checkValue(ExampleGenerator exampleGenerator, String path, String expected) {
710         String value = exampleGenerator.getCldrFile().getStringValue(path);
711         String result =
712                 ExampleGenerator.simplify(exampleGenerator.getExampleHtml(path, value), false);
713         assertEquals("Ellipsis", expected, result);
714     }
715 
simplify(String exampleHtml)716     public static String simplify(String exampleHtml) {
717         return ExampleGenerator.simplify(exampleHtml, false);
718     }
719 
TestClip()720     public void TestClip() {
721         assertEquals("Clipping", "bc", ExampleGenerator.clip("abc", 1, 0));
722         assertEquals("Clipping", "ab", ExampleGenerator.clip("abc", 0, 1));
723         assertEquals(
724                 "Clipping", "b\u0308c\u0308", ExampleGenerator.clip("a\u0308b\u0308c\u0308", 1, 0));
725         assertEquals(
726                 "Clipping", "a\u0308b\u0308", ExampleGenerator.clip("a\u0308b\u0308c\u0308", 0, 1));
727     }
728 
TestPaths()729     public void TestPaths() {
730         showCldrFile(info.getEnglish());
731         showCldrFile(info.getCLDRFile("fr", true));
732     }
733 
TestMiscPatterns()734     public void TestMiscPatterns() {
735         ExampleGenerator exampleGenerator = getExampleGenerator("it");
736         checkValue(
737                 "At least",
738                 "〖≥❬99❭〗",
739                 exampleGenerator,
740                 "//ldml/numbers/miscPatterns[@numberSystem=\"latn\"]/pattern[@type=\"atLeast\"]");
741         checkValue(
742                 "Range",
743                 "〖❬99❭-❬144❭〗",
744                 exampleGenerator,
745                 "//ldml/numbers/miscPatterns[@numberSystem=\"latn\"]/pattern[@type=\"range\"]");
746         // String actual = exampleGenerator.getExampleHtml(
747         // "//ldml/numbers/miscPatterns[@type=\"arab\"]/pattern[@type=\"atLeast\"]",
748         // "at least {0}", Zoomed.IN);
749         // assertEquals("Invalid format",
750         // "<div class='cldr_example'>at least 99</div>", actual);
751     }
752 
TestPluralSamples()753     public void TestPluralSamples() {
754         ExampleGenerator exampleGenerator = getExampleGenerator("sv");
755         String[][] tests = {
756             {
757                 "//ldml/units/unitLength[@type=\"short\"]/unit[@type=\"length-centimeter\"]/unitPattern[@count=\"one\"]",
758                 "Number should be one",
759                 "〖❬1❭ cm〗〖❬Jag tror att 1❭ cm❬ är tillräckligt.❭〗"
760             },
761             {
762                 "//ldml/numbers/minimalPairs/ordinalMinimalPairs[@ordinal=\"one\"]",
763                 "Ordinal one",
764                 "〖Ta ❬1❭:a svängen till höger〗〖❌  Ta ❬3❭:a svängen till höger〗"
765             },
766             {
767                 "//ldml/numbers/minimalPairs/ordinalMinimalPairs[@ordinal=\"other\"]",
768                 "Ordinal other",
769                 "〖Ta ❬3❭:e svängen till höger〗〖❌  Ta ❬1❭:e svängen till höger〗"
770             },
771         };
772         for (String[] row : tests) {
773             String path = row[0];
774             String message = row[1];
775             String expected = row[2];
776             checkValue(message, expected, exampleGenerator, path);
777         }
778     }
779 
TestLocaleDisplayPatterns()780     public void TestLocaleDisplayPatterns() {
781         ExampleGenerator exampleGenerator = getExampleGenerator("it");
782         String actual =
783                 exampleGenerator.getExampleHtml(
784                         "//ldml/localeDisplayNames/localeDisplayPattern/localePattern",
785                         "{0} [{1}]");
786         assertEquals(
787                 "localePattern example faulty",
788                 "<div class='cldr_example'><span class='cldr_substituted'>uzbeco</span> [<span class='cldr_substituted'>Afghanistan</span>]</div>"
789                         + "<div class='cldr_example'><span class='cldr_substituted'>uzbeco</span> [<span class='cldr_substituted'>arabo, Afghanistan</span>]</div>"
790                         + "<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>",
791                 actual);
792         actual =
793                 exampleGenerator.getExampleHtml(
794                         "//ldml/localeDisplayNames/localeDisplayPattern/localeSeparator",
795                         "{0}. {1}");
796         assertEquals(
797                 "localeSeparator example faulty",
798                 "<div class='cldr_example'><span class='cldr_substituted'>uzbeco (arabo</span>. <span class='cldr_substituted'>Afghanistan)</span></div>"
799                         + "<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>",
800                 actual);
801     }
802 
TestCurrencyFormats()803     public void TestCurrencyFormats() {
804         ExampleGenerator exampleGenerator = getExampleGenerator("it");
805         String actual =
806                 simplify(
807                         exampleGenerator.getExampleHtml(
808                                 "//ldml/numbers/currencyFormats[@numberSystem=\"latn\"]/currencyFormatLength/currencyFormat[@type=\"standard\"]/pattern[@type=\"standard\"]",
809                                 "¤ #0.00"));
810         assertEquals("Currency format example faulty", "〖€ ❬1295,00❭〗〖-€ ❬1295,00❭〗", actual);
811     }
812 
TestCurrencyFormatsWithContext()813     public void TestCurrencyFormatsWithContext() {
814         ExampleGenerator exampleGenerator = getExampleGenerator("he");
815         String actual =
816                 simplify(
817                         exampleGenerator.getExampleHtml(
818                                 "//ldml/numbers/currencyFormats[@numberSystem=\"latn\"]/currencyFormatLength/currencyFormat[@type=\"standard\"]/pattern[@type=\"standard\"]",
819                                 "‏#,##0.00 ¤;‏-#,##0.00 ¤"));
820         assertEquals(
821                 "Currency format example faulty",
822                 "【‏❬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〗",
823                 actual);
824     }
825 
TestDateFormatsWithContext()826     public void TestDateFormatsWithContext() {
827         ExampleGenerator exampleGenerator = getExampleGenerator("ar");
828         String actual =
829                 simplify(
830                         exampleGenerator.getExampleHtml(
831                                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/dateFormats/dateFormatLength[@type=\"short\"]/dateFormat[@type=\"standard\"]/pattern[@type=\"standard\"]",
832                                 "d‏/M‏/y"));
833         assertEquals("Currency format example faulty", "【5‏/9‏/1999〗【⃪5‏/9‏/1999〗", actual);
834     }
835 
TestDateTimeComboFormats()836     public void TestDateTimeComboFormats() {
837         ExampleGenerator exampleGenerator = getExampleGenerator("en");
838         checkValue(
839                 "DateTimeCombo long std",
840                 "〖❬September 5, 1999❭, ❬1:25:59 PM Eastern Standard Time❭〗〖❬September 5, 1999❭, ❬1:25 PM❭〗〖❬September 5, 1999❭, ❬7:00 AM – 1:25 PM❭〗〖❬today❭, ❬7:00 AM – 1:25 PM❭〗",
841                 exampleGenerator,
842                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/dateTimeFormats/dateTimeFormatLength[@type=\"long\"]/dateTimeFormat[@type=\"standard\"]/pattern[@type=\"standard\"]");
843         checkValue(
844                 "DateTimeCombo short std",
845                 "〖❬9/5/99❭, ❬1:25:59 PM EST❭〗〖❬9/5/99❭, ❬1:25 PM❭〗〖❬9/5/99❭, ❬7:00 AM – 1:25 PM❭〗〖❬today❭, ❬7:00 AM – 1:25 PM❭〗",
846                 exampleGenerator,
847                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/dateTimeFormats/dateTimeFormatLength[@type=\"short\"]/dateTimeFormat[@type=\"standard\"]/pattern[@type=\"standard\"]");
848         checkValue(
849                 "DateTimeCombo long std",
850                 "〖❬September 5, 1999❭ at ❬1:25:59 PM Eastern Standard Time❭〗〖❬September 5, 1999❭ at ❬1:25 PM❭〗〖❬today❭ at ❬1:25 PM❭〗",
851                 exampleGenerator,
852                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/dateTimeFormats/dateTimeFormatLength[@type=\"long\"]/dateTimeFormat[@type=\"atTime\"]/pattern[@type=\"standard\"]");
853     }
854 
TestDateSymbols()855     public void TestDateSymbols() {
856         ExampleGenerator exampleGenerator = getExampleGenerator("cs");
857         checkValue(
858                 "cs format wide",
859                 "〖5. června 1999〗",
860                 exampleGenerator,
861                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/months/monthContext[@type=\"format\"]/monthWidth[@type=\"wide\"]/month[@type=\"6\"]");
862         checkValue(
863                 "cs format abbreviated",
864                 "〖5. čvn 1999〗",
865                 exampleGenerator,
866                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/months/monthContext[@type=\"format\"]/monthWidth[@type=\"abbreviated\"]/month[@type=\"6\"]");
867         checkValue(
868                 "cs stand-alone wide",
869                 "〖červen 1999〗",
870                 exampleGenerator,
871                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/months/monthContext[@type=\"stand-alone\"]/monthWidth[@type=\"wide\"]/month[@type=\"6\"]");
872         checkValue(
873                 "cs stand-alone abbreviated",
874                 "〖čvn 1999〗",
875                 exampleGenerator,
876                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/months/monthContext[@type=\"stand-alone\"]/monthWidth[@type=\"abbreviated\"]/month[@type=\"6\"]");
877     }
878 
TestMinimumGroupingExamples()879     public void TestMinimumGroupingExamples() {
880         ExampleGenerator exampleGeneratorEn = getExampleGenerator("en"); // min grouping 1
881         ExampleGenerator exampleGeneratorEs = getExampleGenerator("es"); // min grouping 2
882         checkValue(
883                 "MinimumGrouping en: 1",
884                 "〖❬543.21❭〗〖❬6,543❭.❬21❭〗〖❬76,543❭.❬21❭〗",
885                 exampleGeneratorEn,
886                 "//ldml/numbers/minimumGroupingDigits");
887         checkValue(
888                 "MinimumGrouping es: 2",
889                 "〖❬543,21❭〗〖❬6543,21❭〗〖❬76.543❭,❬21❭〗",
890                 exampleGeneratorEs,
891                 "//ldml/numbers/minimumGroupingDigits");
892     }
893 
TestSymbols()894     public void TestSymbols() {
895         CLDRFile english = info.getEnglish();
896         ExampleGenerator exampleGenerator = new ExampleGenerator(english, english);
897         String actual =
898                 exampleGenerator.getExampleHtml(
899                         "//ldml/numbers/symbols[@numberSystem=\"latn\"]/superscriptingExponent",
900                         "x");
901 
902         assertEquals(
903                 "superscriptingExponent faulty",
904                 "<div class='cldr_example'><span class='cldr_substituted'>1.23456789</span>x10<span class='cldr_substituted'><sup>5</sup></span></div>",
905                 actual);
906     }
907 
TestFallbackFormat()908     public void TestFallbackFormat() {
909         ExampleGenerator exampleGenerator =
910                 new ExampleGenerator(info.getEnglish(), info.getEnglish());
911         String actual =
912                 exampleGenerator.getExampleHtml(
913                         "//ldml/dates/timeZoneNames/fallbackFormat", "{1} [{0}]");
914         assertEquals(
915                 "fallbackFormat faulty",
916                 "〖❬Central Time❭ [❬Cancún❭]〗",
917                 ExampleGenerator.simplify(actual, false));
918     }
919 
Test4897()920     public void Test4897() {
921         ExampleGenerator exampleGenerator = getExampleGenerator("it");
922         final CLDRFile cldrFile = exampleGenerator.getCldrFile();
923         for (String xpath :
924                 With.in(
925                         cldrFile.iterator(
926                                 "//ldml/dates/timeZoneNames", cldrFile.getComparator()))) {
927             String value = cldrFile.getStringValue(xpath);
928             String actual = exampleGenerator.getExampleHtml(xpath, value);
929             if (actual == null) {
930                 if (!xpath.contains("singleCountries") && !xpath.contains("gmtZeroFormat")) {
931                     errln("Null value for " + value + "\t" + xpath);
932                     // for debugging
933                     exampleGenerator.getExampleHtml(xpath, value);
934                 }
935             } else {
936                 logln(actual + "\t" + value + "\t" + xpath);
937             }
938         }
939     }
940 
Test4528()941     public void Test4528() {
942         String[][] testPairs = {
943             {
944                 "//ldml/numbers/currencies/currency[@type=\"BMD\"]/displayName[@count=\"other\"]",
945                 "〖❬1,23 ❭dollari delle Bermuda〗〖❬0,00 ❭dollari delle Bermuda〗"
946             },
947             {
948                 "//ldml/numbers/currencyFormats[@numberSystem=\"latn\"]/unitPattern[@count=\"other\"]",
949                 "〖❬1,23❭ ❬dollari statunitensi❭〗〖❬1,23❭ ❬euro❭〗〖❬0,00❭ ❬dollari statunitensi❭〗〖❬0,00❭ ❬euro❭〗"
950             },
951             {"//ldml/numbers/currencies/currency[@type=\"BMD\"]/symbol", "〖❬123.456,79 ❭BMD〗"},
952         };
953 
954         ExampleGenerator exampleGenerator = getExampleGenerator("it");
955         for (String[] testPair : testPairs) {
956             String xpath = testPair[0];
957             String expected = testPair[1];
958             String value = exampleGenerator.getCldrFile().getStringValue(xpath);
959             String actual = simplify(exampleGenerator.getExampleHtml(xpath, value));
960             assertEquals("specifics", expected, actual);
961         }
962     }
963 
Test4607()964     public void Test4607() {
965         String[][] testPairs = {
966             {
967                 "//ldml/numbers/decimalFormats[@numberSystem=\"latn\"]/decimalFormatLength[@type=\"long\"]/decimalFormat[@type=\"standard\"]/pattern[@type=\"1000\"][@count=\"one\"]",
968                 "<div class='cldr_example'><span class='cldr_substituted'>1</span> thousand</div>"
969             },
970             {
971                 "//ldml/numbers/percentFormats[@numberSystem=\"latn\"]/percentFormatLength/percentFormat[@type=\"standard\"]/pattern[@type=\"standard\"]",
972                 "<div class='cldr_example'><span class='cldr_substituted'>5</span>%</div>"
973                         + "<div class='cldr_example'><span class='cldr_substituted'>12,345</span>,<span class='cldr_substituted'>679</span>%</div>"
974                         + "<div class='cldr_example'>-<span class='cldr_substituted'>12,345</span>,<span class='cldr_substituted'>679</span>%</div>"
975             }
976         };
977         final CLDRFile nativeCldrFile = info.getEnglish();
978         ExampleGenerator exampleGenerator =
979                 new ExampleGenerator(info.getEnglish(), info.getEnglish());
980         for (String[] testPair : testPairs) {
981             String xpath = testPair[0];
982             String expected = testPair[1];
983             String value = nativeCldrFile.getStringValue(xpath);
984             String actual = exampleGenerator.getExampleHtml(xpath, value);
985             assertEquals("specifics", expected, actual);
986         }
987     }
988 
showCldrFile(final CLDRFile cldrFile)989     private void showCldrFile(final CLDRFile cldrFile) {
990         ExampleGenerator exampleGenerator = new ExampleGenerator(cldrFile, info.getEnglish());
991         checkPathValue(
992                 exampleGenerator,
993                 "//ldml/dates/calendars/calendar[@type=\"chinese\"]/dateFormats/dateFormatLength[@type=\"full\"]/dateFormat[@type=\"standard\"]/pattern[@type=\"standard\"][@draft=\"unconfirmed\"]",
994                 "EEEE d MMMMl y'x'G",
995                 null);
996 
997         for (String xpath : cldrFile.fullIterable()) {
998             if (xpath.endsWith("/alias")) {
999                 continue;
1000             }
1001             String value = cldrFile.getStringValue(xpath);
1002             checkPathValue(exampleGenerator, xpath, value, null);
1003         }
1004     }
1005 
checkPathValue( ExampleGenerator exampleGenerator, String xpath, String value, String expected)1006     private void checkPathValue(
1007             ExampleGenerator exampleGenerator, String xpath, String value, String expected) {
1008         Set<String> alreadySeen = new HashSet<>();
1009         try {
1010             String text = exampleGenerator.getExampleHtml(xpath, value);
1011             if (text == null) {
1012                 // skip
1013             } else if (text.contains("Exception")) {
1014                 errln("getExampleHtml\t" + text);
1015             } else if (!alreadySeen.contains(text)) {
1016                 if (text.contains("n/a")) {
1017                     if (text.contains("&lt;")) {
1018                         errln("Text not quoted correctly:" + "\t" + text + "\t" + xpath);
1019                     }
1020                 }
1021                 boolean skipLog = false;
1022                 if (expected != null) {
1023                     String simplified = ExampleGenerator.simplify(text, false);
1024                     // redo for debugging
1025                     text = exampleGenerator.getExampleHtml(xpath, value);
1026                     skipLog =
1027                             !assertEquals("Example text for «" + value + "»", expected, simplified);
1028                 }
1029                 if (!skipLog) {
1030                     logln("getExampleHtml\t" + text + "\t" + xpath);
1031                 }
1032                 alreadySeen.add(text);
1033             }
1034         } catch (Exception e) {
1035             errln("getExampleHtml\t" + e.getMessage());
1036         }
1037 
1038         try {
1039             String text = exampleGenerator.getHelpHtml(xpath, value);
1040             if (text == null) {
1041                 // skip
1042             } else if (text.contains("Exception")) {
1043                 errln("getHelpHtml\t" + text);
1044             } else {
1045                 logln("getExampleHtml(help)\t" + "\t" + text + "\t" + xpath);
1046             }
1047         } catch (Exception e) {
1048             if (false) {
1049                 e.printStackTrace();
1050             }
1051             errln("getHelpHtml\t" + e.getMessage());
1052         }
1053     }
1054 
TestCompactPlurals()1055     public void TestCompactPlurals() {
1056         checkCompactExampleFor("de", Count.one, "〖❬1❭ Mio. €〗", "short", "currency", "000000");
1057         checkCompactExampleFor("de", Count.other, "〖❬2❭ Mio. €〗", "short", "currency", "000000");
1058         checkCompactExampleFor("de", Count.one, "〖❬12❭ Mio. €〗", "short", "currency", "0000000");
1059         checkCompactExampleFor("de", Count.other, "〖❬10❭ Mio. €〗", "short", "currency", "0000000");
1060 
1061         checkCompactExampleFor("cs", Count.many, "〖❬1,1❭ milionu〗", "long", "decimal", "000000");
1062         checkCompactExampleFor("pl", Count.other, "〖❬1,1❭ miliona〗", "long", "decimal", "000000");
1063     }
1064 
checkCompactExampleFor( String localeID, Count many, String expected, String longVsShort, String decimalVsCurrency, String zeros)1065     private void checkCompactExampleFor(
1066             String localeID,
1067             Count many,
1068             String expected,
1069             String longVsShort,
1070             String decimalVsCurrency,
1071             String zeros) {
1072         CLDRFile cldrFile = info.getCLDRFile(localeID, true);
1073         ExampleGenerator exampleGenerator = new ExampleGenerator(cldrFile, info.getEnglish());
1074         String path =
1075                 "//ldml/numbers/"
1076                         + decimalVsCurrency
1077                         + "Formats[@numberSystem=\"latn\"]"
1078                         + "/"
1079                         + decimalVsCurrency
1080                         + "FormatLength[@type=\""
1081                         + longVsShort
1082                         + "\"]"
1083                         + "/"
1084                         + decimalVsCurrency
1085                         + "Format[@type=\"standard\"]"
1086                         + "/pattern[@type=\"1"
1087                         + zeros
1088                         + "\"][@count=\""
1089                         + many
1090                         + "\"]";
1091         checkPathValue(exampleGenerator, path, cldrFile.getStringValue(path), expected);
1092     }
1093 
1094     // ldml/numbers/currencyFormats[@numberSystem="latn"]/currencyFormatLength[@type="short"]/currencyFormat[@type="standard"]/pattern[@type="1000"][@count="one"]
1095 
TestDayPeriods()1096     public void TestDayPeriods() {
1097         // checkDayPeriod("da", "format", "morning1", "〖05:00 – 10:00〗〖❬7:30❭ morgens〗");
1098         checkDayPeriod("zh", "format", "morning1", "〖05:00 – 08:00⁻〗〖清晨❬6:30❭〗");
1099 
1100         checkDayPeriod("de", "format", "morning1", "〖05:00 – 10:00⁻〗〖❬7:30 ❭morgens〗");
1101         checkDayPeriod("de", "stand-alone", "morning1", "〖05:00 – 10:00⁻〗");
1102         checkDayPeriod("de", "format", "morning2", "〖10:00 – 12:00⁻〗〖❬11:00 ❭vormittags〗");
1103         checkDayPeriod("de", "stand-alone", "morning2", "〖10:00 – 12:00⁻〗");
1104         checkDayPeriod("pl", "format", "morning1", "〖06:00 – 10:00⁻〗〖❬8:00 ❭rano〗");
1105         checkDayPeriod("pl", "stand-alone", "morning1", "〖06:00 – 10:00⁻〗");
1106 
1107         checkDayPeriod(
1108                 "en", "format", "night1", "〖00:00 – 06:00⁻; 21:00 – 24:00⁻〗〖❬3:00 ❭at night〗");
1109         checkDayPeriod("en", "stand-alone", "night1", "〖00:00 – 06:00⁻; 21:00 – 24:00⁻〗");
1110 
1111         checkDayPeriod("en", "format", "noon", "〖12:00〗〖❬12:00 ❭noon〗");
1112         checkDayPeriod("en", "format", "midnight", "〖00:00〗〖❬12:00 ❭midnight〗");
1113         checkDayPeriod("en", "format", "am", "〖00:00 – 12:00⁻〗〖❬6:00 ❭AM〗");
1114         checkDayPeriod("en", "format", "pm", "〖12:00 – 24:00⁻〗〖❬6:00 ❭PM〗");
1115     }
1116 
checkDayPeriod( String localeId, String type, String dayPeriodCode, String expected)1117     private void checkDayPeriod(
1118             String localeId, String type, String dayPeriodCode, String expected) {
1119         CLDRFile cldrFile = info.getCLDRFile(localeId, true);
1120         ExampleGenerator exampleGenerator = new ExampleGenerator(cldrFile, info.getEnglish());
1121         String prefix =
1122                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/dayPeriods/dayPeriodContext[@type=\"";
1123         String suffix =
1124                 "\"]/dayPeriodWidth[@type=\"wide\"]/dayPeriod[@type=\"" + dayPeriodCode + "\"]";
1125         String path = prefix + type + suffix;
1126         checkPathValue(exampleGenerator, path, cldrFile.getStringValue(path), expected);
1127     }
1128 
TestAllDayPeriods()1129     public void TestAllDayPeriods() { // excludes midnight, see ICU-12278
1130         checkDayPeriodsForLocale(
1131                 "en",
1132                 "Bhm",
1133                 "〖3:00 at night〗〖9:00 in the morning〗〖12:00 noon〗〖3:00 in the afternoon〗〖7:30 in the evening〗");
1134         checkDayPeriodsForLocale(
1135                 "it",
1136                 "Bhm",
1137                 "〖3:00 di notte〗〖9:00 di mattina〗〖12:00 mezzogiorno〗〖3:00 di pomeriggio〗〖9:00 di sera〗");
1138         checkDayPeriodsForLocale(
1139                 "de",
1140                 "Bhm",
1141                 "〖2:30 nachts〗〖7:30 morgens〗〖11:00 vorm.〗〖12:30 mittags〗〖3:30 nachm.〗〖9:00 abends〗");
1142         checkDayPeriodsForLocale("zh", "Bhm", "〖凌晨2:30〗〖早上6:30〗〖上午10:00〗〖中午12:30〗〖下午4:00〗〖晚上9:30〗");
1143         checkDayPeriodsForLocale(
1144                 "am",
1145                 "EBhm",
1146                 "〖ሐሙስ በሌሊት 3:00〗〖ሐሙስ ጥዋት 9:00〗〖ሐሙስ ቀትር 12:00〗〖ሐሙስ ከሰዓት 3:00〗〖ሐሙስ በምሽት 9:00〗");
1147         checkDayPeriodsForLocale(
1148                 "hi",
1149                 "EBhms",
1150                 "〖गुरु रात 2:00:00〗〖गुरु सुबह 8:00:00〗〖गुरु दोपहर 2:00:00〗〖गुरु शाम 6:00:00〗");
1151     }
1152 
checkDayPeriodsForLocale(String localeId, String pattern, String expected)1153     public void checkDayPeriodsForLocale(String localeId, String pattern, String expected) {
1154         ExampleGenerator exampleGenerator = getExampleGenerator(localeId);
1155         String path =
1156                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]"
1157                         + "/dateTimeFormats/availableFormats/dateFormatItem"
1158                         + "[@id=\""
1159                         + pattern
1160                         + "\"]";
1161         String message = "Day periods with pattern \"" + pattern + "\"";
1162         checkValue(message, expected, exampleGenerator, path);
1163     }
1164 
1165     /**
1166      * Test that getExampleHtml returns same output for same input regardless of order in which it
1167      * is called with different inputs.
1168      *
1169      * <p>Calling getExampleHtml with a particular path and value presumably should NOT depend on
1170      * the history of paths and/or values it has been called with previously.
1171      *
1172      * <p>We formerly got different examples for SPECIAL_PATH depending on whether an example was
1173      * first gotten for USE_EVIL_PATH.
1174      *
1175      * <p>Without EVIL_PATH, got right value for SPECIAL_PATH: <div class='cldr_example'><span
1176      * class='cldr_substituted'>123 456,79 </span>€</div>
1177      *
1178      * <p>With EVIL_PATH, got wrong value for SPECIAL_PATH: <div class='cldr_example'><span
1179      * class='cldr_substituted'>123457 k </span>€</div>
1180      *
1181      * <p>This was fixed by doing clone() before returning a DecimalFormat in ICUServiceBuilder.
1182      * Reference: https://unicode-org.atlassian.net/browse/CLDR-13375.
1183      *
1184      * <p>Subsequently, DAIP changed to normalize NNBSP to NBSP for some paths, so this test was
1185      * revised not to depend on that distinction, only to expect an example containing "456,79" as a
1186      * substring (DAIP has its own unit tests). Ideally this test might be improved so as not to
1187      * depend on actual values at all, but would call getExampleHtml repeatedly for the same set of
1188      * paths but in different orders and confirm the example is the same regardless of the order;
1189      * that would require disabling the cache.
1190      *
1191      * @throws IOException
1192      */
TestExampleGeneratorConsistency()1193     public void TestExampleGeneratorConsistency() throws IOException {
1194         final String EVIL_PATH =
1195                 "//ldml/numbers/currencyFormats/currencyFormatLength[@type=\"short\"]/currencyFormat[@type=\"standard\"]/pattern[@type=\"10000\"][@count=\"one\"]";
1196         final String SPECIAL_PATH = "//ldml/numbers/currencies/currency[@type=\"EUR\"]/symbol";
1197         final String EXPECTED_TO_CONTAIN = "456,79";
1198 
1199         final CLDRFile cldrFile = info.getCLDRFile("fr", true);
1200         final ExampleGenerator eg = new ExampleGenerator(cldrFile, info.getEnglish());
1201 
1202         final String evilValue = cldrFile.getStringValue(EVIL_PATH);
1203         final String specialValue = cldrFile.getStringValue(SPECIAL_PATH);
1204 
1205         eg.getExampleHtml(EVIL_PATH, evilValue);
1206         final String specialExample = eg.getExampleHtml(SPECIAL_PATH, specialValue);
1207         if (!specialExample.contains(EXPECTED_TO_CONTAIN)) {
1208             errln("Expected example to contain " + EXPECTED_TO_CONTAIN + "; got " + specialExample);
1209         }
1210     }
1211 
TestInflectedUnitExamples()1212     public void TestInflectedUnitExamples() {
1213         String[][] deTests = {
1214             {
1215                 "one",
1216                 "accusative",
1217                 "〖❬1❭ Tag〗〖❬… für 1❭ Tag❬ …❭〗〖❌  ❬Anstatt 1❭ Tag❬ …❭〗〖〗〖1 day ≡ 24 hour〗〖1 day ≡ 1/7 week〗"
1218             },
1219             {
1220                 "one",
1221                 "dative",
1222                 "〖❬1❭ Tag〗〖❬… mit 1❭ Tag❬ …❭〗〖❌  ❬Anstatt 1❭ Tag❬ …❭〗〖〗〖1 day ≡ 24 hour〗〖1 day ≡ 1/7 week〗"
1223             },
1224             {
1225                 "one",
1226                 "genitive",
1227                 "〖❬1❭ Tages〗〖❬Anstatt 1❭ Tages❬ …❭〗〖❌  ❬… für 1❭ Tages❬ …❭〗〖〗〖1 day ≡ 24 hour〗〖1 day ≡ 1/7 week〗"
1228             },
1229             {
1230                 "one",
1231                 "nominative",
1232                 "〖❬1❭ Tag〗〖❬1❭ Tag❬ kostet (kosten) € 3,50.❭〗〖❌  ❬Anstatt 1❭ Tag❬ …❭〗〖〗〖1 day ≡ 24 hour〗〖1 day ≡ 1/7 week〗"
1233             },
1234             {
1235                 "other",
1236                 "accusative",
1237                 "〖❬1,5❭ Tage〗〖❬… für 1,5❭ Tage❬ …❭〗〖❌  ❬… mit 1,5❭ Tage❬ …❭〗〖〗〖1 day ≡ 24 hour〗〖1 day ≡ 1/7 week〗"
1238             },
1239             {
1240                 "other",
1241                 "dative",
1242                 "〖❬1,5❭ Tagen〗〖❬… mit 1,5❭ Tagen❬ …❭〗〖❌  ❬… für 1,5❭ Tagen❬ …❭〗〖〗〖1 day ≡ 24 hour〗〖1 day ≡ 1/7 week〗"
1243             },
1244             {
1245                 "other",
1246                 "genitive",
1247                 "〖❬1,5❭ Tage〗〖❬Anstatt 1,5❭ Tage❬ …❭〗〖❌  ❬… mit 1,5❭ Tage❬ …❭〗〖〗〖1 day ≡ 24 hour〗〖1 day ≡ 1/7 week〗"
1248             },
1249             {
1250                 "other",
1251                 "nominative",
1252                 "〖❬1,5❭ Tage〗〖❬1,5❭ Tage❬ kostet (kosten) € 3,50.❭〗〖❌  ❬… mit 1,5❭ Tage❬ …❭〗〖〗〖1 day ≡ 24 hour〗〖1 day ≡ 1/7 week〗"
1253             },
1254         };
1255         checkInflectedUnitExamples("de", deTests);
1256         String[][] elTests = {
1257             {
1258                 "one",
1259                 "accusative",
1260                 "〖❬1❭ ημέρα〗〖❬… ανά 1❭ ημέρα❬ …❭〗〖❌  ❬… αξίας 1❭ ημέρα❬ …❭〗〖〗〖1 day ≡ 24 hour〗〖1 day ≡ 1/7 week〗"
1261             },
1262             {
1263                 "one",
1264                 "genitive",
1265                 "〖❬1❭ ημέρας〗〖❬… αξίας 1❭ ημέρας❬ …❭〗〖❌  ❬… ανά 1❭ ημέρας❬ …❭〗〖〗〖1 day ≡ 24 hour〗〖1 day ≡ 1/7 week〗"
1266             },
1267             {
1268                 "one",
1269                 "nominative",
1270                 "〖❬1❭ ημέρα〗〖❬Η απόσταση είναι 1❭ ημέρα❬ …❭〗〖❌  ❬… αξίας 1❭ ημέρα❬ …❭〗〖〗〖1 day ≡ 24 hour〗〖1 day ≡ 1/7 week〗"
1271             },
1272             {
1273                 "other",
1274                 "accusative",
1275                 "〖❬0,9❭ ημέρες〗〖❬… ανά 0,9❭ ημέρες❬ …❭〗〖❌  ❬… αξίας 0,9❭ ημέρες❬ …❭〗〖〗〖1 day ≡ 24 hour〗〖1 day ≡ 1/7 week〗"
1276             },
1277             {
1278                 "other",
1279                 "genitive",
1280                 "〖❬0,9❭ ημερών〗〖❬… αξίας 0,9❭ ημερών❬ …❭〗〖❌  ❬… ανά 0,9❭ ημερών❬ …❭〗〖〗〖1 day ≡ 24 hour〗〖1 day ≡ 1/7 week〗"
1281             },
1282             {
1283                 "other",
1284                 "nominative",
1285                 "〖❬0,9❭ ημέρες〗〖❬Η απόσταση είναι 0,9❭ ημέρες❬ …❭〗〖❌  ❬… αξίας 0,9❭ ημέρες❬ …❭〗〖〗〖1 day ≡ 24 hour〗〖1 day ≡ 1/7 week〗"
1286             },
1287         };
1288         checkInflectedUnitExamples("el", elTests);
1289     }
1290 
checkInflectedUnitExamples(final String locale, String[][] tests)1291     private void checkInflectedUnitExamples(final String locale, String[][] tests) {
1292         final CLDRFile cldrFile = info.getCLDRFile(locale, true);
1293         ExampleGenerator exampleGenerator = getExampleGenerator(locale);
1294         String pattern =
1295                 "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"duration-day\"]/unitPattern[@count=\"COUNT\"][@case=\"CASE\"]";
1296         boolean showWorkingExamples = false;
1297         for (String[] row : tests) {
1298             String path = pattern.replace("COUNT", row[0]).replace("CASE", row[1]);
1299             String expected = row[2];
1300             String value = cldrFile.getStringValue(path);
1301             String actualRaw = exampleGenerator.getExampleHtml(path, value);
1302             String actual = ExampleGenerator.simplify(actualRaw, false);
1303             showWorkingExamples |= !assertEquals(row[0] + ", " + row[1], expected, actual);
1304         }
1305 
1306         // If a test fails, verbose will regenerate what the code thinks they should be.
1307         // Review for correctness, and then replace the test cases
1308 
1309         if (showWorkingExamples) {
1310             System.out.println(
1311                     "## The following would satisfy the test, but check to make sure the expected values are all correct!");
1312             PluralInfo pluralInfo = SDI.getPlurals(PluralType.cardinal, locale);
1313             GrammarInfo grammarInfo = SDI.getGrammarInfo(locale);
1314             final Collection<String> grammaticalValues2 =
1315                     grammarInfo.get(
1316                             GrammaticalTarget.nominal,
1317                             GrammaticalFeature.grammaticalCase,
1318                             GrammaticalScope.units);
1319 
1320             for (Count plural : pluralInfo.getCounts()) {
1321                 for (String grammaticalCase : grammaticalValues2) {
1322                     String path =
1323                             pattern.replace("COUNT", plural.toString())
1324                                     .replace("CASE", grammaticalCase);
1325                     String value = cldrFile.getStringValue(path);
1326                     String actualRaw = exampleGenerator.getExampleHtml(path, value);
1327                     String actual = ExampleGenerator.simplify(actualRaw, false);
1328                     System.out.println(
1329                             "{\""
1330                                     + plural
1331                                     + "\", "
1332                                     + "\""
1333                                     + grammaticalCase
1334                                     + "\", "
1335                                     + "\""
1336                                     + actual
1337                                     + "\"},");
1338                 }
1339             }
1340         }
1341     }
1342 
TestMinimalPairExamples()1343     public void TestMinimalPairExamples() {
1344         String[][] tests = {
1345             {
1346                 "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"one\"]",
1347                 "〖❬1❭ Tag〗〖❌  ❬2❭ Tag〗"
1348             },
1349             {
1350                 "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"other\"]",
1351                 "〖❬2❭ Tage〗〖❌  ❬1❭ Tage〗"
1352             },
1353             {
1354                 "//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"accusative\"]",
1355                 "〖… für ❬1 metrische Pint❭ …〗〖❌  … für ❬1 metrischen Pint❭ …〗"
1356             },
1357             {
1358                 "//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"dative\"]",
1359                 "〖… mit ❬1 metrischen Pint❭ …〗〖❌  … mit ❬1 metrische Pint❭ …〗"
1360             },
1361             {
1362                 "//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"genitive\"]",
1363                 "〖Anstatt ❬1 metrischen Pints❭ …〗〖❌  Anstatt ❬1 metrische Pint❭ …〗"
1364             },
1365             {
1366                 "//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"nominative\"]",
1367                 "〖❬2 metrische Pints❭ kostet (kosten) € 3,50.〗〖❌  ❬1 metrische Pint❭ kostet (kosten) € 3,50.〗"
1368             },
1369             {
1370                 "//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"feminine\"]",
1371                 "〖Die ❬Stunde❭ ist …〗〖❌  Die ❬Tag❭ ist …〗"
1372             },
1373             {
1374                 "//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"masculine\"]",
1375                 "〖Der ❬Tag❭ ist …〗〖❌  Der ❬Stunde❭ ist …〗"
1376             },
1377             {
1378                 "//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"neuter\"]",
1379                 "〖Das ❬Jahr❭ ist …〗〖❌  Das ❬Stunde❭ ist …〗"
1380             },
1381         };
1382         checkMinimalPairExamples("de", tests);
1383 
1384         String[][] elTests = {
1385             {
1386                 "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"one\"]",
1387                 "〖❬1❭ ημέρα〗〖❌  ❬2❭ ημέρα〗"
1388             },
1389             {
1390                 "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"other\"]",
1391                 "〖❬2❭ ημέρες〗〖❌  ❬1❭ ημέρες〗"
1392             },
1393             {
1394                 "//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"accusative\"]",
1395                 "〖… ανά ❬1 τόνο❭ …〗〖❌  … ανά ❬1 τόνου❭ …〗"
1396             },
1397             {
1398                 "//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"genitive\"]",
1399                 "〖… αξίας ❬1 τόνου❭ …〗〖❌  … αξίας ❬1 τόνο❭ …〗"
1400             },
1401             {
1402                 "//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"nominative\"]",
1403                 "〖Η απόσταση είναι ❬2 τόνοι❭ …〗〖❌  Η απόσταση είναι ❬1 τόνο❭ …〗"
1404             },
1405             {
1406                 "//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"feminine\"]",
1407                 "〖Η ❬ημέρα❭ είναι〗〖❌  Η ❬μήνας❭ είναι〗"
1408             },
1409             {
1410                 "//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"masculine\"]",
1411                 "〖Ο ❬μήνας❭ θα είναι〗〖❌  Ο ❬ημέρα❭ θα είναι〗"
1412             },
1413             {
1414                 "//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"neuter\"]",
1415                 "〖Το ❬λεπτό❭ ήταν〗〖❌  Το ❬ημέρα❭ ήταν〗"
1416             },
1417         };
1418         checkMinimalPairExamples("el", elTests);
1419     }
1420 
checkMinimalPairExamples(final String locale, String[][] tests)1421     private void checkMinimalPairExamples(final String locale, String[][] tests) {
1422         final CLDRFile cldrFile = info.getCLDRFile(locale, true);
1423         ExampleGenerator exampleGenerator = getExampleGenerator(locale);
1424         boolean showWorkingExamples = false;
1425         for (String[] row : tests) {
1426             String path = row[0];
1427             String expected = row[1];
1428             String value = cldrFile.getStringValue(path);
1429             String actualRaw = exampleGenerator.getExampleHtml(path, value);
1430             String actual = ExampleGenerator.simplify(actualRaw, false);
1431             showWorkingExamples |= !assertEquals(row[0] + ", " + row[1], expected, actual);
1432         }
1433 
1434         // If a test fails, verbose will regenerate what the code thinks they should be.
1435         // Review for correctness, and then replace the test cases
1436 
1437         if (showWorkingExamples) {
1438             System.out.println(
1439                     "## The following would satisfy the test, but check to make sure the expected values are all correct!");
1440             PluralInfo pluralInfo = SDI.getPlurals(PluralType.cardinal, locale);
1441             ArrayList<String> paths = new ArrayList<>();
1442 
1443             for (Count plural : pluralInfo.getCounts()) {
1444                 paths.add(
1445                         "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\""
1446                                 + plural
1447                                 + "\"]");
1448             }
1449             GrammarInfo grammarInfo = SDI.getGrammarInfo(locale);
1450             for (String grammaticalValues :
1451                     grammarInfo.get(
1452                             GrammaticalTarget.nominal,
1453                             GrammaticalFeature.grammaticalCase,
1454                             GrammaticalScope.units)) {
1455                 paths.add(
1456                         "//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\""
1457                                 + grammaticalValues
1458                                 + "\"]");
1459             }
1460             for (String grammaticalValues :
1461                     grammarInfo.get(
1462                             GrammaticalTarget.nominal,
1463                             GrammaticalFeature.grammaticalGender,
1464                             GrammaticalScope.units)) {
1465                 paths.add(
1466                         "//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\""
1467                                 + grammaticalValues
1468                                 + "\"]");
1469             }
1470             for (String path : paths) {
1471                 String value = cldrFile.getStringValue(path);
1472                 String actualRaw = exampleGenerator.getExampleHtml(path, value);
1473                 String actual = ExampleGenerator.simplify(actualRaw, false);
1474                 System.out.println("{\"" + path.replace("\"", "\\\"") + "\", \"" + actual + "\"},");
1475             }
1476         }
1477     }
1478 
1479     /**
1480      * Test the production of minimal pair examples, to make sure we get no exceptions. If -v, then
1481      * generates lines for spreadsheet survey
1482      */
TestListMinimalPairExamples()1483     public void TestListMinimalPairExamples() {
1484         Set<String> localesWithGrammar = SDI.hasGrammarInfo();
1485         if (isVerbose()) {
1486             System.out.println(
1487                     "\nLC\tLocale\tType\tCode\tCurrent Pattern\tVerify this is correct!\tVerify this is wrong!");
1488         }
1489         final String unused = "∅";
1490         List<String> pluralSheet = new ArrayList();
1491         for (String locale : localesWithGrammar) {
1492             final CLDRFile cldrFile = info.getCLDRFile(locale, true);
1493             ExampleGenerator exampleGenerator = getExampleGenerator(locale);
1494 
1495             PluralInfo pluralInfo = SDI.getPlurals(PluralType.cardinal, cldrFile.getLocaleID());
1496             Map<String, Pair<String, String>> paths = new LinkedHashMap<>();
1497 
1498             Set<Count> counts = pluralInfo.getCounts();
1499             if (counts.size() > 1) {
1500                 for (Count plural : counts) {
1501                     paths.put(
1502                             "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\""
1503                                     + plural
1504                                     + "\"]",
1505                             Pair.of("plural", plural.toString()));
1506                 }
1507             }
1508             GrammarInfo grammarInfo = SDI.getGrammarInfo(locale);
1509             Collection<String> unitCases =
1510                     grammarInfo.get(
1511                             GrammaticalTarget.nominal,
1512                             GrammaticalFeature.grammaticalCase,
1513                             GrammaticalScope.units);
1514             Collection<String> generalCasesRaw =
1515                     grammarInfo.get(
1516                             GrammaticalTarget.nominal,
1517                             GrammaticalFeature.grammaticalCase,
1518                             GrammaticalScope.general);
1519             Collection<CaseValues> generalCases =
1520                     generalCasesRaw.stream()
1521                             .map(x -> CaseValues.valueOf(x))
1522                             .collect(Collectors.toCollection(TreeSet::new));
1523             for (CaseValues unitCase0 : generalCases) {
1524                 String unitCase = unitCase0.toString();
1525                 paths.put(
1526                         (unitCases.contains(unitCase) ? "" : unused)
1527                                 + "//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\""
1528                                 + unitCase
1529                                 + "\"]",
1530                         Pair.of("case", unitCase));
1531             }
1532             Collection<String> unitGenders =
1533                     grammarInfo.get(
1534                             GrammaticalTarget.nominal,
1535                             GrammaticalFeature.grammaticalGender,
1536                             GrammaticalScope.units);
1537             Collection<String> generalGenders =
1538                     grammarInfo.get(
1539                             GrammaticalTarget.nominal,
1540                             GrammaticalFeature.grammaticalGender,
1541                             GrammaticalScope.general);
1542             for (String unitGender : generalGenders) {
1543                 paths.put(
1544                         (unitGenders.contains(unitGender) ? "" : unused)
1545                                 + "//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\""
1546                                 + unitGender
1547                                 + "\"]",
1548                         Pair.of("gender", unitGender));
1549             }
1550             String localeName = CLDRConfig.getInstance().getEnglish().getName(locale);
1551             boolean pluralOnly = true;
1552             if (paths.isEmpty()) {
1553                 pluralSheet.add(
1554                         locale + "\t" + localeName + "\t" + "N/A" + "\t" + "N/A" + "\t" + "N/A");
1555             } else {
1556                 for (Entry<String, Pair<String, String>> pathAndLabel : paths.entrySet()) {
1557                     String path = pathAndLabel.getKey();
1558                     String label = pathAndLabel.getValue().getFirst();
1559                     String code = pathAndLabel.getValue().getSecond();
1560                     if (!label.equals("plural")) {
1561                         pluralOnly = false;
1562                     }
1563                 }
1564                 String lastLabel = "";
1565                 for (Entry<String, Pair<String, String>> pathAndLabel : paths.entrySet()) {
1566                     String path = pathAndLabel.getKey();
1567                     String label = pathAndLabel.getValue().getFirst();
1568                     String code = pathAndLabel.getValue().getSecond();
1569                     String pattern = "";
1570                     String examples = "";
1571                     if (!label.equals(lastLabel)) {
1572                         lastLabel = label;
1573                         if (!pluralOnly) {
1574                             if (isVerbose()) {
1575                                 System.out.println();
1576                             }
1577                         }
1578                     }
1579                     if (path.startsWith(unused)) {
1580                         pattern = "��  Not used with formatted units";
1581                     } else {
1582                         pattern = cldrFile.getStringValue(path);
1583                         if (pattern == null) {
1584                             warnln(
1585                                     "Missing ExampleGenerator html example for "
1586                                             + locale
1587                                             + "("
1588                                             + localeName
1589                                             + "): "
1590                                             + path);
1591                             continue;
1592                         }
1593                         String actualRaw = exampleGenerator.getExampleHtml(path, pattern);
1594                         String actualSimplified = ExampleGenerator.simplify(actualRaw, false);
1595                         examples =
1596                                 actualSimplified
1597                                         .replace("〗〖", "\t")
1598                                         .replace("〗", "")
1599                                         .replace("〖", "");
1600                         List<String> exampleList =
1601                                 com.google.common.base.Splitter.on('\t')
1602                                         .trimResults()
1603                                         .splitToList(examples);
1604                         final int exampleListSize = exampleList.size();
1605                         switch (exampleListSize) {
1606                             case 2: // ok
1607                                 break;
1608                             case 1:
1609                                 warnln(
1610                                         "Expecting exactly 2 examples: "
1611                                                 + exampleList
1612                                                 + ", but got "
1613                                                 + exampleListSize);
1614                                 break;
1615                             default:
1616                                 errln(
1617                                         "Expecting exactly 2 examples: "
1618                                                 + exampleList
1619                                                 + ", but got "
1620                                                 + exampleListSize);
1621                                 break;
1622                         }
1623                         StringBuilder exampleBuffer = new StringBuilder();
1624                         for (String exampleItem : exampleList) {
1625                             if (exampleItem.contains("❬null❭") || exampleItem.contains("❬n/a❭")) {
1626                                 boolean bad = (exampleItem.contains("❌"));
1627                                 exampleItem = "��  No unit available";
1628                                 if (bad) {
1629                                     exampleItem = "❌  " + exampleItem;
1630                                 }
1631                             }
1632                             if (exampleBuffer.length() != 0) {
1633                                 exampleBuffer.append('\t');
1634                             }
1635                             exampleBuffer.append(exampleItem);
1636                         }
1637                         examples = exampleBuffer.toString();
1638                     }
1639                     String line =
1640                             (locale
1641                                     + "\t"
1642                                     + localeName
1643                                     + "\t"
1644                                     + label
1645                                     + "\t"
1646                                     + code
1647                                     + "\t"
1648                                     + pattern
1649                                     + "\t"
1650                                     + examples);
1651                     if (pluralOnly) {
1652                         pluralSheet.add(line);
1653                     } else {
1654                         if (isVerbose()) {
1655                             System.out.println(line);
1656                         }
1657                     }
1658                 }
1659             }
1660             if (pluralOnly) {
1661                 pluralSheet.add("");
1662             } else if (isVerbose()) {
1663                 System.out.println();
1664             }
1665         }
1666         if (isVerbose()) {
1667             System.out.println("#################### Plural Only ###################");
1668             for (String line : pluralSheet) {
1669                 System.out.println(line);
1670             }
1671         }
1672     }
1673 
TestUnicodeSetExamples()1674     public void TestUnicodeSetExamples() {
1675         String[][] tests = {
1676             {
1677                 "hi",
1678                 "//ldml/characters/exemplarCharacters[@type=\"auxiliary\"]",
1679                 "[ॄ‌‍]",
1680                 "〖‎��️ ॑ ॒ ॠ ॡ ॻ ॼ ॾ ॿ ॢ ॣ〗〖❰ZWNJ❱ ≡ cursive non-joiner〗〖❰ZWJ❱ ≡ cursive joiner〗〖❬internal: ❭[ॄ‌‍]〗"
1681             },
1682             // TODO: This test is too fragile. Commented out for discussion in CLDR-17608
1683             // {
1684             //     "hu",
1685             //     "//ldml/characters/exemplarCharacters[@type=\"auxiliary\"]",
1686             //     "[qw-yàâ-èê-ìîïñòôøùûÿāăēĕīĭōŏœūŭ]",
1687             //     "〖‎��️ · ắ ằ ẵ ẳ ấ ầ ẫ ẩ ǎ a̧ ą ą́ a᷆ a᷇ ả ạ ặ ậ a̱ aː áː àː ɓ ć ĉ č ċ ď ḑ đ ḍ ḓ
1688             // ð ɖ ɗ ế ề ễ ể ě ẽ ė ę ę́ e᷆ e᷇ ẻ ẹ ẹ́ ẹ̀ ệ e̱ eː éː èː ǝ ǝ́ ǝ̀ ǝ̂ ǝ̌ ǝ̄ ə ə́ ə̀ ə̂ ə̌
1689             // ə̄ ɛ ɛ́ ɛ̀ ɛ̂ ɛ̌ ɛ̈ ɛ̃ ɛ̧ ɛ̄ ɛ᷆ ɛ᷇ ɛ̱ ɛ̱̈ ƒ ğ ĝ ǧ g̃ ġ ģ g̱ gʷ ǥ ɣ ĥ ȟ ħ ḥ ʻ ǐ ĩ İ i̧
1690             // į į́ i᷆ i᷇ ỉ ị i̱ iː íː ìː íj́ ı ɨ ɨ́ ɨ̀ ɨ̂ ɨ̌ ɨ̄ ɩ ɩ́ ɩ̀ ɩ̂ ĵ ǩ ķ ḵ kʷ ƙ ĺ ľ ļ ł ḷ ḽ
1691             // ḻ ḿ m̀ m̄ ń ǹ ň ṅ ņ n̄ ṇ ṋ ṉ ɲ ŋ ŋ́ ŋ̀ ŋ̄ ố ồ ỗ ổ ǒ õ ǫ ǫ́ o᷆ o᷇ ỏ ơ ớ ờ ỡ ở ợ ọ ọ́
1692             // ọ̀ ộ o̱ oː óː òː ɔ ɔ́ ɔ̀ ɔ̂ ɔ̌ ɔ̈ ɔ̃ ɔ̧ ɔ̄ ɔ᷆ ɔ᷇ ɔ̱ ŕ ř ŗ ṛ ś ŝ š ş ṣ ș ß ť ṭ ț ṱ ṯ ŧ
1693             // ǔ ů ũ u̧ ų u᷆ u᷇ ủ ư ứ ừ ữ ử ự ụ uː úː ùː ʉ ʉ́ ʉ̀ ʉ̂ ʉ̌ ʉ̈ ʉ̄ ʊ ʊ́ ʊ̀ ʊ̂ ṽ ʋ ẃ ẁ ŵ ẅ
1694             // ý ỳ ŷ ỹ ỷ ỵ y̱ ƴ ź ž ż ẓ ʒ ǯ þ ʔ ˀ ʼ ꞌ ǀ ǁ ǂ ǃ〗〖❬internal:
1695             // ❭[qw-yàâ-èê-ìîïñòôøùûÿāăēĕīĭōŏœūŭ]〗"
1696             // },
1697             {
1698                 "de",
1699                 "//ldml/characters/parseLenients[@scope=\"date\"][@level=\"lenient\"]/parseLenient[@sample=\"-\"]",
1700                 "[\\u200B \\- . ๎ ็]",
1701                 "〖‎➕ ❰WNJ❱ ๎ ็〗〖‎➖ ‑ /〗〖❰WNJ❱ ≡ allow line wrap after, aka ZWSP〗〖❬internal: ❭[\\-.็๎​]〗"
1702             },
1703             {
1704                 "de",
1705                 "//ldml/characters/exemplarCharacters",
1706                 "[\\u200B a-z ๎ ็]",
1707                 "〖‎➕ ❰WNJ❱ ๎ ็〗〖‎➖ ä ö ß ü〗〖❰WNJ❱ ≡ allow line wrap after, aka ZWSP〗〖❬internal: ❭[a-z็๎​]〗"
1708             },
1709             {"de", "//ldml/characters/exemplarCharacters", "a-z ❰ZWSP❱", null},
1710         };
1711 
1712         for (String[] test : tests) {
1713             final String locale = test[0];
1714             final String path = test[1];
1715             final String value = test[2];
1716             final String expected = test[3];
1717             ExampleGenerator exampleGenerator = getExampleGenerator(locale);
1718             String actual =
1719                     ExampleGenerator.simplify( //
1720                             exampleGenerator.getExampleHtml(path, value));
1721             assertEquals(locale + path + "=" + value, expected, actual);
1722         }
1723     }
1724 
TestEraMap()1725     public void TestEraMap() {
1726         ExampleGenerator exampleGenerator = getExampleGenerator("en");
1727         Relation<String, String> keyToSubtypes = SupplementalDataInfo.getInstance().getBcp47Keys();
1728         Set<String> calendars = keyToSubtypes.get("ca"); // gets calendar codes
1729         Map<String, String> codeToType =
1730                 new HashMap<String, String>() {
1731                     { // calendars where code != type
1732                         put("gregory", "gregorian");
1733                         put("iso8601", "gregorian");
1734                         put("ethioaa", "ethiopic-amete-alem");
1735                         put("islamic-civil", "islamic");
1736                         put("islamic-rgsa", "islamic");
1737                         put("islamic-tbla", "islamic");
1738                         put("islamic-umalqura", "islamic");
1739                         put("islamicc", "islamic");
1740                     }
1741                 };
1742         for (String id : calendars) {
1743             if (codeToType.containsKey(id)) {
1744                 id = codeToType.get(id);
1745             }
1746             Map<String, List<Date>> calendarMap = exampleGenerator.CALENDAR_ERAS;
1747             assertTrue(
1748                     "CALENDAR_ERAS map contains calendar type \"" + id + "\"",
1749                     calendarMap.containsKey(id));
1750         }
1751     }
1752 
TestEraFormats()1753     public void TestEraFormats() {
1754         ExampleGenerator exampleGeneratorJa = getExampleGenerator("ja");
1755         ExampleGenerator exampleGeneratorEs = getExampleGenerator("es");
1756         ExampleGenerator exampleGeneratorZh = getExampleGenerator("zh");
1757         checkValue(
1758                 "japanese type=235 abbreviated",
1759                 "〖平成1年〗",
1760                 exampleGeneratorJa,
1761                 "//ldml/dates/calendars/calendar[@type=\"japanese\"]/eras/eraAbbr/era[@type=\"235\"]");
1762         checkValue(
1763                 "gregorian type=0 wide",
1764                 "〖1 antes de Cristo〗",
1765                 exampleGeneratorEs,
1766                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/eras/eraNames/era[@type=\"0\"]");
1767         checkValue(
1768                 "gregorian type=0-variant wide",
1769                 "〖1 antes de la era común〗",
1770                 exampleGeneratorEs,
1771                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/eras/eraNames/era[@type=\"0\"][@alt=\"variant\"]");
1772         checkValue(
1773                 "roc type=1 abbreviated",
1774                 "〖民国1年〗",
1775                 exampleGeneratorZh,
1776                 "//ldml/dates/calendars/calendar[@type=\"roc\"]/eras/eraAbbr/era[@type=\"1\"]");
1777     }
1778 
TestQuarterFormats()1779     public void TestQuarterFormats() {
1780         ExampleGenerator exampleGenerator = getExampleGenerator("ti");
1781         checkValue(
1782                 "ti Q2 format wide",
1783                 "〖2ይ ርብዒ 1999〗",
1784                 exampleGenerator,
1785                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/quarters/quarterContext[@type=\"format\"]/quarterWidth[@type=\"wide\"]/quarter[@type=\"2\"]");
1786         checkValue(
1787                 "ti Q2 format abbreviated",
1788                 "〖ር2 1999〗",
1789                 exampleGenerator,
1790                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/quarters/quarterContext[@type=\"format\"]/quarterWidth[@type=\"abbreviated\"]/quarter[@type=\"2\"]");
1791         checkValue(
1792                 "ti Q4 stand-alone wide",
1793                 "〖4ይ ርብዒ 1999〗",
1794                 exampleGenerator,
1795                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/quarters/quarterContext[@type=\"stand-alone\"]/quarterWidth[@type=\"wide\"]/quarter[@type=\"4\"]");
1796         checkValue(
1797                 "ti Q4 stand-alone abbreviated",
1798                 "〖ር4 1999〗",
1799                 exampleGenerator,
1800                 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/quarters/quarterContext[@type=\"stand-alone\"]/quarterWidth[@type=\"abbreviated\"]/quarter[@type=\"4\"]");
1801     }
1802 
TestRelative()1803     public void TestRelative() {
1804         ExampleGenerator exampleGeneratorIt = getExampleGenerator("it");
1805         ExampleGenerator exampleGeneratorAm = getExampleGenerator("am");
1806         checkValue(
1807                 "it relative day type 2",
1808                 "〖Dopodomani (5 settembre)〗〖5 settembre (dopodomani)〗",
1809                 exampleGeneratorIt,
1810                 "//ldml/dates/fields/field[@type=\"day\"]/relative[@type=\"2\"]");
1811         checkValue(
1812                 "it relative hour future-other",
1813                 "〖Tra ❬10❭ ore (18:25)〗〖18:25 (tra ❬10❭ ore)〗",
1814                 exampleGeneratorIt,
1815                 "//ldml/dates/fields/field[@type=\"hour\"]/relativeTime[@type=\"future\"]/relativeTimePattern[@count=\"other\"]");
1816         checkValue(
1817                 "it relative year past-one",
1818                 "〖❬1❭ anno fa (settembre 1999)〗〖settembre 1999 (❬1❭ anno fa)〗",
1819                 exampleGeneratorIt,
1820                 "//ldml/dates/fields/field[@type=\"year\"]/relativeTime[@type=\"past\"]/relativeTimePattern[@count=\"one\"]");
1821         checkValue(
1822                 "am relative month future-one",
1823                 "〖በ❬1❭ ወር ውስጥ (ሴፕቴምበር 1999)〗〖ሴፕቴምበር 1999 (በ❬1❭ ወር ውስጥ)〗",
1824                 exampleGeneratorAm,
1825                 "//ldml/dates/fields/field[@type=\"month\"]/relativeTime[@type=\"future\"]/relativeTimePattern[@count=\"one\"]");
1826         checkValue(
1827                 "am relative month future-other",
1828                 "〖በ❬10❭ ወራት ውስጥ (ሴፕቴምበር 1999)〗〖ሴፕቴምበር 1999 (በ❬10❭ ወራት ውስጥ)〗",
1829                 exampleGeneratorAm,
1830                 "//ldml/dates/fields/field[@type=\"month\"]/relativeTime[@type=\"future\"]/relativeTimePattern[@count=\"other\"]");
1831     }
1832 
1833     static final class MissingKey implements Comparable<MissingKey> {
1834         final SectionId sectionId;
1835         final PageId pageId;
1836         final String starred;
1837 
MissingKey(SectionId sectionId, PageId pageId, String starred)1838         public MissingKey(SectionId sectionId, PageId pageId, String starred) {
1839             this.sectionId = sectionId;
1840             this.pageId = pageId;
1841             this.starred = starred;
1842         }
1843 
1844         @Override
compareTo(MissingKey o)1845         public int compareTo(MissingKey o) {
1846             return ComparisonChain.start()
1847                     .compare(sectionId, o.sectionId)
1848                     .compare(pageId, o.pageId)
1849                     .compare(starred, o.starred)
1850                     .result();
1851         }
1852 
1853         @Override
equals(Object obj)1854         public boolean equals(Object obj) {
1855             return compareTo((MissingKey) obj) == 0;
1856         }
1857 
1858         @Override
hashCode()1859         public int hashCode() {
1860             return Objects.hashCode(sectionId, pageId, starred);
1861         }
1862     }
1863 
TestForMissing()1864     public void TestForMissing() {
1865         Factory factory = info.getCldrFactory(); // don't worry about examples for annotations
1866         DtdData dtdData = DtdData.getInstance(DtdType.ldml);
1867         PathHeader.Factory phf = PathHeader.getFactory();
1868         Set<String> seenPaths =
1869                 new HashSet<>(); // assume whether there is an example is independent of locale, to
1870         // speed up the test.
1871         final String separator = "•";
1872         PathStarrer ps = new PathStarrer();
1873         ps.setSubstitutionPattern("*");
1874 
1875         // Setup for calling phase.getShowRowAction
1876         DummyPathValueInfo dummyPathValueInfo = null;
1877         VoterInfo dummyVoterInfo = null;
1878         UserInfo dummyUserInfo = null;
1879 
1880         // disabled, since it doesn't eliminate anything. However, left under a flag just in case it
1881         // is useful later
1882         if (CHECK_ROW_ACTION) {
1883             dummyPathValueInfo = new DummyPathValueInfo();
1884             dummyVoterInfo =
1885                     new VoterInfo(
1886                             Organization.cldr,
1887                             org.unicode.cldr.util.VoteResolver.Level.vetter,
1888                             "somename");
1889             dummyUserInfo =
1890                     new UserInfo() {
1891                         @Override
1892                         public VoterInfo getVoterInfo() {
1893                             return dummyVoterInfo;
1894                         }
1895                     };
1896         }
1897 
1898         // Use representative locales:
1899         // 'en' for the most coverage,
1900         // 'de' and 'cs' for more complex inflections,
1901         // 'ja' for CJK issues
1902 
1903         for (String localeId : List.of("de", "en", "cs", "ja")) {
1904             CLDRFile cldrFile = factory.make(localeId, true);
1905             CLDRFile cldrFileUnresolved = cldrFile.getUnresolved();
1906 
1907             ExampleGenerator exampleGenerator = new ExampleGenerator(cldrFile, info.getEnglish());
1908             if (CHECK_ROW_ACTION) {
1909                 dummyPathValueInfo.setLocale(CLDRLocale.getInstance(localeId));
1910             }
1911             // for collecting data
1912 
1913             Counter<MissingKey> countWithExamples = new Counter<>();
1914             Map<MissingKey, String> samplesForWith = new HashMap<>();
1915             Counter<MissingKey> countWithoutExamples = new Counter<>();
1916             Multimap<MissingKey, String> samplesForWithout = TreeMultimap.create();
1917             Map<MissingKey, String> sampleUrlForWithout = new TreeMap<>();
1918             TreeMultimap<String, String> skipped = TreeMultimap.create();
1919 
1920             // for each path in the file, check that there is an example
1921             // or we know why not
1922 
1923             for (String xpath : cldrFile.fullIterable()) {
1924                 if (seenPaths.contains(xpath)) {
1925                     continue;
1926                 }
1927                 seenPaths.add(xpath);
1928                 if (xpath.endsWith("/alias")) {
1929                     continue;
1930                 }
1931                 String value = cldrFile.getStringValue(xpath);
1932                 if (value == null) {
1933                     continue;
1934                 }
1935 
1936                 final XPathParts parts = XPathParts.getFrozenInstance(xpath);
1937                 if (dtdData.isDeprecated(parts)) {
1938                     continue;
1939                 }
1940                 Level level = SDI.getCoverageLevel(xpath, "en");
1941                 if (level.compareTo(Level.COMPREHENSIVE) == 0) {
1942                     continue;
1943                 }
1944                 String starred = ps.set(xpath);
1945                 String attrs = ps.getAttributesString(separator);
1946 
1947                 PathHeader ph = phf.fromPath(xpath);
1948                 if (CHECK_ROW_ACTION) {
1949                     dummyPathValueInfo.setXpath(xpath);
1950                     dummyPathValueInfo.setBaselineValue(cldrFileUnresolved.getStringValue(xpath));
1951                     StatusAction action =
1952                             Phase.SUBMISSION.getShowRowAction(
1953                                     dummyPathValueInfo, InputMethod.DIRECT, ph, dummyUserInfo);
1954                     if (action.isForbidden()) {
1955                         System.out.println(xpath + " is forbidden");
1956                         continue;
1957                     }
1958                 }
1959 
1960                 MissingKey key = new MissingKey(ph.getSectionId(), ph.getPageId(), starred);
1961                 String example = null;
1962                 try {
1963                     example = exampleGenerator.getExampleHtml(xpath, value);
1964                 } catch (Exception e) {
1965                 }
1966                 if (example == null) {
1967                     String missingResult = getResult(starred, attrs);
1968                     if (missingResult != null) {
1969                         skipped.put(missingResult, starred);
1970                         continue;
1971                     }
1972 
1973                     samplesForWithout.put(key, sampleAttrAndValue(ps, separator, value));
1974                     sampleUrlForWithout.put(key, ph.getUrl(BaseUrl.PRODUCTION, localeId));
1975                     countWithoutExamples.add(key, 1);
1976                 } else {
1977                     if (!samplesForWith.containsKey(key)) {
1978                         samplesForWith.put(key, sampleAttrAndValue(ps, separator, value));
1979                     }
1980                     countWithExamples.add(key, 1);
1981                 }
1982             }
1983             Set<MissingKey> keys = new TreeSet<>();
1984             keys.addAll(countWithoutExamples.keySet());
1985             keys.addAll(countWithExamples.keySet());
1986             List<String> missingItems = new ArrayList<>();
1987 
1988             // we use the missing keys, which sort by section, page, path
1989 
1990             for (MissingKey key : keys) {
1991                 final long countWithout = countWithoutExamples.get(key);
1992                 if (countWithout == 0) { // ok, no missing
1993                     continue;
1994                 }
1995                 final Collection<String> sampleForWithoutItem = samplesForWithout.get(key);
1996                 final String sampleForWithItem = samplesForWith.get(key);
1997                 final long countWith = countWithExamples.get(key);
1998                 final double doneRatio = countWith / (double) (countWith + countWithout);
1999                 missingItems.add(
2000                         TAB_JOINER.join(
2001                                 doneRatio,
2002                                 countWithout,
2003                                 (sampleForWithItem == null
2004                                         ? sampleForWithoutItem.iterator().next()
2005                                         : Joiner.on("; ")
2006                                                 .join(Iterables.limit(sampleForWithoutItem, 5))),
2007                                 sampleUrlForWithout.get(key),
2008                                 countWith,
2009                                 (sampleForWithItem == null ? "n/a" : sampleForWithItem),
2010                                 key.sectionId,
2011                                 key.pageId,
2012                                 key.starred));
2013             }
2014 
2015             // show all the skipped items, and logKnownIssue items
2016 
2017             for (Entry<String, Collection<String>> entry : skipped.asMap().entrySet()) {
2018                 final String ticketComment = entry.getKey();
2019                 final String paths = CR_TAB2_JOINER.join(entry.getValue());
2020                 if (ticketComment.equals(SKIP)) {
2021                     logln(ticketComment + ";\n\t\t" + paths);
2022                 } else {
2023                     int spacePos = ticketComment.indexOf(' ');
2024                     logKnownIssue(
2025                             ticketComment.substring(0, spacePos),
2026                             ticketComment.substring(spacePos + 1)
2027                                     + ")\n\t\t(For the following paths:\n\t\t"
2028                                     + paths);
2029                 }
2030             }
2031 
2032             // Here is where missing examples will show up.
2033             // If it is ok to skip them (only when there is no reasonable example), add to
2034             // HANDLE_MISSING data
2035             // Otherwise add an example
2036 
2037             if (!missingItems.isEmpty()) {
2038                 errln(
2039                         TAB_JOINER.join(localeId, "missing examples:", missingItems.size())
2040                                 + "\n"
2041                                 + "\nDone?\tWithout\tSample Attrs\tURL\tWith\tSample Attrs\tSection\tPage\tStarred Pattern\n"
2042                                 + Joiner.on("\n").join(missingItems));
2043             }
2044         }
2045     }
2046 
2047     /**
2048      * This is a mechanism for TestMissing exceptions: a) skipping the items where there are no
2049      * reasonable examples b) logging known issues where we know what to do, and have filed tickets
2050      *
2051      * <p>Then only new missing examples will trigger errors.
2052      *
2053      * <p>If new structure is added, an example should be added at the same time if possible,
2054      * otherwise it should be added with "OK".
2055      */
2056     static final Map<String, Map<String, String>> HANDLE_MISSING;
2057 
2058     static {
2059         // The format is 3 items
2060         // a) a list of paths (separated by space or just concatenated)
2061         // b) a return value. OK to just skip, otherwise <ticket><space><comment>
2062         // c) a list of 1 or more attributes (like "mul", "zxx") or a wildcard "*"
2063 
2064         String[][] data = {
2065             // mul➔«Multiple languages»; zxx➔«No linguistic content»
2066             {SKIP, "//ldml/localeDisplayNames/languages/language[@type=\"*\"]", "mul", "zxx"},
2067             {
2068                 SKIP,
2069                 "//ldml/characters/moreInformation"
2070                         + "//ldml/dates/fields/field[@type=\"*\"]/relative[@type=\"*\"]"
2071                         + "//ldml/dates/timeZoneNames/gmtZeroFormat"
2072                         + "//ldml/dates/timeZoneNames/metazone[@type=\"*\"]/short/standard"
2073                         + "//ldml/numbers/symbols[@numberSystem=\"*\"]/infinity"
2074                         + "//ldml/numbers/symbols[@numberSystem=\"*\"]/nan"
2075                         + "//ldml/dates/calendars/calendar[@type=\"*\"]/eras/eraAbbr/era[@type=\"*\"][@alt=\"*\"]"
2076                         + "//ldml/dates/calendars/calendar[@type=\"*\"]/eras/eraNames/era[@type=\"*\"][@alt=\"*\"]"
2077                         + "//ldml/typographicNames/styleName[@type=\"*\"][@subtype=\"*\"][@alt=\"*\"]",
2078                 "*"
2079             },
2080             {
2081                 "CLDR-17756 Add examples of date intervals",
2082                 "//ldml/dates/calendars/calendar[@type=\"*\"]/dateTimeFormats/intervalFormats/intervalFormatItem[@id=\"*\"]/greatestDifference[@id=\"*\"]",
2083                 "*"
2084             },
2085             {
2086                 "CLDR-17756 Show \"{0} ¤¤\" with formatted number and ISO code, eg {0} ¤¤ becomes 3,5 EUR",
2087                 "//ldml/numbers/currencyFormats[@numberSystem=\"*\"]/currencyPatternAppendISO",
2088                 "*"
2089             },
2090             {
2091                 "CLDR-17756 Show 2 currencies with pattern, eg EUR ➔ USD",
2092                 "//ldml/numbers/currencies/currency[@type=\"*\"]/displayName",
2093                 "*"
2094             },
2095             {
2096                 "CLDR-17756 Show as part of a locale name",
2097                 "//ldml/localeDisplayNames/keys/key[@type=\"*\"]"
2098                         + "//ldml/localeDisplayNames/measurementSystemNames/measurementSystemName[@type=\"*\"]"
2099                         + "//ldml/localeDisplayNames/subdivisions/subdivision[@type=\"*\"]"
2100                         + "//ldml/localeDisplayNames/types/type[@key=\"*\"][@type=\"*\"]",
2101                 "*"
2102             },
2103             {
2104                 "CLDR-17756 Show using two months, eg Januar - Juni",
2105                 "//ldml/dates/calendars/calendar[@type=\"*\"]/dateTimeFormats/intervalFormats/intervalFormatFallback",
2106                 "*"
2107             },
2108             {
2109                 "CLDR-15078 Enable compound unit formatting",
2110                 "//ldml/units/unitLength[@type=\"*\"]/unit[@type=\"*\"]/unitPattern[@count=\"*\"]"
2111                         + "//ldml/units/unitLength[@type=\"*\"]/unit[@type=\"*\"]/unitPattern[@count=\"*\"][@case=\"*\"]",
2112                 "*"
2113             },
2114             {
2115                 "CLDR-17756 Show font with field, eg: Helvetica (kursiv), Helvetica (Kursivstellung), Helvetica (vertikale Brüch)",
2116                 "//ldml/typographicNames/styleName[@type=\"*\"][@subtype=\"*\"]"
2117                         + "//ldml/typographicNames/axisName[@type=\"*\"]"
2118                         + "//ldml/typographicNames/featureName[@type=\"*\"]",
2119                 "*"
2120             },
2121             {
2122                 "CLDR-17756 Show in date with both variants: formatting and standalone. That way people can see what difference it makes, eg between MMMM and LLLL",
2123                 "//ldml/dates/calendars/calendar[@type=\"*\"]/days/dayContext[@type=\"*\"]/dayWidth[@type=\"*\"]/day[@type=\"*\"]"
2124                         + "//ldml/dates/calendars/calendar[@type=\"*\"]/months/monthContext[@type=\"*\"]/monthWidth[@type=\"*\"]/month[@type=\"*\"]"
2125                         + "//ldml/dates/calendars/calendar[@type=\"*\"]/months/monthContext[@type=\"*\"]/monthWidth[@type=\"*\"]/month[@type=\"*\"][@yeartype=\"*\"]"
2126                         + "//ldml/dates/calendars/calendar[@type=\"*\"]/quarters/quarterContext[@type=\"*\"]/quarterWidth[@type=\"*\"]/quarter[@type=\"*\"]",
2127                 "*"
2128             },
2129             {
2130                 "CLDR-17756 Show pattern with example",
2131                 "//ldml/dates/fields/field[@type=\"*\"]/relativePeriod",
2132                 "*"
2133             },
2134             {
2135                 "CLDR-17756 Show sample name with 2 different values",
2136                 "//ldml/personNames/foreignSpaceReplacement"
2137                         + "//ldml/personNames/initialPattern[@type=\"*\"]"
2138                         + "//ldml/personNames/nativeSpaceReplacement"
2139                         + "//ldml/personNames/parameterDefault[@parameter=\"*\"]"
2140                         + "//ldml/personNames/sampleName[@item=\"*\"]/nameField[@type=\"*\"]",
2141                 "*"
2142             },
2143             {
2144                 "CLDR-17756 Show two units with pattern, eg 'Meter ➔ Fuß'",
2145                 "//ldml/units/unitLength[@type=\"*\"]/unit[@type=\"*\"]/displayName",
2146                 "*"
2147             },
2148             {
2149                 "CLDR-17756 Show with {0}: {0}, eg Monat: Januar",
2150                 "//ldml/dates/fields/field[@type=\"*\"]/displayName",
2151                 "*"
2152             },
2153             {
2154                 "CLDR-5854 Show with appropriate amount, eg 'in 3 Jahren', and for all relatives > 1 day, add a time",
2155                 "//ldml/dates/fields/field[@type=\"*\"]/relativeTime[@type=\"*\"]/relativeTimePattern[@count=\"*\"]",
2156                 "*"
2157             },
2158             {
2159                 "CLDR-17756 Show with formattted date, including era",
2160                 "//ldml/dates/calendars/calendar[@type=\"*\"]/eras/eraAbbr/era[@type=\"*\"]\n"
2161                         + "//ldml/dates/calendars/calendar[@type=\"*\"]/eras/eraNames/era[@type=\"*\"]",
2162                 "*"
2163             },
2164             {
2165                 "CLDR-17756 Show with pattern, eg '30° Süd'",
2166                 "//ldml/units/unitLength[@type=\"*\"]/coordinateUnit/coordinateUnitPattern[@type=\"*\"]",
2167                 "*"
2168             },
2169             {
2170                 "CLDR-17756 Show with pattern, eg Richtung: 30° Süd",
2171                 "//ldml/units/unitLength[@type=\"*\"]/coordinateUnit/displayName",
2172                 "*"
2173             },
2174             {
2175                 "CLDR-17756 Show with sample characters (where possible, emoji)",
2176                 "//ldml/characterLabels/characterLabelPattern[@type=\"*\"][@count=\"*\"]\n"
2177                         + "//ldml/characterLabels/characterLabel[@type=\"*\"]\n"
2178                         + "//ldml/characterLabels/characterLabelPattern[@type=\"*\"]",
2179                 "*"
2180             },
2181             {
2182                 "CLDR-17756 Use gender minimal pair patterns to show in context — look at the minimal pair examples, reversing the background",
2183                 "//ldml/units/unitLength[@type=\"*\"]/unit[@type=\"*\"]/gender",
2184                 "*"
2185             }
2186         };
2187         Map<String, Map<String, String>> _HANDLE_MISSING = new TreeMap<>();
2188         for (String[] row : data) {
2189             if (row.length < 3) {
2190                 throw new IllegalArgumentException(
2191                         "Need 3+ values; see comments below HANDLE_MISSING");
2192             }
2193             String result = row[0];
2194             String paths = row[1];
2195             for (String path : SLASH2_SPLITTER.split(paths)) {
2196                 path = "//" + path;
2197                 // note, the resulting attributeToResult may be empty
2198                 Map<String, String> attributeToResult = _HANDLE_MISSING.get(path);
2199                 if (attributeToResult == null) {
_HANDLE_MISSING.put(path, attributeToResult = new TreeMap<>())2200                     _HANDLE_MISSING.put(path, attributeToResult = new TreeMap<>());
2201                 }
2202                 for (int i = 2; i < row.length; ++i) {
2203                     String attribute = row[i];
attributeToResult.put(attribute, result)2204                     attributeToResult.put(attribute, result);
2205                 }
2206             }
2207         }
2208         HANDLE_MISSING = CldrUtility.protectCollection(_HANDLE_MISSING);
2209     }
2210 
getResult(String starredPath, String attr)2211     private String getResult(String starredPath, String attr) {
2212         Map<String, String> attributeToResult = HANDLE_MISSING.get(starredPath);
2213         if (attributeToResult == null) {
2214             return null;
2215         }
2216         String result = attributeToResult.get(attr);
2217         if (result == null) {
2218             result = attributeToResult.get("*"); // wildcard
2219         }
2220         return result;
2221     }
2222 
sampleAttrAndValue(PathStarrer ps, final String separator, String value)2223     public String sampleAttrAndValue(PathStarrer ps, final String separator, String value) {
2224         return ps.getAttributesString(separator) + "➔«" + value + "»";
2225     }
2226 }
2227