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