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("<")) { 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