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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 255 public void setExampleCount(Collection<FixedDecimal> exampleCount2) { 256 this.exampleCount = exampleCount2; 257 } 258 259 public Collection<FixedDecimal> getExampleCount() { 260 return exampleCount; 261 } 262 } 263 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 */ 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 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 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 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 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 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 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"] 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 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 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 570 private UnitLength getUnitLength() { 571 return UnitLength.valueOf(parts.getAttributeValue(-3, "type").toUpperCase(Locale.ENGLISH)); 572 } 573 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 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 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 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 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 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 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 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 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 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 751 UnitLength(ListTypeLength listTypeLength) { 752 typeString = "[@type=\"" + name().toLowerCase(Locale.ENGLISH) + "\"]"; 753 this.listTypeLength = listTypeLength; 754 } 755 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 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 774 private String getFormattedUnit(String unitType, UnitLength unitWidth, double unitAmount) { 775 return getFormattedUnit(unitType, unitWidth, new FixedDecimal(unitAmount)); 776 } 777 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 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 */ 813 private String getPattern(String pathFormat, String pathPatternType, String valuePatternType, String value) { 814 return valuePatternType.equals(pathPatternType) ? setBackground(value) : getValueFromFormat(pathFormat, pathPatternType); 815 } 816 817 private String getValueFromFormat(String format, Object... arguments) { 818 return cldrFile.getWinningValue(format(format, arguments)); 819 } 820 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 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 */ 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 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 894 public String format(Date earlier, Date later) { 895 return firstFormat.format(earlier) + secondFormat.format(later); 896 } 897 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 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 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 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 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 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 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 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 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 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 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 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 */ 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 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 */ 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 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 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 */ 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 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 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 */ 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 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 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 */ 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 1689 public String format(String format, Object... objects) { 1690 if (format == null) return null; 1691 return MessageFormat.format(format, objects); 1692 } 1693 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 1830 private String getGMTFormat(String gmtHourString, String gmtFormat, int hours) { 1831 return getGMTFormat(gmtHourString, gmtFormat, hours, 0); 1832 } 1833 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 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 */ 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 1949 public synchronized String getHelpHtml(String xpath, String value) { 1950 return getHelpHtml(xpath, value, false); 1951 } 1952 1953 public static String simplify(String exampleHtml) { 1954 return simplify(exampleHtml, false); 1955 } 1956 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