1 package org.unicode.cldr.util; 2 3 import java.util.Arrays; 4 import java.util.Collections; 5 import java.util.EnumMap; 6 import java.util.HashMap; 7 import java.util.HashSet; 8 import java.util.Iterator; 9 import java.util.LinkedHashMap; 10 import java.util.LinkedHashSet; 11 import java.util.List; 12 import java.util.Locale; 13 import java.util.Map; 14 import java.util.Map.Entry; 15 import java.util.Set; 16 import java.util.TreeMap; 17 import java.util.TreeSet; 18 import java.util.regex.Matcher; 19 import java.util.regex.Pattern; 20 21 import org.unicode.cldr.draft.ScriptMetadata; 22 import org.unicode.cldr.draft.ScriptMetadata.Info; 23 import org.unicode.cldr.tool.LikelySubtags; 24 import org.unicode.cldr.util.RegexLookup.Finder; 25 import org.unicode.cldr.util.With.SimpleIterator; 26 27 import com.google.common.base.Splitter; 28 import com.ibm.icu.impl.Relation; 29 import com.ibm.icu.impl.Row; 30 import com.ibm.icu.lang.UCharacter; 31 import com.ibm.icu.text.Collator; 32 import com.ibm.icu.text.Transform; 33 import com.ibm.icu.util.ICUException; 34 import com.ibm.icu.util.Output; 35 import com.ibm.icu.util.ULocale; 36 37 /** 38 * Provides a mechanism for dividing up LDML paths into understandable 39 * categories, eg for the Survey tool. 40 */ 41 public class PathHeader implements Comparable<PathHeader> { 42 /** 43 * Link to a section. Commenting out the page switch for now. 44 */ 45 public static final String SECTION_LINK = "<a " + /* "target='CLDR_ST-SECTION' "+*/"href='"; 46 static boolean UNIFORM_CONTINENTS = true; 47 static Factory factorySingleton = null; 48 49 static final boolean SKIP_ORIGINAL_PATH = true; 50 51 /** 52 * What status the survey tool should use. Can be overridden in 53 * Phase.getAction() 54 */ 55 public enum SurveyToolStatus { 56 /** 57 * Never show. 58 */ 59 DEPRECATED, 60 /** 61 * Hide. Can be overridden in Phase.getAction() 62 */ 63 HIDE, 64 /** 65 * Don't allow Change box (except TC), instead show ticket. But allow 66 * votes. Can be overridden in Phase.getAction() 67 */ 68 READ_ONLY, 69 /** 70 * Allow change box and votes. Can be overridden in Phase.getAction() 71 */ 72 READ_WRITE, 73 /** 74 * Changes are allowed as READ_WRITE, but field is always displayed as 75 * LTR, even in RTL locales (used for patterns). 76 */ 77 LTR_ALWAYS 78 } 79 80 private static EnumNames<SectionId> SectionIdNames = new EnumNames<>(); 81 82 /** 83 * The Section for a path. Don't change these without committee buy-in. The 84 * 'name' may be 'Core_Data' and the toString is 'Core Data' toString gives 85 * the human name 86 */ 87 public enum SectionId { 88 Core_Data("Core Data"), Locale_Display_Names("Locale Display Names"), DateTime("Date & Time"), Timezones, Numbers, Currencies, Units, Characters, Misc( 89 "Miscellaneous"), BCP47, Supplemental, Special; 90 SectionId(String... alternateNames)91 private SectionId(String... alternateNames) { 92 SectionIdNames.add(this, alternateNames); 93 } 94 forString(String name)95 public static SectionId forString(String name) { 96 return SectionIdNames.forString(name); 97 } 98 99 @Override toString()100 public String toString() { 101 return SectionIdNames.toString(this); 102 } 103 } 104 105 private static EnumNames<PageId> PageIdNames = new EnumNames<>(); 106 private static Relation<SectionId, PageId> SectionIdToPageIds = Relation.of(new TreeMap<SectionId, Set<PageId>>(), 107 TreeSet.class); 108 109 private static class SubstringOrder implements Comparable<SubstringOrder> { 110 final String mainOrder; 111 final int order; 112 SubstringOrder(String source)113 public SubstringOrder(String source) { 114 int pos = source.lastIndexOf('-') + 1; 115 int ordering = COUNTS.indexOf(source.substring(pos)); 116 // account for digits, and "some" future proofing. 117 order = ordering < 0 118 ? source.charAt(pos) 119 : 0x10000 + ordering; 120 mainOrder = source.substring(0, pos); 121 } 122 123 @Override 124 public String toString() { 125 return "{" + mainOrder + ", " + order + "}"; 126 } 127 128 @Override 129 public int compareTo(SubstringOrder other) { 130 int diff = alphabeticCompare(mainOrder, other.mainOrder); 131 if (diff != 0) { 132 return diff; 133 } 134 return order - other.order; 135 } 136 } 137 138 /** 139 * The Page for a path (within a Section). Don't change these without 140 * committee buy-in. the name is for example WAsia where toString gives 141 * Western Asia 142 */ 143 public enum PageId { 144 Alphabetic_Information(SectionId.Core_Data, "Alphabetic Information"), 145 Numbering_Systems(SectionId.Core_Data, "Numbering Systems"), 146 LinguisticElements(SectionId.Core_Data, "Linguistic Elements"), 147 148 Locale_Name_Patterns(SectionId.Locale_Display_Names, "Locale Name Patterns"), 149 Languages_A_D(SectionId.Locale_Display_Names, "Languages (A-D)"), 150 Languages_E_J(SectionId.Locale_Display_Names, "Languages (E-J)"), 151 Languages_K_N(SectionId.Locale_Display_Names, "Languages (K-N)"), 152 Languages_O_S(SectionId.Locale_Display_Names, "Languages (O-S)"), 153 Languages_T_Z(SectionId.Locale_Display_Names, "Languages (T-Z)"), 154 Scripts(SectionId.Locale_Display_Names), 155 Territories(SectionId.Locale_Display_Names, "Geographic Regions"), 156 T_NAmerica(SectionId.Locale_Display_Names, "Territories (North America)"), 157 T_SAmerica( SectionId.Locale_Display_Names, "Territories (South America)"), 158 T_Africa(SectionId.Locale_Display_Names, "Territories (Africa)"), 159 T_Europe( SectionId.Locale_Display_Names, "Territories (Europe)"), 160 T_Asia(SectionId.Locale_Display_Names, "Territories (Asia)"), 161 T_Oceania( SectionId.Locale_Display_Names, "Territories (Oceania)"), 162 Locale_Variants(SectionId.Locale_Display_Names, "Locale Variants"), 163 Keys( SectionId.Locale_Display_Names), 164 165 Fields(SectionId.DateTime), 166 Gregorian(SectionId.DateTime), 167 Generic( SectionId.DateTime), 168 Buddhist(SectionId.DateTime), 169 Chinese(SectionId.DateTime), 170 Coptic( SectionId.DateTime), 171 Dangi(SectionId.DateTime), 172 Ethiopic(SectionId.DateTime), 173 Ethiopic_Amete_Alem( SectionId.DateTime, "Ethiopic-Amete-Alem"), 174 Hebrew(SectionId.DateTime), 175 Indian( SectionId.DateTime), 176 Islamic(SectionId.DateTime), 177 Japanese(SectionId.DateTime), 178 Persian( SectionId.DateTime), 179 Minguo(SectionId.DateTime), 180 181 Timezone_Display_Patterns(SectionId.Timezones, "Timezone Display Patterns"), 182 NAmerica(SectionId.Timezones, "North America"), 183 SAmerica( SectionId.Timezones, "South America"), 184 Africa(SectionId.Timezones), 185 Europe( SectionId.Timezones), 186 Russia(SectionId.Timezones), 187 WAsia(SectionId.Timezones, "Western Asia"), 188 CAsia(SectionId.Timezones, "Central Asia"), 189 EAsia( SectionId.Timezones, "Eastern Asia"), 190 SAsia(SectionId.Timezones, "Southern Asia"), 191 SEAsia( SectionId.Timezones, "Southeast Asia"), 192 Australasia(SectionId.Timezones), 193 Antarctica( SectionId.Timezones), 194 Oceania(SectionId.Timezones), 195 UnknownT( SectionId.Timezones, "Unknown Region"), 196 Overrides(SectionId.Timezones), 197 198 Symbols( SectionId.Numbers), 199 Number_Formatting_Patterns( SectionId.Numbers, "Number Formatting Patterns"), 200 Compact_Decimal_Formatting( SectionId.Numbers, "Compact Decimal Formatting"), 201 Compact_Decimal_Formatting_Other( SectionId.Numbers, "Compact Decimal Formatting (Other Numbering Systems)"), 202 203 Measurement_Systems( SectionId.Units, "Measurement Systems"), 204 Duration( SectionId.Units), 205 Graphics( SectionId.Units), 206 Length( SectionId.Units), 207 Area( SectionId.Units), 208 Volume( SectionId.Units), 209 SpeedAcceleration( SectionId.Units, "Speed and Acceleration"), 210 MassWeight( SectionId.Units, "Mass and Weight"), 211 EnergyPower( SectionId.Units, "Energy and Power"), 212 ElectricalFrequency( SectionId.Units, "Electrical and Frequency"), 213 Weather( SectionId.Units), 214 Digital( SectionId.Units), 215 Coordinates( SectionId.Units), 216 OtherUnits( SectionId.Units, "Other Units"), 217 CompoundUnits( SectionId.Units, "Compound Units"), 218 219 220 Displaying_Lists( SectionId.Misc, "Displaying Lists"), 221 MinimalPairs(SectionId.Misc, "Minimal Pairs"), 222 Transforms( SectionId.Misc), 223 224 Identity( SectionId.Special), 225 Version( SectionId.Special), 226 Suppress( SectionId.Special), 227 Deprecated( SectionId.Special), 228 Unknown( SectionId.Special), 229 230 C_NAmerica( SectionId.Currencies, "North America (C)"), 231 //need to add (C) to differentiate from Timezone territories 232 C_SAmerica(SectionId.Currencies, "South America (C)"), 233 C_NWEurope(SectionId.Currencies, "Northern/Western Europe"), 234 C_SEEurope(SectionId.Currencies, "Southern/Eastern Europe"), 235 C_NAfrica(SectionId.Currencies, "Northern Africa"), 236 C_WAfrica(SectionId.Currencies, "Western Africa"), 237 C_MAfrica( SectionId.Currencies, "Middle Africa"), 238 C_EAfrica(SectionId.Currencies, "Eastern Africa"), 239 C_SAfrica(SectionId.Currencies, "Southern Africa"), 240 C_WAsia(SectionId.Currencies, "Western Asia (C)"), 241 C_CAsia(SectionId.Currencies, "Central Asia (C)"), 242 C_EAsia( SectionId.Currencies, "Eastern Asia (C)"), 243 C_SAsia(SectionId.Currencies, "Southern Asia (C)"), 244 C_SEAsia(SectionId.Currencies, "Southeast Asia (C)"), 245 C_Oceania(SectionId.Currencies, "Oceania (C)"), 246 C_Unknown(SectionId.Currencies, "Unknown Region (C)"), 247 248 // BCP47 249 u_Extension(SectionId.BCP47), 250 t_Extension(SectionId.BCP47), 251 252 // Supplemental 253 Alias(SectionId.Supplemental), 254 IdValidity(SectionId.Supplemental), 255 Locale(SectionId.Supplemental), 256 RegionMapping(SectionId.Supplemental), 257 WZoneMapping( SectionId.Supplemental), 258 Transform(SectionId.Supplemental), 259 Units(SectionId.Supplemental), 260 Likely(SectionId.Supplemental), 261 LanguageMatch( SectionId.Supplemental), 262 TerritoryInfo(SectionId.Supplemental), 263 LanguageInfo(SectionId.Supplemental), 264 LanguageGroup( SectionId.Supplemental), 265 Fallback(SectionId.Supplemental), 266 Gender(SectionId.Supplemental), 267 Grammar(SectionId.Supplemental), 268 Metazone(SectionId.Supplemental), 269 NumberSystem( SectionId.Supplemental), 270 Plural(SectionId.Supplemental), 271 PluralRange(SectionId.Supplemental), 272 Containment( SectionId.Supplemental), 273 Currency(SectionId.Supplemental), 274 Calendar(SectionId.Supplemental), 275 WeekData( SectionId.Supplemental), 276 Measurement(SectionId.Supplemental), 277 Language(SectionId.Supplemental), 278 RBNF( SectionId.Supplemental), 279 Segmentation(SectionId.Supplemental), 280 DayPeriod(SectionId.Supplemental), 281 282 Category(SectionId.Characters), 283 284 // [Smileys, People, Animals & Nature, Food & Drink, Travel & Places, Activities, Objects, Symbols, Flags] 285 Smileys(SectionId.Characters, "Smileys & Emotion"), 286 People(SectionId.Characters, "People & Body"), 287 Animals_Nature(SectionId.Characters, "Animals & Nature"), 288 Food_Drink(SectionId.Characters, "Food & Drink"), 289 Travel_Places(SectionId.Characters, "Travel & Places"), 290 Activities(SectionId.Characters), 291 Objects( SectionId.Characters), 292 Symbols2(SectionId.Characters), 293 Flags(SectionId.Characters), 294 Component(SectionId.Characters), 295 Typography(SectionId.Characters), 296 ; 297 298 private final SectionId sectionId; 299 300 private PageId(SectionId sectionId, String... alternateNames) { 301 this.sectionId = sectionId; 302 SectionIdToPageIds.put(sectionId, this); 303 PageIdNames.add(this, alternateNames); 304 } 305 306 /** 307 * Construct a pageId given a string 308 * 309 * @param name 310 * @return 311 */ 312 public static PageId forString(String name) { 313 try { 314 return PageIdNames.forString(name); 315 } catch (Exception e) { 316 throw new ICUException("No PageId for " + name, e); 317 } 318 } 319 320 /** 321 * Returns the page id 322 * 323 * @return a page ID, such as 'Languages' 324 */ 325 @Override 326 public String toString() { 327 return PageIdNames.toString(this); 328 } 329 330 /** 331 * Get the containing section id, such as 'Code Lists' 332 * 333 * @return the containing section ID 334 */ 335 public SectionId getSectionId() { 336 return sectionId; 337 } 338 } 339 340 private final SectionId sectionId; 341 private final PageId pageId; 342 private final String header; 343 private final String code; 344 private final String originalPath; 345 private final SurveyToolStatus status; 346 347 // Used for ordering 348 private final int headerOrder; 349 private final long codeOrder; 350 private final SubstringOrder codeSuborder; 351 352 static final Pattern SEMI = PatternCache.get("\\s*;\\s*"); 353 static final Matcher ALT_MATCHER = PatternCache.get( 354 "\\[@alt=\"([^\"]*+)\"]") 355 .matcher(""); 356 357 static final Collator alphabetic = CLDRConfig.getInstance().getCollatorRoot(); 358 359 // static final RuleBasedCollator alphabetic = (RuleBasedCollator) Collator 360 // .getInstance(ULocale.ENGLISH); 361 // static { 362 // alphabetic.setNumericCollation(true); 363 // alphabetic.freeze(); 364 // } 365 366 static final SupplementalDataInfo supplementalDataInfo = SupplementalDataInfo.getInstance(); 367 static final Map<String, String> metazoneToContinent = supplementalDataInfo 368 .getMetazoneToContinentMap(); 369 static final StandardCodes standardCode = StandardCodes.make(); 370 static final Map<String, String> metazoneToPageTerritory = new HashMap<>(); 371 static { 372 Map<String, Map<String, String>> metazoneToRegionToZone = supplementalDataInfo.getMetazoneToRegionToZone(); 373 for (Entry<String, Map<String, String>> metazoneEntry : metazoneToRegionToZone.entrySet()) { 374 String metazone = metazoneEntry.getKey(); 375 String worldZone = metazoneEntry.getValue().get("001"); 376 String territory = Containment.getRegionFromZone(worldZone); 377 if (territory == null) { 378 territory = "ZZ"; 379 } 380 // Russia, Antarctica => territory 381 // in Australasia, Asia, S. America => subcontinent 382 // in N. America => N. America (grouping of 3 subcontinents) 383 // in everything else => continent 384 if (territory.equals("RU") || territory.equals("AQ")) { 385 metazoneToPageTerritory.put(metazone, territory); 386 } else { 387 String continent = Containment.getContinent(territory); 388 String subcontinent = Containment.getSubcontinent(territory); 389 if (continent.equals("142")) { // Asia 390 metazoneToPageTerritory.put(metazone, subcontinent); 391 } else if (continent.equals("019")) { // Americas 392 metazoneToPageTerritory.put(metazone, subcontinent.equals("005") ? subcontinent : "003"); 393 } else if (subcontinent.equals("053")) { // Australasia 394 metazoneToPageTerritory.put(metazone, subcontinent); 395 } else { 396 metazoneToPageTerritory.put(metazone, continent); 397 } 398 } 399 } 400 } 401 402 /** 403 * @param section 404 * @param sectionOrder 405 * @param page 406 * @param pageOrder 407 * @param header 408 * @param headerOrder 409 * @param code 410 * @param codeOrder 411 * @param suborder 412 * @param status 413 */ 414 private PathHeader(SectionId sectionId, PageId pageId, String header, 415 int headerOrder, String code, long codeOrder, SubstringOrder suborder, SurveyToolStatus status, 416 String originalPath) { 417 this.sectionId = sectionId; 418 this.pageId = pageId; 419 this.header = header; 420 this.headerOrder = headerOrder; 421 this.code = code; 422 this.codeOrder = codeOrder; 423 this.codeSuborder = suborder; 424 this.originalPath = originalPath; 425 this.status = status; 426 } 427 428 /** 429 * Return a factory for use in creating the headers. This is cached after first use. 430 * The calls are thread-safe. Null gets the default (CLDRConfig) english file. 431 * 432 * @param englishFile 433 */ 434 public static Factory getFactory(CLDRFile englishFile) { 435 if (factorySingleton == null) { 436 if (englishFile == null) { 437 englishFile = CLDRConfig.getInstance().getEnglish(); 438 } 439 if (!englishFile.getLocaleID().equals(ULocale.ENGLISH.getBaseName())) { 440 throw new IllegalArgumentException("PathHeader's CLDRFile must be '" + 441 ULocale.ENGLISH.getBaseName() + "', but found '" + englishFile.getLocaleID() + "'"); 442 } 443 factorySingleton = new Factory(englishFile); 444 } 445 return factorySingleton; 446 } 447 448 /** 449 * Convenience method for common case. See {{@link #getFactory(CLDRFile)}} 450 */ 451 public static Factory getFactory() { 452 return getFactory(null); 453 } 454 455 /** 456 * @deprecated 457 */ 458 @Deprecated 459 public String getSection() { 460 return sectionId.toString(); 461 } 462 463 public SectionId getSectionId() { 464 return sectionId; 465 } 466 467 /** 468 * @deprecated 469 */ 470 @Deprecated 471 public String getPage() { 472 return pageId.toString(); 473 } 474 475 public PageId getPageId() { 476 return pageId; 477 } 478 479 public String getHeader() { 480 return header == null ? "" : header; 481 } 482 483 public String getCode() { 484 return code; 485 } 486 487 public String getHeaderCode() { 488 return getHeader() + ": " + getCode(); 489 } 490 491 public String getOriginalPath() { 492 return originalPath; 493 } 494 495 public SurveyToolStatus getSurveyToolStatus() { 496 return status; 497 } 498 499 @Override 500 public String toString() { 501 return sectionId 502 + "\t" + pageId 503 + "\t" + header // + "\t" + headerOrder 504 + "\t" + code // + "\t" + codeOrder 505 ; 506 } 507 508 @Override 509 public int compareTo(PathHeader other) { 510 // Within each section, order alphabetically if the integer orders are 511 // not different. 512 try { 513 int result; 514 if (0 != (result = sectionId.compareTo(other.sectionId))) { 515 return result; 516 } 517 if (0 != (result = pageId.compareTo(other.pageId))) { 518 return result; 519 } 520 if (0 != (result = headerOrder - other.headerOrder)) { 521 return result; 522 } 523 if (0 != (result = alphabeticCompare(header, other.header))) { 524 return result; 525 } 526 long longResult; 527 if (0 != (longResult = codeOrder - other.codeOrder)) { 528 return longResult < 0 ? -1 : longResult > 0 ? 1 : 0; 529 } 530 if (codeSuborder != null) { // do all three cases, for transitivity 531 if (other.codeSuborder != null) { 532 if (0 != (result = codeSuborder.compareTo(other.codeSuborder))) { 533 return result; 534 } 535 } else { 536 return 1; // if codeSuborder != null (and other.codeSuborder 537 // == null), it is greater 538 } 539 } else if (other.codeSuborder != null) { 540 return -1; // if codeSuborder == null (and other.codeSuborder != 541 // null), it is greater 542 } 543 if (0 != (result = alphabeticCompare(code, other.code))) { 544 return result; 545 } 546 if (!SKIP_ORIGINAL_PATH && 0 != (result = alphabeticCompare(originalPath, other.originalPath))) { 547 return result; 548 } 549 return 0; 550 } catch (RuntimeException e) { 551 throw new IllegalArgumentException("Internal problem comparing " + this + " and " + other, e); 552 } 553 } 554 555 public int compareHeader(PathHeader other) { 556 int result; 557 if (0 != (result = headerOrder - other.headerOrder)) { 558 return result; 559 } 560 if (0 != (result = alphabeticCompare(header, other.header))) { 561 return result; 562 } 563 return result; 564 } 565 566 public int compareCode(PathHeader other) { 567 int result; 568 long longResult; 569 if (0 != (longResult = codeOrder - other.codeOrder)) { 570 return longResult < 0 ? -1 : longResult > 0 ? 1 : 0; 571 } 572 if (codeSuborder != null) { // do all three cases, for transitivity 573 if (other.codeSuborder != null) { 574 if (0 != (result = codeSuborder.compareTo(other.codeSuborder))) { 575 return result; 576 } 577 } else { 578 return 1; // if codeSuborder != null (and other.codeSuborder 579 // == null), it is greater 580 } 581 } else if (other.codeSuborder != null) { 582 return -1; // if codeSuborder == null (and other.codeSuborder != 583 // null), it is greater 584 } 585 if (0 != (result = alphabeticCompare(code, other.code))) { 586 return result; 587 } 588 return result; 589 } 590 591 @Override 592 public boolean equals(Object obj) { 593 PathHeader other; 594 try { 595 other = (PathHeader) obj; 596 } catch (Exception e) { 597 return false; 598 } 599 return sectionId == other.sectionId && pageId == other.pageId 600 && header.equals(other.header) && code.equals(other.code); 601 } 602 603 @Override 604 public int hashCode() { 605 return sectionId.hashCode() ^ pageId.hashCode() ^ header.hashCode() ^ code.hashCode(); 606 } 607 608 public static class Factory implements Transform<String, PathHeader> { 609 static final RegexLookup<RawData> lookup = RegexLookup 610 .of(new PathHeaderTransform()) 611 .setPatternTransform( 612 RegexLookup.RegexFinderTransformPath) 613 .loadFromFile( 614 PathHeader.class, 615 "data/PathHeader.txt"); 616 // synchronized with lookup 617 static final Output<String[]> args = new Output<>(); 618 // synchronized with lookup 619 static final Counter<RawData> counter = new Counter<>(); 620 // synchronized with lookup 621 static final Map<RawData, String> samples = new HashMap<>(); 622 // synchronized with lookup 623 static long order; 624 static SubstringOrder suborder; 625 626 static final Map<String, PathHeader> cache = new HashMap<>(); 627 // synchronized with cache 628 static final Map<SectionId, Map<PageId, SectionPage>> sectionToPageToSectionPage = new EnumMap<>( 629 SectionId.class); 630 static final Relation<SectionPage, String> sectionPageToPaths = Relation 631 .of(new TreeMap<SectionPage, Set<String>>(), 632 HashSet.class); 633 private static CLDRFile englishFile; 634 private Set<String> matchersFound = new HashSet<>(); 635 636 /** 637 * Create a factory for creating PathHeaders. 638 * 639 * @param englishFile 640 * - only sets the file (statically!) if not already set. 641 */ 642 private Factory(CLDRFile englishFile) { 643 setEnglishCLDRFileIfNotSet(englishFile); // temporary 644 } 645 646 /** 647 * Returns true if we set it, false if set before. 648 * 649 * @param englishFile2 650 * @return 651 */ 652 private static boolean setEnglishCLDRFileIfNotSet(CLDRFile englishFile2) { 653 synchronized (Factory.class) { 654 if (englishFile != null) { 655 return false; 656 } 657 englishFile = englishFile2; 658 return true; 659 } 660 } 661 662 /** 663 * Use only when trying to find unmatched patterns 664 */ 665 public void clearCache() { 666 synchronized (cache) { 667 cache.clear(); 668 } 669 } 670 671 /** 672 * Return the PathHeader for a given path. Thread-safe. 673 */ 674 public PathHeader fromPath(String path) { 675 return fromPath(path, null); 676 } 677 678 /** 679 * Return the PathHeader for a given path. Thread-safe. 680 */ 681 @Override 682 public PathHeader transform(String path) { 683 return fromPath(path, null); 684 } 685 686 /** 687 * Return the PathHeader for a given path. Thread-safe. 688 * @param failures a list of failures to add to. 689 */ 690 public PathHeader fromPath(final String path, List<String> failures) { 691 if (path == null) { 692 throw new NullPointerException("Path cannot be null"); 693 } 694 synchronized (cache) { 695 PathHeader old = cache.get(path); 696 if (old != null) { 697 return old; 698 } 699 } 700 synchronized (lookup) { 701 String cleanPath = path; 702 // special handling for alt 703 String alt = null; 704 int altPos = cleanPath.indexOf("[@alt="); 705 if (altPos >= 0 && !cleanPath.endsWith("/symbol[@alt=\"narrow\"]")) { 706 if (ALT_MATCHER.reset(cleanPath).find()) { 707 alt = ALT_MATCHER.group(1); 708 cleanPath = cleanPath.substring(0, ALT_MATCHER.start()) 709 + cleanPath.substring(ALT_MATCHER.end()); 710 int pos = alt.indexOf("proposed"); 711 if (pos >= 0 && !path.startsWith("//ldml/collations")) { 712 alt = pos == 0 ? null : alt.substring(0, pos - 1); 713 // drop "proposed", 714 // change "xxx-proposed" to xxx. 715 } 716 } else { 717 throw new IllegalArgumentException(); 718 } 719 } 720 Output<Finder> matcherFound = new Output<>(); 721 RawData data = lookup.get(cleanPath, null, args, matcherFound, failures); 722 if (data == null) { 723 return null; 724 } 725 matchersFound.add(matcherFound.value.toString()); 726 counter.add(data, 1); 727 if (!samples.containsKey(data)) { 728 samples.put(data, cleanPath); 729 } 730 try { 731 PathHeader result = new PathHeader( 732 SectionId.forString(fix(data.section, 0)), 733 PageId.forString(fix(data.page, 0)), 734 fix(data.header, data.headerOrder), 735 (int)order, // only valid after call to fix. TODO, make 736 // this cleaner 737 fix(data.code + (alt == null ? "" : ("-" + alt)), data.codeOrder), 738 order, // only valid after call to fix 739 suborder, 740 data.status, 741 path); 742 synchronized (cache) { 743 PathHeader old = cache.get(path); 744 if (old == null) { 745 cache.put(path, result); 746 } else { 747 result = old; 748 } 749 Map<PageId, SectionPage> pageToPathHeaders = sectionToPageToSectionPage 750 .get(result.sectionId); 751 if (pageToPathHeaders == null) { 752 sectionToPageToSectionPage.put(result.sectionId, pageToPathHeaders = new EnumMap<>(PageId.class)); 753 } 754 SectionPage sectionPage = pageToPathHeaders.get(result.pageId); 755 if (sectionPage == null) { 756 sectionPage = new SectionPage(result.sectionId, result.pageId); 757 pageToPathHeaders.put(result.pageId, sectionPage); 758 } 759 sectionPageToPaths.put(sectionPage, path); 760 } 761 return result; 762 } catch (Exception e) { 763 throw new IllegalArgumentException( 764 "Probably mismatch in Page/Section enum, or too few capturing groups in regex for " + path, 765 e); 766 } 767 } 768 } 769 770 private static class SectionPage implements Comparable<SectionPage> { 771 private final SectionId sectionId; 772 private final PageId pageId; 773 SectionPage(SectionId sectionId, PageId pageId)774 public SectionPage(SectionId sectionId, PageId pageId) { 775 this.sectionId = sectionId; 776 this.pageId = pageId; 777 } 778 779 @Override compareTo(SectionPage other)780 public int compareTo(SectionPage other) { 781 // Within each section, order alphabetically if the integer 782 // orders are 783 // not different. 784 int result; 785 if (0 != (result = sectionId.compareTo(other.sectionId))) { 786 return result; 787 } 788 if (0 != (result = pageId.compareTo(other.pageId))) { 789 return result; 790 } 791 return 0; 792 } 793 794 @Override equals(Object obj)795 public boolean equals(Object obj) { 796 PathHeader other; 797 try { 798 other = (PathHeader) obj; 799 } catch (Exception e) { 800 return false; 801 } 802 return sectionId == other.sectionId && pageId == other.pageId; 803 } 804 805 @Override hashCode()806 public int hashCode() { 807 return sectionId.hashCode() ^ pageId.hashCode(); 808 } 809 @Override toString()810 public String toString() { 811 return sectionId + " > " + pageId; 812 } 813 } 814 815 /** 816 * Returns a set of paths currently associated with the given section 817 * and page. 818 * <p> 819 * <b>Warning:</b> 820 * <ol> 821 * <li>The set may not be complete for a cldrFile unless all of paths in 822 * the file have had fromPath called. And this includes getExtraPaths(). 823 * </li> 824 * <li>The set may include paths that have no value in the current 825 * cldrFile.</li> 826 * <li>The set may be empty, if the section/page aren't valid.</li> 827 * </ol> 828 * Thread-safe. 829 * 830 * @target a collection where the paths are to be returned. 831 */ getCachedPaths(SectionId sectionId, PageId page)832 public static Set<String> getCachedPaths(SectionId sectionId, PageId page) { 833 Set<String> target = new HashSet<>(); 834 synchronized (cache) { 835 Map<PageId, SectionPage> pageToSectionPage = sectionToPageToSectionPage 836 .get(sectionId); 837 if (pageToSectionPage == null) { 838 return target; 839 } 840 SectionPage sectionPage = pageToSectionPage.get(page); 841 if (sectionPage == null) { 842 return target; 843 } 844 Set<String> set = sectionPageToPaths.getAll(sectionPage); 845 target.addAll(set); 846 } 847 return target; 848 } 849 850 /** 851 * Return the Sections and Pages that are in defined, for display in 852 * menus. Both are ordered. 853 */ getSectionIdsToPageIds()854 public static Relation<SectionId, PageId> getSectionIdsToPageIds() { 855 SectionIdToPageIds.freeze(); // just in case 856 return SectionIdToPageIds; 857 } 858 859 /** 860 * Return paths that have the designated section and page. 861 * 862 * @param sectionId 863 * @param pageId 864 * @param file 865 */ filterCldr(SectionId sectionId, PageId pageId, CLDRFile file)866 public Iterable<String> filterCldr(SectionId sectionId, PageId pageId, CLDRFile file) { 867 return new FilteredIterable(sectionId, pageId, file); 868 } 869 870 /** 871 * Return the names for Sections and Pages that are defined, for display 872 * in menus. Both are ordered. 873 * 874 * @deprecated Use getSectionIdsToPageIds 875 */ 876 @Deprecated getSectionsToPages()877 public static LinkedHashMap<String, Set<String>> getSectionsToPages() { 878 LinkedHashMap<String, Set<String>> sectionsToPages = new LinkedHashMap<>(); 879 for (PageId pageId : PageId.values()) { 880 String sectionId2 = pageId.getSectionId().toString(); 881 Set<String> pages = sectionsToPages.get(sectionId2); 882 if (pages == null) { 883 sectionsToPages.put(sectionId2, pages = new LinkedHashSet<>()); 884 } 885 pages.add(pageId.toString()); 886 } 887 return sectionsToPages; 888 } 889 890 /** 891 * @deprecated, use the filterCldr with the section/page ids. 892 */ filterCldr(String section, String page, CLDRFile file)893 public Iterable<String> filterCldr(String section, String page, CLDRFile file) { 894 return new FilteredIterable(section, page, file); 895 } 896 897 private class FilteredIterable implements Iterable<String>, SimpleIterator<String> { 898 private final SectionId sectionId; 899 private final PageId pageId; 900 private final Iterator<String> fileIterator; 901 FilteredIterable(SectionId sectionId, PageId pageId, CLDRFile file)902 FilteredIterable(SectionId sectionId, PageId pageId, CLDRFile file) { 903 this.sectionId = sectionId; 904 this.pageId = pageId; 905 this.fileIterator = file.fullIterable().iterator(); 906 } 907 FilteredIterable(String section, String page, CLDRFile file)908 public FilteredIterable(String section, String page, CLDRFile file) { 909 this(SectionId.forString(section), PageId.forString(page), file); 910 } 911 912 @Override iterator()913 public Iterator<String> iterator() { 914 return With.toIterator(this); 915 } 916 917 @Override next()918 public String next() { 919 while (fileIterator.hasNext()) { 920 String path = fileIterator.next(); 921 PathHeader pathHeader = fromPath(path); 922 if (sectionId == pathHeader.sectionId && pageId == pathHeader.pageId) { 923 return path; 924 } 925 } 926 return null; 927 } 928 } 929 930 private static class ChronologicalOrder { 931 private Map<String, Integer> map = new HashMap<>(); 932 private String item; 933 private int order; 934 private ChronologicalOrder toClear; 935 ChronologicalOrder(ChronologicalOrder toClear)936 ChronologicalOrder(ChronologicalOrder toClear) { 937 this.toClear = toClear; 938 } 939 getOrder()940 int getOrder() { 941 return order; 942 } 943 set(String itemToOrder)944 public String set(String itemToOrder) { 945 if (itemToOrder.startsWith("*")) { 946 item = itemToOrder.substring(1, itemToOrder.length()); 947 return item; // keep old order 948 } 949 item = itemToOrder; 950 Integer old = map.get(item); 951 if (old != null) { 952 order = old.intValue(); 953 } else { 954 order = map.size(); 955 map.put(item, order); 956 clearLower(); 957 } 958 return item; 959 } 960 clearLower()961 private void clearLower() { 962 if (toClear != null) { 963 toClear.map.clear(); 964 toClear.order = 0; 965 toClear.clearLower(); 966 } 967 } 968 } 969 970 static class RawData { 971 static ChronologicalOrder codeOrdering = new ChronologicalOrder(null); 972 static ChronologicalOrder headerOrdering = new ChronologicalOrder(codeOrdering); 973 RawData(String source)974 public RawData(String source) { 975 String[] split = SEMI.split(source); 976 section = split[0]; 977 // HACK 978 if (section.equals("Timezones") && split[1].equals("Indian")) { 979 page = "Indian2"; 980 } else { 981 page = split[1]; 982 } 983 984 header = headerOrdering.set(split[2]); 985 headerOrder = headerOrdering.getOrder(); 986 987 code = codeOrdering.set(split[3]); 988 codeOrder = codeOrdering.getOrder(); 989 990 status = split.length < 5 ? SurveyToolStatus.READ_WRITE : SurveyToolStatus.valueOf(split[4]); 991 } 992 993 public final String section; 994 public final String page; 995 public final String header; 996 public final int headerOrder; 997 public final String code; 998 public final int codeOrder; 999 public final SurveyToolStatus status; 1000 1001 @Override 1002 public String toString() { 1003 return section + "\t" 1004 + page + "\t" 1005 + header + "\t" + headerOrder + "\t" 1006 + code + "\t" + codeOrder + "\t" 1007 + status; 1008 } 1009 } 1010 1011 static class PathHeaderTransform implements Transform<String, RawData> { 1012 @Override 1013 public RawData transform(String source) { 1014 return new RawData(source); 1015 } 1016 } 1017 1018 /** 1019 * Internal data, for testing and debugging. 1020 * 1021 * @deprecated 1022 */ 1023 @Deprecated 1024 public class CounterData extends Row.R4<String, RawData, String, String> { 1025 public CounterData(String a, RawData b, String c) { 1026 super(a, b, c == null ? "no sample" : c, c == null ? "no sample" : fromPath(c) 1027 .toString()); 1028 } 1029 } 1030 1031 /** 1032 * Get the internal data, for testing and debugging. 1033 * 1034 * @deprecated 1035 */ 1036 @Deprecated 1037 public Counter<CounterData> getInternalCounter() { 1038 synchronized (lookup) { 1039 Counter<CounterData> result = new Counter<>(); 1040 for (Map.Entry<Finder, RawData> foo : lookup) { 1041 Finder finder = foo.getKey(); 1042 RawData data = foo.getValue(); 1043 long count = counter.get(data); 1044 result.add(new CounterData(finder.toString(), data, samples.get(data)), count); 1045 } 1046 return result; 1047 } 1048 } 1049 1050 static Map<String, Transform<String, String>> functionMap = new HashMap<>(); 1051 static String[] months = { "Jan", "Feb", "Mar", 1052 "Apr", "May", "Jun", 1053 "Jul", "Aug", "Sep", 1054 "Oct", "Nov", "Dec", 1055 "Und" }; 1056 static List<String> days = Arrays.asList("sun", "mon", 1057 "tue", "wed", "thu", 1058 "fri", "sat"); 1059 static List<String> unitOrder = DtdData.unitOrder.getOrder(); 1060 static final MapComparator<String> dayPeriods = new MapComparator<String>().add( 1061 "am", "pm", "midnight", "noon", 1062 "morning1", "morning2", "afternoon1", "afternoon2", "evening1", "evening2", "night1", "night2").freeze(); 1063 // static Map<String, String> likelySubtags = 1064 // supplementalDataInfo.getLikelySubtags(); 1065 static LikelySubtags likelySubtags = new LikelySubtags(); 1066 static HyphenSplitter hyphenSplitter = new HyphenSplitter(); 1067 static Transform<String, String> catFromTerritory; 1068 static Transform<String, String> catFromTimezone; 1069 static { 1070 // Put any new functions used in PathHeader.txt in here. 1071 // To change the order of items within a section or heading, set 1072 // order/suborder to be the relative position of the current item. 1073 functionMap.put("month", new Transform<String, String>() { 1074 @Override 1075 public String transform(String source) { 1076 int m = Integer.parseInt(source); 1077 order = m; 1078 return months[m - 1]; 1079 } 1080 }); 1081 functionMap.put("count", new Transform<String, String>() { 1082 @Override 1083 public String transform(String source) { 1084 suborder = new SubstringOrder(source); 1085 return source; 1086 } 1087 }); 1088 functionMap.put("count2", new Transform<String, String>() { 1089 @Override 1090 public String transform(String source) { 1091 int pos = source.indexOf('-'); 1092 source = pos + source.substring(pos); 1093 suborder = new SubstringOrder(source); // make 10000-... 1094 // into 5- 1095 return source; 1096 } 1097 }); 1098 functionMap.put("currencySymbol", new Transform<String, String>() { 1099 @Override 1100 public String transform(String source) { 1101 order = 901; 1102 if (source.endsWith("narrow")) { 1103 order = 902; 1104 } 1105 if (source.endsWith("variant")) { 1106 order = 903; 1107 } 1108 return source; 1109 } 1110 }); 1111 functionMap.put("unitCount", new Transform<String, String>() { 1112 @Override 1113 public String transform(String source) { 1114 String[] unitLengths = { "long", "short", "narrow" }; 1115 int pos = 9; 1116 for (int i = 0; i < unitLengths.length; i++) { 1117 if (source.startsWith(unitLengths[i])) { 1118 pos = i; 1119 continue; 1120 } 1121 } 1122 order = pos; 1123 suborder = new SubstringOrder(pos + "-" + source); // 1124 return source; 1125 } 1126 }); 1127 functionMap.put("day", new Transform<String, String>() { 1128 @Override 1129 public String transform(String source) { 1130 int m = days.indexOf(source); 1131 order = m; 1132 return source; 1133 } 1134 }); 1135 functionMap.put("dayPeriod", new Transform<String, String>() { 1136 @Override 1137 public String transform(String source) { 1138 try { 1139 order = dayPeriods.getNumericOrder(source); 1140 } catch (Exception e) { 1141 // if an old item is tried, like "evening", this will fail. 1142 // so that old data still works, hack this. 1143 order = Math.abs(source.hashCode() << 16); 1144 } 1145 return source; 1146 } 1147 }); 1148 functionMap.put("calendar", new Transform<String, String>() { 1149 Map<String, String> fixNames = Builder.with(new HashMap<String, String>()) 1150 .put("islamicc", "Islamic Civil") 1151 .put("roc", "Minguo") 1152 .put("Ethioaa", "Ethiopic Amete Alem") 1153 .put("Gregory", "Gregorian") 1154 .put("iso8601", "ISO 8601") 1155 .freeze(); 1156 1157 @Override 1158 public String transform(String source) { 1159 String result = fixNames.get(source); 1160 return result != null ? result : UCharacter.toTitleCase(source, null); 1161 } 1162 }); 1163 1164 functionMap.put("calField", new Transform<String, String>() { 1165 @Override 1166 public String transform(String source) { 1167 String[] fields = source.split(":", 3); 1168 order = 0; 1169 final List<String> widthValues = Arrays.asList( 1170 "wide", "abbreviated", "short", "narrow"); 1171 final List<String> calendarFieldValues = Arrays.asList( 1172 "Eras", 1173 "Quarters", 1174 "Months", 1175 "Days", 1176 "DayPeriods", 1177 "Formats"); 1178 final List<String> calendarFormatTypes = Arrays.asList( 1179 "Standard", 1180 "Flexible", 1181 "Intervals"); 1182 final List<String> calendarContextTypes = Arrays.asList( 1183 "none", 1184 "format", 1185 "stand-alone"); 1186 final List<String> calendarFormatSubtypes = Arrays.asList( 1187 "date", 1188 "time", 1189 "time12", 1190 "time24", 1191 "dateTime", 1192 "fallback"); 1193 1194 Map<String, String> fixNames = Builder.with(new HashMap<String, String>()) 1195 .put("DayPeriods", "Day Periods") 1196 .put("format", "Formatting") 1197 .put("stand-alone", "Standalone") 1198 .put("none", "") 1199 .put("date", "Date Formats") 1200 .put("time", "Time Formats") 1201 .put("time12", "12 Hour Time Formats") 1202 .put("time24", "24 Hour Time Formats") 1203 .put("dateTime", "Date & Time Combination Formats") 1204 .freeze(); 1205 1206 if (calendarFieldValues.contains(fields[0])) { 1207 order = calendarFieldValues.indexOf(fields[0]) * 100; 1208 } else { 1209 order = calendarFieldValues.size() * 100; 1210 } 1211 1212 if (fields[0].equals("Formats")) { 1213 if (calendarFormatTypes.contains(fields[1])) { 1214 order += calendarFormatTypes.indexOf(fields[1]) * 10; 1215 } else { 1216 order += calendarFormatTypes.size() * 10; 1217 } 1218 if (calendarFormatSubtypes.contains(fields[2])) { 1219 order += calendarFormatSubtypes.indexOf(fields[2]); 1220 } else { 1221 order += calendarFormatSubtypes.size(); 1222 } 1223 } else { 1224 if (widthValues.contains(fields[1])) { 1225 order += widthValues.indexOf(fields[1]) * 10; 1226 } else { 1227 order += widthValues.size() * 10; 1228 } 1229 if (calendarContextTypes.contains(fields[2])) { 1230 order += calendarContextTypes.indexOf(fields[2]); 1231 } else { 1232 order += calendarContextTypes.size(); 1233 } 1234 } 1235 1236 String[] fixedFields = new String[fields.length]; 1237 for (int i = 0; i < fields.length; i++) { 1238 String s = fixNames.get(fields[i]); 1239 fixedFields[i] = s != null ? s : fields[i]; 1240 } 1241 1242 return fixedFields[0] + 1243 " - " + fixedFields[1] + 1244 (fixedFields[2].length() > 0 ? " - " + fixedFields[2] : ""); 1245 } 1246 }); 1247 1248 functionMap.put("titlecase", new Transform<String, String>() { 1249 @Override 1250 public String transform(String source) { 1251 return UCharacter.toTitleCase(source, null); 1252 } 1253 }); 1254 functionMap.put("categoryFromScript", new Transform<String, String>() { 1255 @Override 1256 public String transform(String source) { 1257 String script = hyphenSplitter.split(source); 1258 Info info = ScriptMetadata.getInfo(script); 1259 if (info == null) { 1260 info = ScriptMetadata.getInfo("Zzzz"); 1261 } 1262 order = 100 - info.idUsage.ordinal(); 1263 return info.idUsage.name; 1264 } 1265 }); 1266 functionMap.put("categoryFromKey", new Transform<String, String>() { 1267 Map<String, String> fixNames = Builder.with(new HashMap<String, String>()) 1268 .put("lb", "Line Break") 1269 .put("hc", "Hour Cycle") 1270 .put("ms", "Measurement System") 1271 .put("cf", "Currency Format") 1272 .freeze(); 1273 1274 @Override 1275 public String transform(String source) { 1276 String fixedName = fixNames.get(source); 1277 return fixedName != null ? fixedName : source; 1278 } 1279 }); 1280 functionMap.put("languageSection", new Transform<String, String>() { 1281 char[] languageRangeStartPoints = { 'A', 'E', 'K', 'O', 'T' }; 1282 char[] languageRangeEndPoints = { 'D', 'J', 'N', 'S', 'Z' }; 1283 1284 @Override 1285 public String transform(String source0) { 1286 char firstLetter = getEnglishFirstLetter(source0).charAt(0); 1287 for (int i = 0; i < languageRangeStartPoints.length; i++) { 1288 if (firstLetter >= languageRangeStartPoints[i] && firstLetter <= languageRangeEndPoints[i]) { 1289 return "Languages (" + Character.toUpperCase(languageRangeStartPoints[i]) + "-" + Character.toUpperCase(languageRangeEndPoints[i]) 1290 + ")"; 1291 } 1292 } 1293 return "Languages"; 1294 } 1295 }); 1296 functionMap.put("firstLetter", new Transform<String, String>() { 1297 @Override 1298 public String transform(String source0) { 1299 return getEnglishFirstLetter(source0); 1300 } 1301 }); 1302 functionMap.put("languageSort", new Transform<String, String>() { 1303 @Override 1304 public String transform(String source0) { 1305 String languageOnlyPart; 1306 int underscorePos = source0.indexOf("_"); 1307 if (underscorePos > 0) { 1308 languageOnlyPart = source0.substring(0, underscorePos); 1309 } else { 1310 languageOnlyPart = source0; 1311 } 1312 1313 return englishFile.getName(CLDRFile.LANGUAGE_NAME, languageOnlyPart) + " \u25BA " + source0; 1314 } 1315 }); 1316 functionMap.put("scriptFromLanguage", new Transform<String, String>() { 1317 @Override 1318 public String transform(String source0) { 1319 String language = hyphenSplitter.split(source0); 1320 String script = likelySubtags.getLikelyScript(language); 1321 if (script == null) { 1322 script = likelySubtags.getLikelyScript(language); 1323 } 1324 String scriptName = englishFile.getName(CLDRFile.SCRIPT_NAME, script); 1325 return "Languages in " + (script.equals("Hans") || script.equals("Hant") ? "Han Script" 1326 : scriptName.endsWith(" Script") ? scriptName 1327 : scriptName + " Script"); 1328 } 1329 }); 1330 functionMap.put("categoryFromTerritory", 1331 catFromTerritory = new Transform<String, String>() { 1332 @Override 1333 public String transform(String source) { 1334 String territory = getSubdivisionsTerritory(source, null); 1335 String container = Containment.getContainer(territory); 1336 order = Containment.getOrder(territory); 1337 return englishFile.getName(CLDRFile.TERRITORY_NAME, container); 1338 } 1339 }); 1340 functionMap.put("territorySection", new Transform<String, String>() { 1341 final Set<String> specialRegions = new HashSet<>(Arrays.asList("EZ", "EU", "QO", "UN", "ZZ")); 1342 1343 @Override 1344 public String transform(String source0) { 1345 // support subdivisions 1346 String theTerritory = getSubdivisionsTerritory(source0, null); 1347 try { 1348 if (specialRegions.contains(theTerritory) 1349 || theTerritory.charAt(0) < 'A' && Integer.valueOf(theTerritory) > 0) { 1350 return "Geographic Regions"; 1351 } 1352 } catch (NumberFormatException ex) { 1353 } 1354 String theContinent = Containment.getContinent(theTerritory); 1355 String theSubContinent; 1356 switch (theContinent) { // was Integer.valueOf 1357 case "019": // Americas - For the territorySection, we just group North America & South America 1358 final String subcontinent = Containment.getSubcontinent(theTerritory); 1359 theSubContinent = subcontinent.equals("005") ? "005" : "003"; // was Integer.valueOf(subcontinent) == 5 1360 return "Territories (" + englishFile.getName(CLDRFile.TERRITORY_NAME, theSubContinent) + ")"; 1361 case "001": 1362 case "ZZ": 1363 return "Geographic Regions"; // not in containment 1364 default: 1365 return "Territories (" + englishFile.getName(CLDRFile.TERRITORY_NAME, theContinent) + ")"; 1366 } 1367 } 1368 }); 1369 functionMap.put("categoryFromTimezone", 1370 catFromTimezone = new Transform<String, String>() { 1371 @Override 1372 public String transform(String source0) { 1373 String territory = Containment.getRegionFromZone(source0); 1374 if (territory == null) { 1375 territory = "ZZ"; 1376 } 1377 return catFromTerritory.transform(territory); 1378 } 1379 }); 1380 functionMap.put("timeZonePage", new Transform<String, String>() { 1381 Set<String> singlePageTerritories = new HashSet<>(Arrays.asList("AQ", "RU", "ZZ")); 1382 1383 @Override 1384 public String transform(String source0) { 1385 String theTerritory = Containment.getRegionFromZone(source0); 1386 if (theTerritory == null || theTerritory == "001") { 1387 theTerritory = "ZZ"; 1388 } 1389 if (singlePageTerritories.contains(theTerritory)) { 1390 return englishFile.getName(CLDRFile.TERRITORY_NAME, theTerritory); 1391 } 1392 String theContinent = Containment.getContinent(theTerritory); 1393 final String subcontinent = Containment.getSubcontinent(theTerritory); 1394 String theSubContinent; 1395 switch (Integer.valueOf(theContinent)) { 1396 case 9: // Oceania - For the timeZonePage, we group Australasia on one page, and the rest of Oceania on the other. 1397 try { 1398 theSubContinent = subcontinent.equals("053") ? "053" : "009"; // was Integer.valueOf(subcontinent) == 53 1399 } catch (NumberFormatException ex) { 1400 theSubContinent = "009"; 1401 } 1402 return englishFile.getName(CLDRFile.TERRITORY_NAME, theSubContinent); 1403 case 19: // Americas - For the timeZonePage, we just group North America & South America 1404 theSubContinent = Integer.valueOf(subcontinent) == 5 ? "005" : "003"; 1405 return englishFile.getName(CLDRFile.TERRITORY_NAME, theSubContinent); 1406 case 142: // Asia 1407 return englishFile.getName(CLDRFile.TERRITORY_NAME, subcontinent); 1408 default: 1409 return englishFile.getName(CLDRFile.TERRITORY_NAME, theContinent); 1410 } 1411 } 1412 }); 1413 1414 functionMap.put("timezoneSorting", new Transform<String, String>() { 1415 @Override 1416 public String transform(String source) { 1417 final List<String> codeValues = Arrays.asList( 1418 "generic-long", 1419 "generic-short", 1420 "standard-long", 1421 "standard-short", 1422 "daylight-long", 1423 "daylight-short"); 1424 if (codeValues.contains(source)) { 1425 order = codeValues.indexOf(source); 1426 } else { 1427 order = codeValues.size(); 1428 } 1429 return source; 1430 } 1431 }); 1432 1433 functionMap.put("tzdpField", new Transform<String, String>() { 1434 @Override 1435 public String transform(String source) { 1436 Map<String, String> fieldNames = Builder.with(new HashMap<String, String>()) 1437 .put("regionFormat", "Region Format - Generic") 1438 .put("regionFormat-standard", "Region Format - Standard") 1439 .put("regionFormat-daylight", "Region Format - Daylight") 1440 .put("gmtFormat", "GMT Format") 1441 .put("hourFormat", "GMT Hours/Minutes Format") 1442 .put("gmtZeroFormat", "GMT Zero Format") 1443 .put("fallbackFormat", "Location Fallback Format") 1444 .freeze(); 1445 final List<String> fieldOrder = Arrays.asList( 1446 "regionFormat", 1447 "regionFormat-standard", 1448 "regionFormat-daylight", 1449 "gmtFormat", 1450 "hourFormat", 1451 "gmtZeroFormat", 1452 "fallbackFormat"); 1453 1454 if (fieldOrder.contains(source)) { 1455 order = fieldOrder.indexOf(source); 1456 } else { 1457 order = fieldOrder.size(); 1458 } 1459 1460 String result = fieldNames.get(source); 1461 return result == null ? source : result; 1462 } 1463 }); 1464 functionMap.put("unit", new Transform<String, String>() { 1465 @Override 1466 public String transform(String source) { 1467 int m = unitOrder.indexOf(source); 1468 order = m; 1469 return source.substring(source.indexOf('-') + 1); 1470 } 1471 }); 1472 1473 functionMap.put("numericSort", new Transform<String, String>() { 1474 // Probably only works well for small values, like -5 through +4. 1475 @Override 1476 public String transform(String source) { 1477 Integer pos = Integer.valueOf(source) + 5; 1478 suborder = new SubstringOrder(pos.toString()); 1479 return source; 1480 } 1481 }); 1482 1483 functionMap.put("metazone", new Transform<String, String>() { 1484 1485 @Override 1486 public String transform(String source) { 1487 if (PathHeader.UNIFORM_CONTINENTS) { 1488 String container = getMetazonePageTerritory(source); 1489 order = Containment.getOrder(container); 1490 return englishFile.getName(CLDRFile.TERRITORY_NAME, container); 1491 } else { 1492 String continent = metazoneToContinent.get(source); 1493 if (continent == null) { 1494 continent = "UnknownT"; 1495 } 1496 return continent; 1497 } 1498 } 1499 }); 1500 1501 Object[][] ctto = { 1502 { "BUK", "MM" }, 1503 { "CSD", "RS" }, 1504 { "CSK", "CZ" }, 1505 { "DDM", "DE" }, 1506 { "EUR", "ZZ" }, 1507 { "RHD", "ZW" }, 1508 { "SUR", "RU" }, 1509 { "TPE", "TL" }, 1510 { "XAG", "ZZ" }, 1511 { "XAU", "ZZ" }, 1512 { "XBA", "ZZ" }, 1513 { "XBB", "ZZ" }, 1514 { "XBC", "ZZ" }, 1515 { "XBD", "ZZ" }, 1516 { "XDR", "ZZ" }, 1517 { "XEU", "ZZ" }, 1518 { "XFO", "ZZ" }, 1519 { "XFU", "ZZ" }, 1520 { "XPD", "ZZ" }, 1521 { "XPT", "ZZ" }, 1522 { "XRE", "ZZ" }, 1523 { "XSU", "ZZ" }, 1524 { "XTS", "ZZ" }, 1525 { "XUA", "ZZ" }, 1526 { "XXX", "ZZ" }, 1527 { "YDD", "YE" }, 1528 { "YUD", "RS" }, 1529 { "YUM", "RS" }, 1530 { "YUN", "RS" }, 1531 { "YUR", "RS" }, 1532 { "ZRN", "CD" }, 1533 { "ZRZ", "CD" }, 1534 }; 1535 1536 Object[][] sctc = { 1537 { "Northern America", "North America (C)" }, 1538 { "Central America", "North America (C)" }, 1539 { "Caribbean", "North America (C)" }, 1540 { "South America", "South America (C)" }, 1541 { "Northern Africa", "Northern Africa" }, 1542 { "Western Africa", "Western Africa" }, 1543 { "Middle Africa", "Middle Africa" }, 1544 { "Eastern Africa", "Eastern Africa" }, 1545 { "Southern Africa", "Southern Africa" }, 1546 { "Europe", "Northern/Western Europe" }, 1547 { "Northern Europe", "Northern/Western Europe" }, 1548 { "Western Europe", "Northern/Western Europe" }, 1549 { "Eastern Europe", "Southern/Eastern Europe" }, 1550 { "Southern Europe", "Southern/Eastern Europe" }, 1551 { "Western Asia", "Western Asia (C)" }, 1552 { "Central Asia", "Central Asia (C)" }, 1553 { "Eastern Asia", "Eastern Asia (C)" }, 1554 { "Southern Asia", "Southern Asia (C)" }, 1555 { "Southeast Asia", "Southeast Asia (C)" }, 1556 { "Australasia", "Oceania (C)" }, 1557 { "Melanesia", "Oceania (C)" }, 1558 { "Micronesian Region", "Oceania (C)" }, // HACK 1559 { "Polynesia", "Oceania (C)" }, 1560 { "Unknown Region", "Unknown Region (C)" }, 1561 }; 1562 1563 final Map<String, String> currencyToTerritoryOverrides = CldrUtility.asMap(ctto); 1564 final Map<String, String> subContinentToContinent = CldrUtility.asMap(sctc); 1565 final Set<String> fundCurrencies = new HashSet<>(Arrays.asList("CHE", "CHW", "CLF", "COU", "ECV", "MXV", "USN", "USS", "UYI", "XEU", "ZAL")); 1566 final Set<String> offshoreCurrencies = new HashSet<>(Arrays.asList("CNH")); 1567 // TODO: Put this into supplementalDataInfo ? 1568 1569 functionMap.put("categoryFromCurrency", new Transform<String, String>() { 1570 @Override 1571 public String transform(String source0) { 1572 String tenderOrNot = ""; 1573 String territory = likelySubtags.getLikelyTerritoryFromCurrency(source0); 1574 if (territory == null) { 1575 String tag; 1576 if (fundCurrencies.contains(source0)) { 1577 tag = " (fund)"; 1578 } else if (offshoreCurrencies.contains(source0)) { 1579 tag = " (offshore)"; 1580 } else { 1581 tag = " (old)"; 1582 } 1583 tenderOrNot = ": " + source0 + tag; 1584 } 1585 if (currencyToTerritoryOverrides.keySet().contains(source0)) { 1586 territory = currencyToTerritoryOverrides.get(source0); 1587 } else if (territory == null) { 1588 territory = source0.substring(0, 2); 1589 } 1590 1591 if (territory.equals("ZZ")) { 1592 order = 999; 1593 return englishFile.getName(CLDRFile.TERRITORY_NAME, territory) + ": " + source0; 1594 } else { 1595 return catFromTerritory.transform(territory) + ": " 1596 + englishFile.getName(CLDRFile.TERRITORY_NAME, territory) 1597 + tenderOrNot; 1598 } 1599 } 1600 }); 1601 functionMap.put("continentFromCurrency", new Transform<String, String>() { 1602 @Override 1603 public String transform(String source0) { 1604 String subContinent; 1605 String territory = likelySubtags.getLikelyTerritoryFromCurrency(source0); 1606 if (currencyToTerritoryOverrides.keySet().contains(source0)) { 1607 territory = currencyToTerritoryOverrides.get(source0); 1608 } else if (territory == null) { 1609 territory = source0.substring(0, 2); 1610 } 1611 1612 if (territory.equals("ZZ")) { 1613 order = 999; 1614 subContinent = englishFile.getName(CLDRFile.TERRITORY_NAME, territory); 1615 } else { 1616 subContinent = catFromTerritory.transform(territory); 1617 } 1618 1619 String result = subContinentToContinent.get(subContinent); //the continent is the last word in the territory representation 1620 return result; 1621 } 1622 }); 1623 functionMap.put("numberingSystem", new Transform<String, String>() { 1624 @Override 1625 public String transform(String source0) { 1626 if ("latn".equals(source0)) { 1627 return ""; 1628 } 1629 String displayName = englishFile.getStringValue("//ldml/localeDisplayNames/types/type[@key=\"numbers\"][@type=\"" 1630 + source0 + "\"]"); 1631 return "using " + (displayName == null ? source0 : displayName + " (" + source0 + ")"); 1632 } 1633 }); 1634 1635 functionMap.put("datefield", new Transform<String, String>() { 1636 private final String[] datefield = { 1637 "era", "era-short", "era-narrow", 1638 "century", "century-short", "century-narrow", 1639 "year", "year-short", "year-narrow", 1640 "quarter", "quarter-short", "quarter-narrow", 1641 "month", "month-short", "month-narrow", 1642 "week", "week-short", "week-narrow", 1643 "weekOfMonth", "weekOfMonth-short", "weekOfMonth-narrow", 1644 "day", "day-short", "day-narrow", 1645 "dayOfYear", "dayOfYear-short", "dayOfYear-narrow", 1646 "weekday", "weekday-short", "weekday-narrow", 1647 "weekdayOfMonth", "weekdayOfMonth-short", "weekdayOfMonth-narrow", 1648 "dayperiod", "dayperiod-short", "dayperiod-narrow", 1649 "zone", "zone-short", "zone-narrow", 1650 "hour", "hour-short", "hour-narrow", 1651 "minute", "minute-short", "minute-narrow", 1652 "second", "second-short", "second-narrow", 1653 "millisecond", "millisecond-short", "millisecond-narrow", 1654 "microsecond", "microsecond-short", "microsecond-narrow", 1655 "nanosecond", "nanosecond-short", "nanosecond-narrow", 1656 1657 }; 1658 1659 @Override 1660 public String transform(String source) { 1661 order = getIndex(source, datefield); 1662 return source; 1663 } 1664 }); 1665 // //ldml/dates/fields/field[@type="%A"]/relative[@type="%A"] 1666 functionMap.put("relativeDate", new Transform<String, String>() { 1667 private final String[] relativeDateField = { 1668 "year", "year-short", "year-narrow", 1669 "quarter", "quarter-short", "quarter-narrow", 1670 "month", "month-short", "month-narrow", 1671 "week", "week-short", "week-narrow", 1672 "day", "day-short", "day-narrow", 1673 "hour", "hour-short", "hour-narrow", 1674 "minute", "minute-short", "minute-narrow", 1675 "second", "second-short", "second-narrow", 1676 "sun", "sun-short", "sun-narrow", 1677 "mon", "mon-short", "mon-narrow", 1678 "tue", "tue-short", "tue-narrow", 1679 "wed", "wed-short", "wed-narrow", 1680 "thu", "thu-short", "thu-narrow", 1681 "fri", "fri-short", "fri-narrow", 1682 "sat", "sat-short", "sat-narrow", 1683 }; 1684 private final String[] longNames = { 1685 "Year", "Year Short", "Year Narrow", 1686 "Quarter", "Quarter Short", "Quarter Narrow", 1687 "Month", "Month Short", "Month Narrow", 1688 "Week", "Week Short", "Week Narrow", 1689 "Day", "Day Short", "Day Narrow", 1690 "Hour", "Hour Short", "Hour Narrow", 1691 "Minute", "Minute Short", "Minute Narrow", 1692 "Second", "Second Short", "Second Narrow", 1693 "Sunday", "Sunday Short", "Sunday Narrow", 1694 "Monday", "Monday Short", "Monday Narrow", 1695 "Tuesday", "Tuesday Short", "Tuesday Narrow", 1696 "Wednesday", "Wednesday Short", "Wednesday Narrow", 1697 "Thursday", "Thursday Short", "Thursday Narrow", 1698 "Friday", "Friday Short", "Friday Narrow", 1699 "Saturday", "Saturday Short", "Saturday Narrow", 1700 }; 1701 1702 @Override 1703 public String transform(String source) { 1704 order = getIndex(source, relativeDateField) + 100; 1705 return "Relative " + longNames[getIndex(source, relativeDateField)]; 1706 } 1707 }); 1708 // Sorts numberSystem items (except for decimal formats). 1709 functionMap.put("number", new Transform<String, String>() { 1710 private final String[] symbols = { "decimal", "group", 1711 "plusSign", "minusSign", "approximatelySign", 1712 "percentSign", "perMille", 1713 "exponential", "superscriptingExponent", 1714 "infinity", "nan", "list", "currencies" 1715 }; 1716 1717 @Override 1718 public String transform(String source) { 1719 String[] parts = source.split("-"); 1720 order = getIndex(parts[0], symbols); 1721 // e.g. "currencies-one" 1722 if (parts.length > 1) { 1723 suborder = new SubstringOrder(parts[1]); 1724 } 1725 return source; 1726 } 1727 }); 1728 functionMap.put("numberFormat", new Transform<String, String>() { 1729 @Override 1730 public String transform(String source) { 1731 final List<String> fieldOrder = Arrays.asList( 1732 "standard-decimal", 1733 "standard-currency", 1734 "standard-currency-accounting", 1735 "standard-percent", 1736 "standard-scientific"); 1737 1738 if (fieldOrder.contains(source)) { 1739 order = fieldOrder.indexOf(source); 1740 } else { 1741 order = fieldOrder.size(); 1742 } 1743 1744 return source; 1745 } 1746 }); 1747 1748 functionMap.put("localePattern", new Transform<String, String>() { 1749 @Override 1750 public String transform(String source) { 1751 // Put localeKeyTypePattern behind localePattern and 1752 // localeSeparator. 1753 if (source.equals("localeKeyTypePattern")) { 1754 order = 10; 1755 } 1756 return source; 1757 } 1758 }); 1759 functionMap.put("listOrder", new Transform<String, String>() { 1760 private String[] listParts = { "2", "start", "middle", "end" }; 1761 1762 @Override 1763 public String transform(String source) { 1764 order = getIndex(source, listParts); 1765 return source; 1766 } 1767 }); 1768 functionMap.put("alphaOrder", new Transform<String, String>() { 1769 @Override 1770 public String transform(String source) { 1771 order = 0; 1772 return source; 1773 } 1774 }); 1775 functionMap.put("transform", new Transform<String, String>() { 1776 Splitter commas = Splitter.on(',').trimResults(); 1777 1778 @Override 1779 public String transform(String source) { 1780 List<String> parts = commas.splitToList(source); 1781 return parts.get(1) 1782 + (parts.get(0).equals("both") ? "↔︎" : "→") 1783 + parts.get(2) 1784 + (parts.size() > 3 ? "/" + parts.get(3) : ""); 1785 } 1786 }); 1787 functionMap.put("major", new Transform<String, String>() { 1788 @Override 1789 public String transform(String source) { 1790 String major = Emoji.getMajorCategory(source); 1791 // check that result is reasonable by running through PageId. 1792 switch(major) { 1793 default: 1794 PageId pageId2 = PageId.forString(major); 1795 if (pageId2.getSectionId() != SectionId.Characters) { 1796 if (pageId2 == PageId.Symbols) { 1797 pageId2 = PageId.Symbols2; 1798 } 1799 } 1800 return pageId2.toString(); 1801 case "Smileys & People": 1802 String minorCat = Emoji.getMinorCategory(source); 1803 if (minorCat.equals("skin-tone") || minorCat.equals("hair-style")) { 1804 return PageId.Component.toString(); 1805 } else if (!minorCat.contains("face")) { 1806 return PageId.People.toString(); 1807 } else { 1808 return PageId.Smileys.toString(); 1809 } 1810 } 1811 } 1812 }); 1813 functionMap.put("minor", new Transform<String, String>() { 1814 @Override 1815 public String transform(String source) { 1816 String minorCat = Emoji.getMinorCategory(source); 1817 order = Emoji.getEmojiMinorOrder(minorCat); 1818 return minorCat; 1819 } 1820 }); 1821 /** 1822 * Use the ordering of the emoji in getEmojiToOrder rather than alphabetic, 1823 * since the collator data won't be ready until the candidates are final. 1824 */ 1825 functionMap.put("emoji", new Transform<String, String>() { 1826 @Override 1827 public String transform(String source) { 1828 int dashPos = source.indexOf(' '); 1829 String emoji = source.substring(0, dashPos); 1830 order = (Emoji.getEmojiToOrder(emoji) << 1) + (source.endsWith("name") ? 0 : 1); 1831 return source; 1832 } 1833 }); 1834 1835 } 1836 1837 private static int getIndex(String item, String[] array) { 1838 for (int i = 0; i < array.length; i++) { 1839 if (item.equals(array[i])) { 1840 return i; 1841 } 1842 } 1843 return -1; 1844 } 1845 1846 private static String getEnglishFirstLetter(String s) { 1847 String languageOnlyPart; 1848 int underscorePos = s.indexOf("_"); 1849 if (underscorePos > 0) { 1850 languageOnlyPart = s.substring(0, underscorePos); 1851 } else { 1852 languageOnlyPart = s; 1853 } 1854 final String name = englishFile.getName(CLDRFile.LANGUAGE_NAME, languageOnlyPart); 1855 return name == null ? "?" : name.substring(0, 1).toUpperCase(); 1856 } 1857 1858 static class HyphenSplitter { 1859 String main; 1860 String extras; 1861 1862 String split(String source) { 1863 int hyphenPos = source.indexOf('-'); 1864 if (hyphenPos < 0) { 1865 main = source; 1866 extras = ""; 1867 } else { 1868 main = source.substring(0, hyphenPos); 1869 extras = source.substring(hyphenPos); 1870 } 1871 return main; 1872 } 1873 } 1874 1875 /** 1876 * This converts "functions", like &month, and sets the order. 1877 * 1878 * @param input 1879 * @param order 1880 * @return 1881 */ 1882 private static String fix(String input, int orderIn) { 1883 if (input.contains("")) { 1884 int debug = 0; 1885 } 1886 String oldInput = input; 1887 input = RegexLookup.replace(input, args.value); 1888 order = orderIn; 1889 suborder = null; 1890 int pos = 0; 1891 while (true) { 1892 int functionStart = input.indexOf('&', pos); 1893 if (functionStart < 0) { 1894 return input; 1895 } 1896 int functionEnd = input.indexOf('(', functionStart); 1897 int argEnd = input.indexOf(')', functionEnd+2); // we must insert at least one character 1898 Transform<String, String> func = functionMap.get(input.substring(functionStart + 1, 1899 functionEnd)); 1900 final String arg = input.substring(functionEnd + 1, argEnd); 1901 String temp = func.transform(arg); 1902 if (temp == null) { 1903 func.transform(arg); 1904 throw new IllegalArgumentException("Function returns invalid results for «" + arg + "»."); 1905 } 1906 input = input.substring(0, functionStart) + temp + input.substring(argEnd + 1); 1907 pos = functionStart + temp.length(); 1908 } 1909 } 1910 1911 /** 1912 * Collect all the paths for a CLDRFile, and make sure that they have 1913 * cached PathHeaders 1914 * 1915 * @param file 1916 * @return immutable set of paths in the file 1917 */ 1918 public Set<String> pathsForFile(CLDRFile file) { 1919 // make sure we cache all the path headers 1920 HashSet<String> filePaths = new HashSet<>(); 1921 file.fullIterable().forEach(filePaths::add); 1922 for (String path : filePaths) { 1923 try { 1924 fromPath(path); // call to make sure cached 1925 } catch (Throwable t) { 1926 // ... some other exception 1927 } 1928 } 1929 return Collections.unmodifiableSet(filePaths); 1930 } 1931 1932 /** 1933 * Returns those regexes that were never matched. 1934 * @return 1935 */ 1936 public Set<String> getUnmatchedRegexes() { 1937 Map<String, RawData> outputUnmatched = new LinkedHashMap<>(); 1938 lookup.getUnmatchedPatterns(matchersFound, outputUnmatched); 1939 return outputUnmatched.keySet(); 1940 } 1941 1942 public String getRegexInfo() { 1943 return lookup.toString(); 1944 } 1945 } 1946 1947 /** 1948 * Return the territory used for the title of the Metazone page in the 1949 * Survey Tool. 1950 * 1951 * @param source 1952 * @return 1953 */ 1954 public static String getMetazonePageTerritory(String source) { 1955 String result = metazoneToPageTerritory.get(source); 1956 return result == null ? "ZZ" : result; 1957 } 1958 1959 private static final List<String> COUNTS = Arrays.asList("displayName", "zero", "one", "two", "few", "many", "other", "per"); 1960 1961 private static int alphabeticCompare(String aa, String bb) { 1962 // A frozen Collator is thread-safe. 1963 return alphabetic.compare(aa, bb); 1964 } 1965 1966 public enum BaseUrl { 1967 //http://st.unicode.org/smoketest/survey?_=af&strid=55053dffac611328 1968 //http://st.unicode.org/cldr-apps/survey?_=en&strid=3cd31261bf6738e1 1969 SMOKE("http://st.unicode.org/smoketest/survey"), PRODUCTION("http://st.unicode.org/cldr-apps/survey"); 1970 final String base; 1971 1972 private BaseUrl(String url) { 1973 base = url; 1974 } 1975 } 1976 1977 /** 1978 * @deprecated, use CLDRConfig.urls().forPathHeader() instead. 1979 * @param baseUrl 1980 * @param locale 1981 * @return 1982 */ 1983 public String getUrl(BaseUrl baseUrl, String locale) { 1984 return getUrl(baseUrl.base, locale); 1985 } 1986 1987 /** 1988 * @deprecated, use CLDRConfig.urls().forPathHeader() instead. 1989 * @param baseUrl 1990 * @param locale 1991 * @return 1992 */ 1993 public String getUrl(String baseUrl, String locale) { 1994 return getUrl(baseUrl, locale, getOriginalPath()); 1995 } 1996 1997 /** 1998 * Map http://st.unicode.org/smoketest/survey to http://st.unicode.org/smoketest etc 1999 * @param str 2000 * @return 2001 */ 2002 public static String trimLast(String str) { 2003 int n = str.lastIndexOf('/'); 2004 if (n == -1) return ""; 2005 return str.substring(0, n + 1); 2006 } 2007 2008 public static String getUrlForLocalePath(String locale, String path) { 2009 return getUrl(SURVEY_URL, locale, path); 2010 } 2011 2012 public String getUrlForLocalePath(String locale) { 2013 return getUrl(SURVEY_URL, locale, originalPath); 2014 } 2015 2016 public static String getUrl(String baseUrl, String locale, String path) { 2017 return trimLast(baseUrl) + "v#/" + locale + "//" + StringId.getHexId(path); 2018 } 2019 2020 // eg http://st.unicode.org/cldr-apps/survey?_=fr&x=Locale%20Name%20Patterns 2021 /** 2022 * @deprecated use CLDRConfig.urls() 2023 * @param baseUrl 2024 * @param locale 2025 * @param subsection 2026 * @return 2027 */ 2028 @Deprecated 2029 public static String getPageUrl(String baseUrl, String locale, PageId subsection) { 2030 return trimLast(baseUrl) + "v#/" + locale + "/" + subsection + "/"; 2031 } 2032 2033 private static String SURVEY_URL = CLDRConfig.getInstance().getProperty("CLDR_SURVEY_URL", "http://st.unicode.org/cldr-apps/survey"); 2034 2035 public static String getLinkedView(String baseUrl, CLDRFile file, String path) { 2036 return getLinkedView(baseUrl, file.getLocaleID(), path); 2037 } 2038 /** 2039 * @deprecated use CLDRConfig.urls() 2040 * @param baseUrl 2041 * @param file 2042 * @param path 2043 * @return 2044 */ 2045 @Deprecated 2046 public static String getLinkedView(String baseUrl, String localeId, String path) { 2047 // String value = file.getStringValue(path); 2048 // if (value == null) { 2049 // return null; 2050 // } 2051 return SECTION_LINK + PathHeader.getUrl(baseUrl, localeId, path) + "'><em>view</em></a>"; 2052 } 2053 2054 /** 2055 * If a subdivision, return the (uppercased) territory and if suffix != null, the suffix. Otherwise return the input as is. 2056 * @param input 2057 * @param suffix 2058 * @return 2059 */ 2060 private static String getSubdivisionsTerritory(String input, Output<String> suffix) { 2061 String theTerritory; 2062 if (StandardCodes.LstrType.subdivision.isWellFormed(input)) { 2063 int territoryEnd = input.charAt(0) < 'A' ? 3 : 2; 2064 theTerritory = input.substring(0, territoryEnd).toUpperCase(Locale.ROOT); 2065 if (suffix != null) { 2066 suffix.value = input.substring(territoryEnd); 2067 } 2068 } else { 2069 theTerritory = input; 2070 if (suffix != null) { 2071 suffix.value = ""; 2072 } 2073 } 2074 return theTerritory; 2075 } 2076 } 2077