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 default: 294 pushIgnoredContainer(qName); 295 break; 296 } 297 break; 298 case "dayPeriod": 299 // for FormatData 300 // add to string array entry of AmPmMarkers element 301 if (attributes.getValue("alt") == null) { 302 switch (attributes.getValue("type")) { 303 case "am": 304 pushStringArrayElement(qName, attributes, 0); 305 break; 306 case "pm": 307 pushStringArrayElement(qName, attributes, 1); 308 break; 309 default: 310 pushIgnoredContainer(qName); 311 break; 312 } 313 } else { 314 // discard alt values 315 pushIgnoredContainer(qName); 316 } 317 break; 318 case "eraNames": 319 // CLDR era names are inconsistent in terms of their lengths. For example, 320 // the full names of Japanese imperial eras are eraAbbr, while the full names 321 // of the Julian eras are eraNames. 322 if (currentCalendarType == null) { 323 assert currentContainer instanceof IgnoredContainer; 324 pushIgnoredContainer(qName); 325 } else { 326 String key = currentCalendarType.keyElementName() + "long.Eras"; // for now 327 pushStringArrayEntry(qName, attributes, key, currentCalendarType.getEraLength(qName)); 328 } 329 break; 330 case "eraAbbr": 331 // for FormatData 332 // create string array entry 333 if (currentCalendarType == null) { 334 assert currentContainer instanceof IgnoredContainer; 335 pushIgnoredContainer(qName); 336 } else { 337 String key = currentCalendarType.keyElementName() + "Eras"; 338 pushStringArrayEntry(qName, attributes, key, currentCalendarType.getEraLength(qName)); 339 } 340 break; 341 case "eraNarrow": 342 // mainly used for the Japanese imperial calendar 343 if (currentCalendarType == null) { 344 assert currentContainer instanceof IgnoredContainer; 345 pushIgnoredContainer(qName); 346 } else { 347 String key = currentCalendarType.keyElementName() + "narrow.Eras"; 348 pushStringArrayEntry(qName, attributes, key, currentCalendarType.getEraLength(qName)); 349 } 350 break; 351 case "era": 352 // for FormatData 353 // add to string array entry of eraAbbr element 354 if (currentCalendarType == null) { 355 assert currentContainer instanceof IgnoredContainer; 356 pushIgnoredContainer(qName); 357 } else { 358 int index = Integer.parseInt(attributes.getValue("type")); 359 index = currentCalendarType.normalizeEraIndex(index); 360 if (index >= 0) { 361 pushStringArrayElement(qName, attributes, index); 362 } else { 363 pushIgnoredContainer(qName); 364 } 365 if (currentContainer.getParent() == null) { 366 throw new InternalError("currentContainer: null parent"); 367 } 368 } 369 break; 370 case "quarterContext": 371 { 372 // for FormatData 373 // need to keep stand-alone and format, to allow for inheritance in CLDR 374 String type = attributes.getValue("type"); 375 if ("stand-alone".equals(type) || "format".equals(type)) { 376 currentContext = type; 377 pushKeyContainer(qName, attributes, type); 378 } else { 379 pushIgnoredContainer(qName); 380 } 381 } 382 break; 383 case "quarterWidth": 384 { 385 // for FormatData 386 // keep info about the context type so we can sort out inheritance later 387 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName(); 388 currentWidth = attributes.getValue("type"); 389 switch (currentWidth) { 390 case "wide": 391 pushStringArrayEntry(qName, attributes, prefix + "QuarterNames/" + getContainerKey(), 4); 392 break; 393 case "abbreviated": 394 pushStringArrayEntry(qName, attributes, prefix + "QuarterAbbreviations/" + getContainerKey(), 4); 395 break; 396 case "narrow": 397 pushStringArrayEntry(qName, attributes, prefix + "QuarterNarrows/" + getContainerKey(), 4); 398 break; 399 default: 400 pushIgnoredContainer(qName); 401 break; 402 } 403 } 404 break; 405 case "quarter": 406 // for FormatData 407 // add to string array entry of quarterWidth element 408 pushStringArrayElement(qName, attributes, Integer.parseInt(attributes.getValue("type")) - 1); 409 break; 410 411 // 412 // Time zone names 413 // 414 case "timeZoneNames": 415 pushContainer(qName, attributes); 416 break; 417 case "zone": 418 { 419 String tzid = attributes.getValue("type"); // Olson tz id 420 zonePrefix = CLDRConverter.TIMEZONE_ID_PREFIX; 421 put(zonePrefix + tzid, new HashMap<String, String>()); 422 pushKeyContainer(qName, attributes, tzid); 423 } 424 break; 425 case "metazone": 426 { 427 String zone = attributes.getValue("type"); // LDML meta zone id 428 zonePrefix = CLDRConverter.METAZONE_ID_PREFIX; 429 put(zonePrefix + zone, new HashMap<String, String>()); 430 pushKeyContainer(qName, attributes, zone); 431 } 432 break; 433 case "long": 434 zoneNameStyle = "long"; 435 pushContainer(qName, attributes); 436 break; 437 case "short": 438 zoneNameStyle = "short"; 439 pushContainer(qName, attributes); 440 break; 441 case "generic": // generic name 442 case "standard": // standard time name 443 case "daylight": // daylight saving (summer) time name 444 pushStringEntry(qName, attributes, CLDRConverter.ZONE_NAME_PREFIX + qName + "." + zoneNameStyle); 445 break; 446 case "exemplarCity": // not used in JDK 447 pushIgnoredContainer(qName); 448 break; 449 450 // 451 // Number format information 452 // 453 case "decimalFormatLength": 454 if (attributes.getValue("type") == null) { 455 // skipping type="short" data 456 // for FormatData 457 // copy string for later assembly into NumberPatterns 458 pushStringEntry(qName, attributes, "NumberPatterns/decimal"); 459 } else { 460 pushIgnoredContainer(qName); 461 } 462 break; 463 case "currencyFormat": 464 // for FormatData 465 // copy string for later assembly into NumberPatterns 466 if (attributes.getValue("type").equals("standard")) { 467 pushStringEntry(qName, attributes, "NumberPatterns/currency"); 468 } else { 469 pushIgnoredContainer(qName); 470 } 471 break; 472 case "percentFormat": 473 // for FormatData 474 // copy string for later assembly into NumberPatterns 475 if (attributes.getValue("type").equals("standard")) { 476 pushStringEntry(qName, attributes, "NumberPatterns/percent"); 477 } else { 478 pushIgnoredContainer(qName); 479 } 480 break; 481 case "defaultNumberingSystem": 482 // default numbering system if multiple numbering systems are used. 483 pushStringEntry(qName, attributes, "DefaultNumberingSystem"); 484 break; 485 case "symbols": 486 // for FormatData 487 // look up numberingSystems 488 symbols: { 489 String script = attributes.getValue("numberSystem"); 490 if (script == null) { 491 // Has no script. Just ignore. 492 pushIgnoredContainer(qName); 493 break; 494 } 495 496 // Use keys as <script>."NumberElements/<symbol>" 497 currentNumberingSystem = script + "."; 498 String digits = CLDRConverter.handlerNumbering.get(script); 499 if (digits == null) { 500 throw new InternalError("null digits for " + script); 501 } 502 if (Character.isSurrogate(digits.charAt(0))) { 503 // DecimalFormatSymbols doesn't support supplementary characters as digit zero. 504 pushIgnoredContainer(qName); 505 break; 506 } 507 // in case digits are in the reversed order, reverse back the order. 508 if (digits.charAt(0) > digits.charAt(digits.length() - 1)) { 509 StringBuilder sb = new StringBuilder(digits); 510 digits = sb.reverse().toString(); 511 } 512 // Check if the order is sequential. 513 char c0 = digits.charAt(0); 514 for (int i = 1; i < digits.length(); i++) { 515 if (digits.charAt(i) != c0 + i) { 516 pushIgnoredContainer(qName); 517 break symbols; 518 } 519 } 520 @SuppressWarnings("unchecked") 521 List<String> numberingScripts = (List<String>) get("numberingScripts"); 522 if (numberingScripts == null) { 523 numberingScripts = new ArrayList<>(); 524 put("numberingScripts", numberingScripts); 525 } 526 numberingScripts.add(script); 527 put(currentNumberingSystem + "NumberElements/zero", digits.substring(0, 1)); 528 pushContainer(qName, attributes); 529 } 530 break; 531 case "decimal": 532 // for FormatData 533 // copy string for later assembly into NumberElements 534 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/decimal"); 535 break; 536 case "group": 537 // for FormatData 538 // copy string for later assembly into NumberElements 539 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/group"); 540 break; 541 case "list": 542 // for FormatData 543 // copy string for later assembly into NumberElements 544 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/list"); 545 break; 546 case "percentSign": 547 // for FormatData 548 // copy string for later assembly into NumberElements 549 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/percent"); 550 break; 551 case "nativeZeroDigit": 552 // for FormatData 553 // copy string for later assembly into NumberElements 554 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/zero"); 555 break; 556 case "patternDigit": 557 // for FormatData 558 // copy string for later assembly into NumberElements 559 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/pattern"); 560 break; 561 case "plusSign": 562 // TODO: DecimalFormatSymbols doesn't support plusSign 563 pushIgnoredContainer(qName); 564 break; 565 case "minusSign": 566 // for FormatData 567 // copy string for later assembly into NumberElements 568 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/minus"); 569 break; 570 case "exponential": 571 // for FormatData 572 // copy string for later assembly into NumberElements 573 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/exponential"); 574 break; 575 case "perMille": 576 // for FormatData 577 // copy string for later assembly into NumberElements 578 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/permille"); 579 break; 580 case "infinity": 581 // for FormatData 582 // copy string for later assembly into NumberElements 583 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/infinity"); 584 break; 585 case "nan": 586 // for FormatData 587 // copy string for later assembly into NumberElements 588 pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/nan"); 589 break; 590 case "timeFormatLength": 591 { 592 // for FormatData 593 // copy string for later assembly into DateTimePatterns 594 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName(); 595 pushStringEntry(qName, attributes, prefix + "DateTimePatterns/" + attributes.getValue("type") + "-time"); 596 } 597 break; 598 case "dateFormatLength": 599 { 600 // for FormatData 601 // copy string for later assembly into DateTimePatterns 602 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName(); 603 pushStringEntry(qName, attributes, prefix + "DateTimePatterns/" + attributes.getValue("type") + "-date"); 604 } 605 break; 606 case "dateTimeFormatLength": 607 { 608 // for FormatData 609 // copy string for later assembly into DateTimePatterns 610 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName(); 611 pushStringEntry(qName, attributes, prefix + "DateTimePatterns/" + attributes.getValue("type") + "-dateTime"); 612 } 613 break; 614 case "localizedPatternChars": 615 { 616 // for FormatData 617 // copy string for later adaptation to JRE use 618 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName(); 619 pushStringEntry(qName, attributes, prefix + "DateTimePatternChars"); 620 } 621 break; 622 623 // "alias" for root 624 case "alias": 625 { 626 if (id.equals("root") && 627 !isIgnored(attributes) && 628 currentCalendarType != null && 629 !currentCalendarType.lname().startsWith("islamic-")) { // ignore Islamic variants 630 pushAliasEntry(qName, attributes, attributes.getValue("path")); 631 } else { 632 pushIgnoredContainer(qName); 633 } 634 } 635 break; 636 637 default: 638 // treat anything else as a container 639 pushContainer(qName, attributes); 640 break; 641 } 642 } 643 644 private static final String[] CONTEXTS = {"stand-alone", "format"}; 645 private static final String[] WIDTHS = {"wide", "narrow", "abbreviated"}; 646 private static final String[] LENGTHS = {"full", "long", "medium", "short"}; 647 648 private void populateWidthAlias(String type, Set<String> keys) { 649 for (String context : CONTEXTS) { 650 for (String width : WIDTHS) { 651 String keyName = toJDKKey(type+"Width", context, width); 652 if (keyName.length() > 0) { 653 keys.add(keyName + "," + context + "," + width); 654 } 655 } 656 } 657 } 658 659 private void populateFormatLengthAlias(String type, Set<String> keys) { 660 for (String length: LENGTHS) { 661 String keyName = toJDKKey(type+"FormatLength", currentContext, length); 662 if (keyName.length() > 0) { 663 keys.add(keyName + "," + currentContext + "," + length); 664 } 665 } 666 } 667 668 private Set<String> populateAliasKeys(String qName, String context, String width) { 669 HashSet<String> ret = new HashSet<>(); 670 String keyName = qName; 671 672 switch (qName) { 673 case "monthWidth": 674 case "dayWidth": 675 case "quarterWidth": 676 case "dayPeriodWidth": 677 case "dateFormatLength": 678 case "timeFormatLength": 679 case "dateTimeFormatLength": 680 case "eraNames": 681 case "eraAbbr": 682 case "eraNarrow": 683 ret.add(toJDKKey(qName, context, width) + "," + context + "," + width); 684 break; 685 case "days": 686 populateWidthAlias("day", ret); 687 break; 688 case "months": 689 populateWidthAlias("month", ret); 690 break; 691 case "quarters": 692 populateWidthAlias("quarter", ret); 693 break; 694 case "dayPeriods": 695 populateWidthAlias("dayPeriod", ret); 696 break; 697 case "eras": 698 ret.add(toJDKKey("eraNames", context, width) + "," + context + "," + width); 699 ret.add(toJDKKey("eraAbbr", context, width) + "," + context + "," + width); 700 ret.add(toJDKKey("eraNarrow", context, width) + "," + context + "," + width); 701 break; 702 case "dateFormats": 703 populateFormatLengthAlias("date", ret); 704 break; 705 case "timeFormats": 706 populateFormatLengthAlias("time", ret); 707 break; 708 default: 709 break; 710 } 711 return ret; 712 } 713 714 private String translateWidthAlias(String qName, String context, String width) { 715 String keyName = qName; 716 String type = Character.toUpperCase(qName.charAt(0)) + qName.substring(1, qName.indexOf("Width")); 717 718 switch (width) { 719 case "wide": 720 keyName = type + "Names/" + context; 721 break; 722 case "abbreviated": 723 keyName = type + "Abbreviations/" + context; 724 break; 725 case "narrow": 726 keyName = type + "Narrows/" + context; 727 break; 728 default: 729 assert false; 730 } 731 732 return keyName; 733 } 734 735 private String toJDKKey(String containerqName, String context, String type) { 736 String keyName = containerqName; 737 738 switch (containerqName) { 739 case "monthWidth": 740 case "dayWidth": 741 case "quarterWidth": 742 keyName = translateWidthAlias(keyName, context, type); 743 break; 744 case "dayPeriodWidth": 745 switch (type) { 746 case "wide": 747 keyName = "AmPmMarkers/" + context; 748 break; 749 case "narrow": 750 keyName = "narrow.AmPmMarkers/" + context; 751 break; 752 case "abbreviated": 753 keyName = ""; 754 break; 755 } 756 break; 757 case "dateFormatLength": 758 case "timeFormatLength": 759 case "dateTimeFormatLength": 760 keyName = "DateTimePatterns/" + 761 type + "-" + 762 keyName.substring(0, keyName.indexOf("FormatLength")); 763 break; 764 case "eraNames": 765 keyName = "long.Eras"; 766 break; 767 case "eraAbbr": 768 keyName = "Eras"; 769 break; 770 case "eraNarrow": 771 keyName = "narrow.Eras"; 772 break; 773 case "dateFormats": 774 case "timeFormats": 775 case "days": 776 case "months": 777 case "quarters": 778 case "dayPeriods": 779 case "eras": 780 break; 781 default: 782 keyName = ""; 783 break; 784 } 785 786 return keyName; 787 } 788 789 private String getTarget(String path, String calType, String context, String width) { 790 // Target qName 791 int lastSlash = path.lastIndexOf('/'); 792 String qName = path.substring(lastSlash+1); 793 int bracket = qName.indexOf('['); 794 if (bracket != -1) { 795 qName = qName.substring(0, bracket); 796 } 797 798 // calType 799 String typeKey = "/calendar[@type='"; 800 int start = path.indexOf(typeKey); 801 if (start != -1) { 802 calType = path.substring(start+typeKey.length(), path.indexOf("']", start)); 803 } 804 805 // context 806 typeKey = "Context[@type='"; 807 start = path.indexOf(typeKey); 808 if (start != -1) { 809 context = (path.substring(start+typeKey.length(), path.indexOf("']", start))); 810 } 811 812 // width 813 typeKey = "Width[@type='"; 814 start = path.indexOf(typeKey); 815 if (start != -1) { 816 width = path.substring(start+typeKey.length(), path.indexOf("']", start)); 817 } 818 819 return calType + "." + toJDKKey(qName, context, width); 820 } 821 822 @Override 823 public void endElement(String uri, String localName, String qName) throws SAXException { 824 assert qName.equals(currentContainer.getqName()) : "current=" + currentContainer.getqName() + ", param=" + qName; 825 switch (qName) { 826 case "calendar": 827 assert !(currentContainer instanceof Entry); 828 currentCalendarType = null; 829 break; 830 831 case "defaultNumberingSystem": 832 if (currentContainer instanceof StringEntry) { 833 defaultNumberingSystem = ((StringEntry) currentContainer).getValue(); 834 assert defaultNumberingSystem != null; 835 put(((StringEntry) currentContainer).getKey(), defaultNumberingSystem); 836 } else { 837 defaultNumberingSystem = null; 838 } 839 break; 840 841 case "timeZoneNames": 842 zonePrefix = null; 843 break; 844 845 case "generic": 846 case "standard": 847 case "daylight": 848 if (zonePrefix != null && (currentContainer instanceof Entry)) { 849 @SuppressWarnings("unchecked") 850 Map<String, String> valmap = (Map<String, String>) get(zonePrefix + getContainerKey()); 851 Entry<?> entry = (Entry<?>) currentContainer; 852 valmap.put(entry.getKey(), (String) entry.getValue()); 853 } 854 break; 855 856 case "monthWidth": 857 case "dayWidth": 858 case "dayPeriodWidth": 859 case "quarterWidth": 860 currentWidth = ""; 861 putIfEntry(); 862 break; 863 864 case "monthContext": 865 case "dayContext": 866 case "dayPeriodContext": 867 case "quarterContext": 868 currentContext = ""; 869 putIfEntry(); 870 break; 871 872 default: 873 putIfEntry(); 874 } 875 currentContainer = currentContainer.getParent(); 876 } 877 878 private void putIfEntry() { 879 if (currentContainer instanceof AliasEntry) { 880 Entry<?> entry = (Entry<?>) currentContainer; 881 String containerqName = entry.getParent().getqName(); 882 Set<String> keyNames = populateAliasKeys(containerqName, currentContext, currentWidth); 883 if (!keyNames.isEmpty()) { 884 for (String keyName : keyNames) { 885 String[] tmp = keyName.split(",", 3); 886 String calType = currentCalendarType.lname(); 887 String src = calType+"."+tmp[0]; 888 String target = getTarget( 889 entry.getKey(), 890 calType, 891 tmp[1].length()>0 ? tmp[1] : currentContext, 892 tmp[2].length()>0 ? tmp[2] : currentWidth); 893 if (target.substring(target.lastIndexOf('.')+1).equals(containerqName)) { 894 target = target.substring(0, target.indexOf('.'))+"."+tmp[0]; 895 } 896 CLDRConverter.aliases.put(src.replaceFirst("^gregorian.", ""), 897 target.replaceFirst("^gregorian.", "")); 898 } 899 } 900 } else if (currentContainer instanceof Entry) { 901 Entry<?> entry = (Entry<?>) currentContainer; 902 Object value = entry.getValue(); 903 if (value != null) { 904 String key = entry.getKey(); 905 // Tweak for MonthNames for the root locale, Needed for 906 // SimpleDateFormat.format()/parse() roundtrip. 907 if (id.equals("root") && key.startsWith("MonthNames")) { 908 value = new DateFormatSymbols(Locale.US).getShortMonths(); 909 } 910 put(entry.getKey(), value); 911 } 912 } 913 } 914 }