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