1 /* 2 * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package build.tools.cldrconverter; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.text.DateFormatSymbols; 31 import java.util.ArrayList; 32 import java.util.HashMap; 33 import java.util.HashSet; 34 import java.util.List; 35 import java.util.Locale; 36 import java.util.Map; 37 import java.util.Set; 38 import org.xml.sax.Attributes; 39 import org.xml.sax.InputSource; 40 import org.xml.sax.SAXException; 41 42 /** 43 * Handles parsing of files in Locale Data Markup Language and produces a map 44 * that uses the keys and values of JRE locale data. 45 */ 46 class LDMLParseHandler extends AbstractLDMLHandler<Object> { 47 private String defaultNumberingSystem; 48 private String currentNumberingSystem = ""; 49 private CalendarType currentCalendarType; 50 private String zoneNameStyle; // "long" or "short" for time zone names 51 private String zonePrefix; 52 private final String id; 53 private String currentContext = ""; // "format"/"stand-alone" 54 private String currentWidth = ""; // "wide"/"narrow"/"abbreviated" 55 56 LDMLParseHandler(String id) { 57 this.id = id; 58 } 59 60 @Override 61 public InputSource resolveEntity(String publicID, String systemID) throws IOException, SAXException { 62 // avoid HTTP traffic to unicode.org 63 if (systemID.startsWith(CLDRConverter.LDML_DTD_SYSTEM_ID)) { 64 return new InputSource((new File(CLDRConverter.LOCAL_LDML_DTD)).toURI().toString()); 65 } 66 return null; 67 } 68 69 @Override 70 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { 71 switch (qName) { 72 // 73 // Generic information 74 // 75 case "identity": 76 // ignore this element - it has language and territory elements that aren't locale data 77 pushIgnoredContainer(qName); 78 break; 79 80 // for LocaleNames 81 // copy string 82 case "localeSeparator": 83 pushStringEntry(qName, attributes, 84 CLDRConverter.LOCALE_SEPARATOR); 85 break; 86 case "localeKeyTypePattern": 87 pushStringEntry(qName, attributes, 88 CLDRConverter.LOCALE_KEYTYPE); 89 break; 90 91 case "language": 92 case "script": 93 case "territory": 94 case "variant": 95 // for LocaleNames 96 // copy string 97 pushStringEntry(qName, attributes, 98 CLDRConverter.LOCALE_NAME_PREFIX + 99 (qName.equals("variant") ? "%%" : "") + 100 attributes.getValue("type")); 101 break; 102 103 case "key": 104 // for LocaleNames 105 // copy string 106 { 107 String key = convertOldKeyName(attributes.getValue("type")); 108 if (key.length() == 2) { 109 pushStringEntry(qName, attributes, 110 CLDRConverter.LOCALE_KEY_PREFIX + key); 111 } else { 112 pushIgnoredContainer(qName); 113 } 114 } 115 break; 116 117 case "type": 118 // for LocaleNames/CalendarNames 119 // copy string 120 { 121 String key = convertOldKeyName(attributes.getValue("key")); 122 if (key.length() == 2) { 123 pushStringEntry(qName, attributes, 124 CLDRConverter.LOCALE_TYPE_PREFIX + key + "." + 125 attributes.getValue("type")); 126 } else { 127 pushIgnoredContainer(qName); 128 } 129 } 130 break; 131 132 // 133 // Currency information 134 // 135 case "currency": 136 // for CurrencyNames 137 // stash away "type" value for nested <symbol> 138 pushKeyContainer(qName, attributes, attributes.getValue("type")); 139 break; 140 case "symbol": 141 // for CurrencyNames 142 // need to get the key from the containing <currency> element 143 pushStringEntry(qName, attributes, CLDRConverter.CURRENCY_SYMBOL_PREFIX 144 + getContainerKey()); 145 break; 146 147 // Calendar or currency 148 case "displayName": 149 { 150 if (currentContainer.getqName().equals("field")) { 151 pushStringEntry(qName, attributes, 152 (currentCalendarType != null ? currentCalendarType.keyElementName() : "") 153 + "field." + getContainerKey()); 154 } else { 155 // for CurrencyNames 156 // need to get the key from the containing <currency> element 157 // ignore if is has "count" attribute 158 String containerKey = getContainerKey(); 159 if (containerKey != null && attributes.getValue("count") == null) { 160 pushStringEntry(qName, attributes, 161 CLDRConverter.CURRENCY_NAME_PREFIX 162 + containerKey.toLowerCase(Locale.ROOT), 163 attributes.getValue("type")); 164 } else { 165 pushIgnoredContainer(qName); 166 } 167 } 168 } 169 break; 170 171 // 172 // Calendar information 173 // 174 case "calendar": 175 { 176 // mostly for FormatData (CalendarData items firstDay and minDays are also nested) 177 // use only if it's supported by java.util.Calendar. 178 String calendarName = attributes.getValue("type"); 179 currentCalendarType = CalendarType.forName(calendarName); 180 if (currentCalendarType != null) { 181 pushContainer(qName, attributes); 182 } else { 183 pushIgnoredContainer(qName); 184 } 185 } 186 break; 187 case "fields": 188 { 189 pushContainer(qName, attributes); 190 } 191 break; 192 case "field": 193 { 194 String type = attributes.getValue("type"); 195 switch (type) { 196 case "era": 197 case "year": 198 case "month": 199 case "week": 200 case "weekday": 201 case "dayperiod": 202 case "hour": 203 case "minute": 204 case "second": 205 case "zone": 206 pushKeyContainer(qName, attributes, type); 207 break; 208 default: 209 pushIgnoredContainer(qName); 210 break; 211 } 212 } 213 break; 214 case "monthContext": 215 { 216 // for FormatData 217 // need to keep stand-alone and format, to allow for inheritance in CLDR 218 String type = attributes.getValue("type"); 219 if ("stand-alone".equals(type) || "format".equals(type)) { 220 currentContext = type; 221 pushKeyContainer(qName, attributes, type); 222 } else { 223 pushIgnoredContainer(qName); 224 } 225 } 226 break; 227 case "monthWidth": 228 { 229 // for FormatData 230 // create string array for the two types that the JRE knows 231 // keep info about the context type so we can sort out inheritance later 232 if (currentCalendarType == null) { 233 pushIgnoredContainer(qName); 234 break; 235 } 236 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName(); 237 currentWidth = attributes.getValue("type"); 238 switch (currentWidth) { 239 case "wide": 240 pushStringArrayEntry(qName, attributes, prefix + "MonthNames/" + getContainerKey(), 13); 241 break; 242 case "abbreviated": 243 pushStringArrayEntry(qName, attributes, prefix + "MonthAbbreviations/" + getContainerKey(), 13); 244 break; 245 case "narrow": 246 pushStringArrayEntry(qName, attributes, prefix + "MonthNarrows/" + getContainerKey(), 13); 247 break; 248 default: 249 pushIgnoredContainer(qName); 250 break; 251 } 252 } 253 break; 254 case "month": 255 // for FormatData 256 // add to string array entry of monthWidth element 257 pushStringArrayElement(qName, attributes, Integer.parseInt(attributes.getValue("type")) - 1); 258 break; 259 case "dayContext": 260 { 261 // for FormatData 262 // need to keep stand-alone and format, to allow for multiple inheritance in CLDR 263 String type = attributes.getValue("type"); 264 if ("stand-alone".equals(type) || "format".equals(type)) { 265 currentContext = type; 266 pushKeyContainer(qName, attributes, type); 267 } else { 268 pushIgnoredContainer(qName); 269 } 270 } 271 break; 272 case "dayWidth": 273 { 274 // for FormatData 275 // create string array for the two types that the JRE knows 276 // keep info about the context type so we can sort out inheritance later 277 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName(); 278 currentWidth = attributes.getValue("type"); 279 switch (currentWidth) { 280 case "wide": 281 pushStringArrayEntry(qName, attributes, prefix + "DayNames/" + getContainerKey(), 7); 282 break; 283 case "abbreviated": 284 pushStringArrayEntry(qName, attributes, prefix + "DayAbbreviations/" + getContainerKey(), 7); 285 break; 286 case "narrow": 287 pushStringArrayEntry(qName, attributes, prefix + "DayNarrows/" + getContainerKey(), 7); 288 break; 289 default: 290 pushIgnoredContainer(qName); 291 break; 292 } 293 } 294 break; 295 case "day": 296 // for FormatData 297 // add to string array entry of monthWidth element 298 pushStringArrayElement(qName, attributes, Integer.parseInt(DAY_OF_WEEK_MAP.get(attributes.getValue("type"))) - 1); 299 break; 300 case "dayPeriodContext": 301 // for FormatData 302 // need to keep stand-alone and format, to allow for multiple inheritance in CLDR 303 // for FormatData 304 // need to keep stand-alone and format, to allow for multiple inheritance in CLDR 305 { 306 String type = attributes.getValue("type"); 307 if ("stand-alone".equals(type) || "format".equals(type)) { 308 currentContext = type; 309 pushKeyContainer(qName, attributes, type); 310 } else { 311 pushIgnoredContainer(qName); 312 } 313 } 314 break; 315 case "dayPeriodWidth": 316 // for FormatData 317 // create string array entry for am/pm. only keeping wide 318 currentWidth = attributes.getValue("type"); 319 switch (currentWidth) { 320 case "wide": 321 pushStringArrayEntry(qName, attributes, "AmPmMarkers/" + getContainerKey(), 2); 322 break; 323 case "narrow": 324 pushStringArrayEntry(qName, attributes, "narrow.AmPmMarkers/" + getContainerKey(), 2); 325 break; 326 case "abbreviated": 327 pushStringArrayEntry(qName, attributes, "abbreviated.AmPmMarkers/" + getContainerKey(), 2); 328 break; 329 default: 330 pushIgnoredContainer(qName); 331 break; 332 } 333 break; 334 case "dayPeriod": 335 // for FormatData 336 // add to string array entry of AmPmMarkers element 337 if (attributes.getValue("alt") == null) { 338 switch (attributes.getValue("type")) { 339 case "am": 340 pushStringArrayElement(qName, attributes, 0); 341 break; 342 case "pm": 343 pushStringArrayElement(qName, attributes, 1); 344 break; 345 default: 346 pushIgnoredContainer(qName); 347 break; 348 } 349 } else { 350 // discard alt values 351 pushIgnoredContainer(qName); 352 } 353 break; 354 case "eraNames": 355 // CLDR era names are inconsistent in terms of their lengths. For example, 356 // the full names of Japanese imperial eras are eraAbbr, while the full names 357 // of the Julian eras are eraNames. 358 if (currentCalendarType == null) { 359 assert currentContainer instanceof IgnoredContainer; 360 pushIgnoredContainer(qName); 361 } else { 362 String key = currentCalendarType.keyElementName() + "long.Eras"; // for now 363 pushStringArrayEntry(qName, attributes, key, currentCalendarType.getEraLength(qName)); 364 } 365 break; 366 case "eraAbbr": 367 // for FormatData 368 // create string array entry 369 if (currentCalendarType == null) { 370 assert currentContainer instanceof IgnoredContainer; 371 pushIgnoredContainer(qName); 372 } else { 373 String key = currentCalendarType.keyElementName() + "Eras"; 374 pushStringArrayEntry(qName, attributes, key, currentCalendarType.getEraLength(qName)); 375 } 376 break; 377 case "eraNarrow": 378 // mainly used for the Japanese imperial calendar 379 if (currentCalendarType == null) { 380 assert currentContainer instanceof IgnoredContainer; 381 pushIgnoredContainer(qName); 382 } else { 383 String key = currentCalendarType.keyElementName() + "narrow.Eras"; 384 pushStringArrayEntry(qName, attributes, key, currentCalendarType.getEraLength(qName)); 385 } 386 break; 387 case "era": 388 // for FormatData 389 // add to string array entry of eraAbbr element 390 if (currentCalendarType == null) { 391 assert currentContainer instanceof IgnoredContainer; 392 pushIgnoredContainer(qName); 393 } else { 394 int index = Integer.parseInt(attributes.getValue("type")); 395 index = currentCalendarType.normalizeEraIndex(index); 396 if (index >= 0) { 397 pushStringArrayElement(qName, attributes, index); 398 } else { 399 pushIgnoredContainer(qName); 400 } 401 if (currentContainer.getParent() == null) { 402 throw new InternalError("currentContainer: null parent"); 403 } 404 } 405 break; 406 case "quarterContext": 407 { 408 // for FormatData 409 // need to keep stand-alone and format, to allow for inheritance in CLDR 410 String type = attributes.getValue("type"); 411 if ("stand-alone".equals(type) || "format".equals(type)) { 412 currentContext = type; 413 pushKeyContainer(qName, attributes, type); 414 } else { 415 pushIgnoredContainer(qName); 416 } 417 } 418 break; 419 case "quarterWidth": 420 { 421 // for FormatData 422 // keep info about the context type so we can sort out inheritance later 423 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName(); 424 currentWidth = attributes.getValue("type"); 425 switch (currentWidth) { 426 case "wide": 427 pushStringArrayEntry(qName, attributes, prefix + "QuarterNames/" + getContainerKey(), 4); 428 break; 429 case "abbreviated": 430 pushStringArrayEntry(qName, attributes, prefix + "QuarterAbbreviations/" + getContainerKey(), 4); 431 break; 432 case "narrow": 433 pushStringArrayEntry(qName, attributes, prefix + "QuarterNarrows/" + getContainerKey(), 4); 434 break; 435 default: 436 pushIgnoredContainer(qName); 437 break; 438 } 439 } 440 break; 441 case "quarter": 442 // for FormatData 443 // add to string array entry of quarterWidth element 444 pushStringArrayElement(qName, attributes, Integer.parseInt(attributes.getValue("type")) - 1); 445 break; 446 447 // 448 // Time zone names 449 // 450 case "timeZoneNames": 451 pushContainer(qName, attributes); 452 break; 453 case "hourFormat": 454 pushStringEntry(qName, attributes, "timezone.hourFormat"); 455 break; 456 case "gmtFormat": 457 pushStringEntry(qName, attributes, "timezone.gmtFormat"); 458 break; 459 case "gmtZeroFormat": 460 pushStringEntry(qName, attributes, "timezone.gmtZeroFormat"); 461 break; 462 case "regionFormat": 463 { 464 String type = attributes.getValue("type"); 465 pushStringEntry(qName, attributes, "timezone.regionFormat" + 466 (type == null ? "" : "." + type)); 467 } 468 break; 469 case "zone": 470 { 471 String tzid = attributes.getValue("type"); // Olson tz id 472 zonePrefix = CLDRConverter.TIMEZONE_ID_PREFIX; 473 put(zonePrefix + tzid, new HashMap<String, String>()); 474 pushKeyContainer(qName, attributes, tzid); 475 } 476 break; 477 case "metazone": 478 { 479 String zone = attributes.getValue("type"); // LDML meta zone id 480 zonePrefix = CLDRConverter.METAZONE_ID_PREFIX; 481 put(zonePrefix + zone, new HashMap<String, String>()); 482 pushKeyContainer(qName, attributes, zone); 483 } 484 break; 485 case "long": 486 zoneNameStyle = "long"; 487 pushContainer(qName, attributes); 488 break; 489 case "short": 490 zoneNameStyle = "short"; 491 pushContainer(qName, attributes); 492 break; 493 case "generic": // generic name 494 case "standard": // standard time name 495 case "daylight": // daylight saving (summer) time name 496 pushStringEntry(qName, attributes, CLDRConverter.ZONE_NAME_PREFIX + qName + "." + zoneNameStyle); 497 break; 498 case "exemplarCity": 499 pushStringEntry(qName, attributes, CLDRConverter.EXEMPLAR_CITY_PREFIX); 500 break; 501 502 // 503 // Number format information 504 // 505 case "decimalFormatLength": 506 if (attributes.getValue("type") == null) { 507 // skipping type="short" data 508 // for FormatData 509 // copy string for later assembly into NumberPatterns 510 pushStringEntry(qName, attributes, "NumberPatterns/decimal"); 511 } else { 512 pushIgnoredContainer(qName); 513 } 514 break; 515 case "currencyFormatLength": 516 if (attributes.getValue("type") == null) { 517 // skipping type="short" data 518 // for FormatData 519 pushContainer(qName, attributes); 520 } else { 521 pushIgnoredContainer(qName); 522 } 523 break; 524 case "currencyFormat": 525 // for FormatData 526 // copy string for later assembly into NumberPatterns 527 if (attributes.getValue("type").equals("standard")) { 528 pushStringEntry(qName, attributes, "NumberPatterns/currency"); 529 } else { 530 pushIgnoredContainer(qName); 531 } 532 break; 533 case "percentFormat": 534 // for FormatData 535 // copy string for later assembly into NumberPatterns 536 if (attributes.getValue("type").equals("standard")) { 537 pushStringEntry(qName, attributes, "NumberPatterns/percent"); 538 } else { 539 pushIgnoredContainer(qName); 540 } 541 break; 542 case "defaultNumberingSystem": 543 // default numbering system if multiple numbering systems are used. 544 pushStringEntry(qName, attributes, "DefaultNumberingSystem"); 545 break; 546 case "symbols": 547 // for FormatData 548 // look up numberingSystems 549 symbols: { 550 String script = attributes.getValue("numberSystem"); 551 if (script == null) { 552 // Has no script. Just ignore. 553 pushIgnoredContainer(qName); 554 break; 555 } 556 557 // Use keys as <script>."NumberElements/<symbol>" 558 currentNumberingSystem = script + "."; 559 String digits = CLDRConverter.handlerNumbering.get(script); 560 if (digits == null) { 561 pushIgnoredContainer(qName); 562 break; 563 } 564 565 @SuppressWarnings("unchecked") 566 List<String> numberingScripts = (List<String>) get("numberingScripts"); 567 if (numberingScripts == null) { 568 numberingScripts = new ArrayList<>(); 569 put("numberingScripts", numberingScripts); 570 } 571 numberingScripts.add(script); 572 put(currentNumberingSystem + "NumberElements/zero", digits.substring(0, 1)); 573 pushContainer(qName, attributes); 574 } 575 break; 576 case "decimal": 577 // for FormatData 578 // copy string for later assembly into NumberElements 579 if (currentContainer.getqName().equals("symbols")) { 580 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/decimal"); 581 } else { 582 pushIgnoredContainer(qName); 583 } 584 break; 585 case "group": 586 // for FormatData 587 // copy string for later assembly into NumberElements 588 if (currentContainer.getqName().equals("symbols")) { 589 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/group"); 590 } else { 591 pushIgnoredContainer(qName); 592 } 593 break; 594 case "list": 595 // for FormatData 596 // copy string for later assembly into NumberElements 597 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/list"); 598 break; 599 case "percentSign": 600 // for FormatData 601 // copy string for later assembly into NumberElements 602 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/percent"); 603 break; 604 case "nativeZeroDigit": 605 // for FormatData 606 // copy string for later assembly into NumberElements 607 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/zero"); 608 break; 609 case "patternDigit": 610 // for FormatData 611 // copy string for later assembly into NumberElements 612 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/pattern"); 613 break; 614 case "plusSign": 615 // TODO: DecimalFormatSymbols doesn't support plusSign 616 pushIgnoredContainer(qName); 617 break; 618 case "minusSign": 619 // for FormatData 620 // copy string for later assembly into NumberElements 621 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/minus"); 622 break; 623 case "exponential": 624 // for FormatData 625 // copy string for later assembly into NumberElements 626 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/exponential"); 627 break; 628 case "perMille": 629 // for FormatData 630 // copy string for later assembly into NumberElements 631 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/permille"); 632 break; 633 case "infinity": 634 // for FormatData 635 // copy string for later assembly into NumberElements 636 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/infinity"); 637 break; 638 case "nan": 639 // for FormatData 640 // copy string for later assembly into NumberElements 641 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/nan"); 642 break; 643 case "timeFormatLength": 644 { 645 // for FormatData 646 // copy string for later assembly into DateTimePatterns 647 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName(); 648 pushStringEntry(qName, attributes, prefix + "DateTimePatterns/" + attributes.getValue("type") + "-time"); 649 } 650 break; 651 case "dateFormatLength": 652 { 653 // for FormatData 654 // copy string for later assembly into DateTimePatterns 655 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName(); 656 pushStringEntry(qName, attributes, prefix + "DateTimePatterns/" + attributes.getValue("type") + "-date"); 657 } 658 break; 659 case "dateTimeFormatLength": 660 { 661 // for FormatData 662 // copy string for later assembly into DateTimePatterns 663 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName(); 664 pushStringEntry(qName, attributes, prefix + "DateTimePatterns/" + attributes.getValue("type") + "-dateTime"); 665 } 666 break; 667 case "localizedPatternChars": 668 { 669 // for FormatData 670 // copy string for later adaptation to JRE use 671 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName(); 672 pushStringEntry(qName, attributes, prefix + "DateTimePatternChars"); 673 } 674 break; 675 676 // "alias" for root 677 case "alias": 678 { 679 if (id.equals("root") && 680 !isIgnored(attributes) && 681 currentCalendarType != null && 682 !currentCalendarType.lname().startsWith("islamic-")) { // ignore Islamic variants 683 pushAliasEntry(qName, attributes, attributes.getValue("path")); 684 } else { 685 pushIgnoredContainer(qName); 686 } 687 } 688 break; 689 690 default: 691 // treat anything else as a container 692 pushContainer(qName, attributes); 693 break; 694 } 695 } 696 697 private static final String[] CONTEXTS = {"stand-alone", "format"}; 698 private static final String[] WIDTHS = {"wide", "narrow", "abbreviated"}; 699 private static final String[] LENGTHS = {"full", "long", "medium", "short"}; 700 701 private void populateWidthAlias(String type, Set<String> keys) { 702 for (String context : CONTEXTS) { 703 for (String width : WIDTHS) { 704 String keyName = toJDKKey(type+"Width", context, width); 705 if (keyName.length() > 0) { 706 keys.add(keyName + "," + context + "," + width); 707 } 708 } 709 } 710 } 711 712 private void populateFormatLengthAlias(String type, Set<String> keys) { 713 for (String length: LENGTHS) { 714 String keyName = toJDKKey(type+"FormatLength", currentContext, length); 715 if (keyName.length() > 0) { 716 keys.add(keyName + "," + currentContext + "," + length); 717 } 718 } 719 } 720 721 private Set<String> populateAliasKeys(String qName, String context, String width) { 722 HashSet<String> ret = new HashSet<>(); 723 String keyName = qName; 724 725 switch (qName) { 726 case "monthWidth": 727 case "dayWidth": 728 case "quarterWidth": 729 case "dayPeriodWidth": 730 case "dateFormatLength": 731 case "timeFormatLength": 732 case "dateTimeFormatLength": 733 case "eraNames": 734 case "eraAbbr": 735 case "eraNarrow": 736 ret.add(toJDKKey(qName, context, width) + "," + context + "," + width); 737 break; 738 case "days": 739 populateWidthAlias("day", ret); 740 break; 741 case "months": 742 populateWidthAlias("month", ret); 743 break; 744 case "quarters": 745 populateWidthAlias("quarter", ret); 746 break; 747 case "dayPeriods": 748 populateWidthAlias("dayPeriod", ret); 749 break; 750 case "eras": 751 ret.add(toJDKKey("eraNames", context, width) + "," + context + "," + width); 752 ret.add(toJDKKey("eraAbbr", context, width) + "," + context + "," + width); 753 ret.add(toJDKKey("eraNarrow", context, width) + "," + context + "," + width); 754 break; 755 case "dateFormats": 756 populateFormatLengthAlias("date", ret); 757 break; 758 case "timeFormats": 759 populateFormatLengthAlias("time", ret); 760 break; 761 default: 762 break; 763 } 764 return ret; 765 } 766 767 private String translateWidthAlias(String qName, String context, String width) { 768 String keyName = qName; 769 String type = Character.toUpperCase(qName.charAt(0)) + qName.substring(1, qName.indexOf("Width")); 770 771 switch (width) { 772 case "wide": 773 keyName = type + "Names/" + context; 774 break; 775 case "abbreviated": 776 keyName = type + "Abbreviations/" + context; 777 break; 778 case "narrow": 779 keyName = type + "Narrows/" + context; 780 break; 781 default: 782 assert false; 783 } 784 785 return keyName; 786 } 787 788 private String toJDKKey(String containerqName, String context, String type) { 789 String keyName = containerqName; 790 791 switch (containerqName) { 792 case "monthWidth": 793 case "dayWidth": 794 case "quarterWidth": 795 keyName = translateWidthAlias(keyName, context, type); 796 break; 797 case "dayPeriodWidth": 798 switch (type) { 799 case "wide": 800 keyName = "AmPmMarkers/" + context; 801 break; 802 case "narrow": 803 keyName = "narrow.AmPmMarkers/" + context; 804 break; 805 case "abbreviated": 806 keyName = "abbreviated.AmPmMarkers/" + context; 807 break; 808 } 809 break; 810 case "dateFormatLength": 811 case "timeFormatLength": 812 case "dateTimeFormatLength": 813 keyName = "DateTimePatterns/" + 814 type + "-" + 815 keyName.substring(0, keyName.indexOf("FormatLength")); 816 break; 817 case "eraNames": 818 keyName = "long.Eras"; 819 break; 820 case "eraAbbr": 821 keyName = "Eras"; 822 break; 823 case "eraNarrow": 824 keyName = "narrow.Eras"; 825 break; 826 case "dateFormats": 827 case "timeFormats": 828 case "days": 829 case "months": 830 case "quarters": 831 case "dayPeriods": 832 case "eras": 833 break; 834 default: 835 keyName = ""; 836 break; 837 } 838 839 return keyName; 840 } 841 842 private String getTarget(String path, String calType, String context, String width) { 843 // Target qName 844 int lastSlash = path.lastIndexOf('/'); 845 String qName = path.substring(lastSlash+1); 846 int bracket = qName.indexOf('['); 847 if (bracket != -1) { 848 qName = qName.substring(0, bracket); 849 } 850 851 // calType 852 String typeKey = "/calendar[@type='"; 853 int start = path.indexOf(typeKey); 854 if (start != -1) { 855 calType = path.substring(start+typeKey.length(), path.indexOf("']", start)); 856 } 857 858 // context 859 typeKey = "Context[@type='"; 860 start = path.indexOf(typeKey); 861 if (start != -1) { 862 context = (path.substring(start+typeKey.length(), path.indexOf("']", start))); 863 } 864 865 // width 866 typeKey = "Width[@type='"; 867 start = path.indexOf(typeKey); 868 if (start != -1) { 869 width = path.substring(start+typeKey.length(), path.indexOf("']", start)); 870 } 871 872 return calType + "." + toJDKKey(qName, context, width); 873 } 874 875 @Override 876 public void endElement(String uri, String localName, String qName) throws SAXException { 877 assert qName.equals(currentContainer.getqName()) : "current=" + currentContainer.getqName() + ", param=" + qName; 878 switch (qName) { 879 case "calendar": 880 assert !(currentContainer instanceof Entry); 881 currentCalendarType = null; 882 break; 883 884 case "defaultNumberingSystem": 885 if (currentContainer instanceof StringEntry) { 886 defaultNumberingSystem = ((StringEntry) currentContainer).getValue(); 887 assert defaultNumberingSystem != null; 888 put(((StringEntry) currentContainer).getKey(), defaultNumberingSystem); 889 } else { 890 defaultNumberingSystem = null; 891 } 892 break; 893 894 case "timeZoneNames": 895 zonePrefix = null; 896 break; 897 898 case "generic": 899 case "standard": 900 case "daylight": 901 case "exemplarCity": 902 if (zonePrefix != null && (currentContainer instanceof Entry)) { 903 @SuppressWarnings("unchecked") 904 Map<String, String> valmap = (Map<String, String>) get(zonePrefix + getContainerKey()); 905 Entry<?> entry = (Entry<?>) currentContainer; 906 if (qName.equals("exemplarCity")) { 907 put(CLDRConverter.EXEMPLAR_CITY_PREFIX + getContainerKey(), (String) entry.getValue()); 908 } else { 909 valmap.put(entry.getKey(), (String) entry.getValue()); 910 } 911 } 912 break; 913 914 case "monthWidth": 915 case "dayWidth": 916 case "dayPeriodWidth": 917 case "quarterWidth": 918 currentWidth = ""; 919 putIfEntry(); 920 break; 921 922 case "monthContext": 923 case "dayContext": 924 case "dayPeriodContext": 925 case "quarterContext": 926 currentContext = ""; 927 putIfEntry(); 928 break; 929 930 default: 931 putIfEntry(); 932 } 933 currentContainer = currentContainer.getParent(); 934 } 935 936 private void putIfEntry() { 937 if (currentContainer instanceof AliasEntry) { 938 Entry<?> entry = (Entry<?>) currentContainer; 939 String containerqName = entry.getParent().getqName(); 940 Set<String> keyNames = populateAliasKeys(containerqName, currentContext, currentWidth); 941 if (!keyNames.isEmpty()) { 942 for (String keyName : keyNames) { 943 String[] tmp = keyName.split(",", 3); 944 String calType = currentCalendarType.lname(); 945 String src = calType+"."+tmp[0]; 946 String target = getTarget( 947 entry.getKey(), 948 calType, 949 tmp[1].length()>0 ? tmp[1] : currentContext, 950 tmp[2].length()>0 ? tmp[2] : currentWidth); 951 if (target.substring(target.lastIndexOf('.')+1).equals(containerqName)) { 952 target = target.substring(0, target.indexOf('.'))+"."+tmp[0]; 953 } 954 CLDRConverter.aliases.put(src.replaceFirst("^gregorian.", ""), 955 target.replaceFirst("^gregorian.", "")); 956 } 957 } 958 } else if (currentContainer instanceof Entry) { 959 Entry<?> entry = (Entry<?>) currentContainer; 960 Object value = entry.getValue(); 961 if (value != null) { 962 String key = entry.getKey(); 963 // Tweak for MonthNames for the root locale, Needed for 964 // SimpleDateFormat.format()/parse() roundtrip. 965 if (id.equals("root") && key.startsWith("MonthNames")) { 966 value = new DateFormatSymbols(Locale.US).getShortMonths(); 967 } 968 put(entry.getKey(), value); 969 } 970 } 971 } 972 973 public String convertOldKeyName(String key) { 974 // Explicitly obtained from "alias" attribute in each "key" element. 975 switch (key) { 976 case "calendar": 977 return "ca"; 978 case "currency": 979 return "cu"; 980 case "collation": 981 return "co"; 982 case "numbers": 983 return "nu"; 984 case "timezone": 985 return "tz"; 986 default: 987 return key; 988 } 989 } 990 }