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