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 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 if (currentContainer.getqName().equals("symbols")) { 553 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/decimal"); 554 } else { 555 pushIgnoredContainer(qName); 556 } 557 break; 558 case "group": 559 // for FormatData 560 // copy string for later assembly into NumberElements 561 if (currentContainer.getqName().equals("symbols")) { 562 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/group"); 563 } else { 564 pushIgnoredContainer(qName); 565 } 566 break; 567 case "list": 568 // for FormatData 569 // copy string for later assembly into NumberElements 570 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/list"); 571 break; 572 case "percentSign": 573 // for FormatData 574 // copy string for later assembly into NumberElements 575 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/percent"); 576 break; 577 case "nativeZeroDigit": 578 // for FormatData 579 // copy string for later assembly into NumberElements 580 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/zero"); 581 break; 582 case "patternDigit": 583 // for FormatData 584 // copy string for later assembly into NumberElements 585 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/pattern"); 586 break; 587 case "plusSign": 588 // TODO: DecimalFormatSymbols doesn't support plusSign 589 pushIgnoredContainer(qName); 590 break; 591 case "minusSign": 592 // for FormatData 593 // copy string for later assembly into NumberElements 594 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/minus"); 595 break; 596 case "exponential": 597 // for FormatData 598 // copy string for later assembly into NumberElements 599 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/exponential"); 600 break; 601 case "perMille": 602 // for FormatData 603 // copy string for later assembly into NumberElements 604 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/permille"); 605 break; 606 case "infinity": 607 // for FormatData 608 // copy string for later assembly into NumberElements 609 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/infinity"); 610 break; 611 case "nan": 612 // for FormatData 613 // copy string for later assembly into NumberElements 614 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/nan"); 615 break; 616 case "timeFormatLength": 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") + "-time"); 622 } 623 break; 624 case "dateFormatLength": 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") + "-date"); 630 } 631 break; 632 case "dateTimeFormatLength": 633 { 634 // for FormatData 635 // copy string for later assembly into DateTimePatterns 636 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName(); 637 pushStringEntry(qName, attributes, prefix + "DateTimePatterns/" + attributes.getValue("type") + "-dateTime"); 638 } 639 break; 640 case "localizedPatternChars": 641 { 642 // for FormatData 643 // copy string for later adaptation to JRE use 644 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName(); 645 pushStringEntry(qName, attributes, prefix + "DateTimePatternChars"); 646 } 647 break; 648 649 // "alias" for root 650 case "alias": 651 { 652 if (id.equals("root") && 653 !isIgnored(attributes) && 654 currentCalendarType != null && 655 !currentCalendarType.lname().startsWith("islamic-")) { // ignore Islamic variants 656 pushAliasEntry(qName, attributes, attributes.getValue("path")); 657 } else { 658 pushIgnoredContainer(qName); 659 } 660 } 661 break; 662 663 default: 664 // treat anything else as a container 665 pushContainer(qName, attributes); 666 break; 667 } 668 } 669 670 private static final String[] CONTEXTS = {"stand-alone", "format"}; 671 private static final String[] WIDTHS = {"wide", "narrow", "abbreviated"}; 672 private static final String[] LENGTHS = {"full", "long", "medium", "short"}; 673 674 private void populateWidthAlias(String type, Set<String> keys) { 675 for (String context : CONTEXTS) { 676 for (String width : WIDTHS) { 677 String keyName = toJDKKey(type+"Width", context, width); 678 if (keyName.length() > 0) { 679 keys.add(keyName + "," + context + "," + width); 680 } 681 } 682 } 683 } 684 685 private void populateFormatLengthAlias(String type, Set<String> keys) { 686 for (String length: LENGTHS) { 687 String keyName = toJDKKey(type+"FormatLength", currentContext, length); 688 if (keyName.length() > 0) { 689 keys.add(keyName + "," + currentContext + "," + length); 690 } 691 } 692 } 693 694 private Set<String> populateAliasKeys(String qName, String context, String width) { 695 HashSet<String> ret = new HashSet<>(); 696 String keyName = qName; 697 698 switch (qName) { 699 case "monthWidth": 700 case "dayWidth": 701 case "quarterWidth": 702 case "dayPeriodWidth": 703 case "dateFormatLength": 704 case "timeFormatLength": 705 case "dateTimeFormatLength": 706 case "eraNames": 707 case "eraAbbr": 708 case "eraNarrow": 709 ret.add(toJDKKey(qName, context, width) + "," + context + "," + width); 710 break; 711 case "days": 712 populateWidthAlias("day", ret); 713 break; 714 case "months": 715 populateWidthAlias("month", ret); 716 break; 717 case "quarters": 718 populateWidthAlias("quarter", ret); 719 break; 720 case "dayPeriods": 721 populateWidthAlias("dayPeriod", ret); 722 break; 723 case "eras": 724 ret.add(toJDKKey("eraNames", context, width) + "," + context + "," + width); 725 ret.add(toJDKKey("eraAbbr", context, width) + "," + context + "," + width); 726 ret.add(toJDKKey("eraNarrow", context, width) + "," + context + "," + width); 727 break; 728 case "dateFormats": 729 populateFormatLengthAlias("date", ret); 730 break; 731 case "timeFormats": 732 populateFormatLengthAlias("time", ret); 733 break; 734 default: 735 break; 736 } 737 return ret; 738 } 739 740 private String translateWidthAlias(String qName, String context, String width) { 741 String keyName = qName; 742 String type = Character.toUpperCase(qName.charAt(0)) + qName.substring(1, qName.indexOf("Width")); 743 744 switch (width) { 745 case "wide": 746 keyName = type + "Names/" + context; 747 break; 748 case "abbreviated": 749 keyName = type + "Abbreviations/" + context; 750 break; 751 case "narrow": 752 keyName = type + "Narrows/" + context; 753 break; 754 default: 755 assert false; 756 } 757 758 return keyName; 759 } 760 761 private String toJDKKey(String containerqName, String context, String type) { 762 String keyName = containerqName; 763 764 switch (containerqName) { 765 case "monthWidth": 766 case "dayWidth": 767 case "quarterWidth": 768 keyName = translateWidthAlias(keyName, context, type); 769 break; 770 case "dayPeriodWidth": 771 switch (type) { 772 case "wide": 773 keyName = "AmPmMarkers/" + context; 774 break; 775 case "narrow": 776 keyName = "narrow.AmPmMarkers/" + context; 777 break; 778 case "abbreviated": 779 keyName = "abbreviated.AmPmMarkers/" + context; 780 break; 781 } 782 break; 783 case "dateFormatLength": 784 case "timeFormatLength": 785 case "dateTimeFormatLength": 786 keyName = "DateTimePatterns/" + 787 type + "-" + 788 keyName.substring(0, keyName.indexOf("FormatLength")); 789 break; 790 case "eraNames": 791 keyName = "long.Eras"; 792 break; 793 case "eraAbbr": 794 keyName = "Eras"; 795 break; 796 case "eraNarrow": 797 keyName = "narrow.Eras"; 798 break; 799 case "dateFormats": 800 case "timeFormats": 801 case "days": 802 case "months": 803 case "quarters": 804 case "dayPeriods": 805 case "eras": 806 break; 807 default: 808 keyName = ""; 809 break; 810 } 811 812 return keyName; 813 } 814 815 private String getTarget(String path, String calType, String context, String width) { 816 // Target qName 817 int lastSlash = path.lastIndexOf('/'); 818 String qName = path.substring(lastSlash+1); 819 int bracket = qName.indexOf('['); 820 if (bracket != -1) { 821 qName = qName.substring(0, bracket); 822 } 823 824 // calType 825 String typeKey = "/calendar[@type='"; 826 int start = path.indexOf(typeKey); 827 if (start != -1) { 828 calType = path.substring(start+typeKey.length(), path.indexOf("']", start)); 829 } 830 831 // context 832 typeKey = "Context[@type='"; 833 start = path.indexOf(typeKey); 834 if (start != -1) { 835 context = (path.substring(start+typeKey.length(), path.indexOf("']", start))); 836 } 837 838 // width 839 typeKey = "Width[@type='"; 840 start = path.indexOf(typeKey); 841 if (start != -1) { 842 width = path.substring(start+typeKey.length(), path.indexOf("']", start)); 843 } 844 845 return calType + "." + toJDKKey(qName, context, width); 846 } 847 848 @Override 849 public void endElement(String uri, String localName, String qName) throws SAXException { 850 assert qName.equals(currentContainer.getqName()) : "current=" + currentContainer.getqName() + ", param=" + qName; 851 switch (qName) { 852 case "calendar": 853 assert !(currentContainer instanceof Entry); 854 currentCalendarType = null; 855 break; 856 857 case "defaultNumberingSystem": 858 if (currentContainer instanceof StringEntry) { 859 defaultNumberingSystem = ((StringEntry) currentContainer).getValue(); 860 assert defaultNumberingSystem != null; 861 put(((StringEntry) currentContainer).getKey(), defaultNumberingSystem); 862 } else { 863 defaultNumberingSystem = null; 864 } 865 break; 866 867 case "timeZoneNames": 868 zonePrefix = null; 869 break; 870 871 case "generic": 872 case "standard": 873 case "daylight": 874 if (zonePrefix != null && (currentContainer instanceof Entry)) { 875 @SuppressWarnings("unchecked") 876 Map<String, String> valmap = (Map<String, String>) get(zonePrefix + getContainerKey()); 877 Entry<?> entry = (Entry<?>) currentContainer; 878 valmap.put(entry.getKey(), (String) entry.getValue()); 879 } 880 break; 881 882 case "monthWidth": 883 case "dayWidth": 884 case "dayPeriodWidth": 885 case "quarterWidth": 886 currentWidth = ""; 887 putIfEntry(); 888 break; 889 890 case "monthContext": 891 case "dayContext": 892 case "dayPeriodContext": 893 case "quarterContext": 894 currentContext = ""; 895 putIfEntry(); 896 break; 897 898 default: 899 putIfEntry(); 900 } 901 currentContainer = currentContainer.getParent(); 902 } 903 904 private void putIfEntry() { 905 if (currentContainer instanceof AliasEntry) { 906 Entry<?> entry = (Entry<?>) currentContainer; 907 String containerqName = entry.getParent().getqName(); 908 Set<String> keyNames = populateAliasKeys(containerqName, currentContext, currentWidth); 909 if (!keyNames.isEmpty()) { 910 for (String keyName : keyNames) { 911 String[] tmp = keyName.split(",", 3); 912 String calType = currentCalendarType.lname(); 913 String src = calType+"."+tmp[0]; 914 String target = getTarget( 915 entry.getKey(), 916 calType, 917 tmp[1].length()>0 ? tmp[1] : currentContext, 918 tmp[2].length()>0 ? tmp[2] : currentWidth); 919 if (target.substring(target.lastIndexOf('.')+1).equals(containerqName)) { 920 target = target.substring(0, target.indexOf('.'))+"."+tmp[0]; 921 } 922 CLDRConverter.aliases.put(src.replaceFirst("^gregorian.", ""), 923 target.replaceFirst("^gregorian.", "")); 924 } 925 } 926 } else if (currentContainer instanceof Entry) { 927 Entry<?> entry = (Entry<?>) currentContainer; 928 Object value = entry.getValue(); 929 if (value != null) { 930 String key = entry.getKey(); 931 // Tweak for MonthNames for the root locale, Needed for 932 // SimpleDateFormat.format()/parse() roundtrip. 933 if (id.equals("root") && key.startsWith("MonthNames")) { 934 value = new DateFormatSymbols(Locale.US).getShortMonths(); 935 } 936 put(entry.getKey(), value); 937 } 938 } 939 } 940 }