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