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 //
486 zoneNameStyle = "long";
487 pushContainer(qName, attributes);
488 break;
489 case "short":
490 zoneNameStyle = "short";
491 pushContainer(qName, attributes);
492 break;
493 case "generic": // generic name
494 case "standard": // standard time name
495 case "daylight": // daylight saving (summer) time name
496 pushStringEntry(qName, attributes, CLDRConverter.ZONE_NAME_PREFIX + qName + "." + zoneNameStyle);
497 break;
498 case "exemplarCity":
499 pushStringEntry(qName, attributes, CLDRConverter.EXEMPLAR_CITY_PREFIX);
500 break;
501
502 //
503 // Number format information
504 //
505 case "decimalFormatLength":
506 if (attributes.getValue("type") == null) {
507 // skipping type="short" data
508 // for FormatData
509 // copy string for later assembly into NumberPatterns
510 pushStringEntry(qName, attributes, "NumberPatterns/decimal");
511 } else {
512 pushIgnoredContainer(qName);
513 }
514 break;
515 case "currencyFormatLength":
516 if (attributes.getValue("type") == null) {
517 // skipping type="short" data
518 // for FormatData
519 pushContainer(qName, attributes);
520 } else {
521 pushIgnoredContainer(qName);
522 }
523 break;
524 case "currencyFormat":
525 // for FormatData
526 // copy string for later assembly into NumberPatterns
527 if (attributes.getValue("type").equals("standard")) {
528 pushStringEntry(qName, attributes, "NumberPatterns/currency");
529 } else {
530 pushIgnoredContainer(qName);
531 }
532 break;
659 case "dateTimeFormatLength":
660 {
661 // for FormatData
662 // copy string for later assembly into DateTimePatterns
663 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName();
664 pushStringEntry(qName, attributes, prefix + "DateTimePatterns/" + attributes.getValue("type") + "-dateTime");
665 }
666 break;
667 case "localizedPatternChars":
668 {
669 // for FormatData
670 // copy string for later adaptation to JRE use
671 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName();
672 pushStringEntry(qName, attributes, prefix + "DateTimePatternChars");
673 }
674 break;
675
676 // "alias" for root
677 case "alias":
678 {
679 if (id.equals("root") &&
680 !isIgnored(attributes) &&
681 currentCalendarType != null &&
682 !currentCalendarType.lname().startsWith("islamic-")) { // ignore Islamic variants
683 pushAliasEntry(qName, attributes, attributes.getValue("path"));
684 } else {
685 pushIgnoredContainer(qName);
686 }
687 }
688 break;
689
690 default:
691 // treat anything else as a container
692 pushContainer(qName, attributes);
693 break;
694 }
695 }
696
697 private static final String[] CONTEXTS = {"stand-alone", "format"};
698 private static final String[] WIDTHS = {"wide", "narrow", "abbreviated"};
699 private static final String[] LENGTHS = {"full", "long", "medium", "short"};
700
701 private void populateWidthAlias(String type, Set<String> keys) {
702 for (String context : CONTEXTS) {
814 type + "-" +
815 keyName.substring(0, keyName.indexOf("FormatLength"));
816 break;
817 case "eraNames":
818 keyName = "long.Eras";
819 break;
820 case "eraAbbr":
821 keyName = "Eras";
822 break;
823 case "eraNarrow":
824 keyName = "narrow.Eras";
825 break;
826 case "dateFormats":
827 case "timeFormats":
828 case "days":
829 case "months":
830 case "quarters":
831 case "dayPeriods":
832 case "eras":
833 break;
834 default:
835 keyName = "";
836 break;
837 }
838
839 return keyName;
840 }
841
842 private String getTarget(String path, String calType, String context, String width) {
843 // Target qName
844 int lastSlash = path.lastIndexOf('/');
845 String qName = path.substring(lastSlash+1);
846 int bracket = qName.indexOf('[');
847 if (bracket != -1) {
848 qName = qName.substring(0, bracket);
849 }
850
851 // calType
852 String typeKey = "/calendar[@type='";
853 int start = path.indexOf(typeKey);
854 if (start != -1) {
855 calType = path.substring(start+typeKey.length(), path.indexOf("']", start));
856 }
857
858 // context
859 typeKey = "Context[@type='";
860 start = path.indexOf(typeKey);
861 if (start != -1) {
862 context = (path.substring(start+typeKey.length(), path.indexOf("']", start)));
863 }
864
865 // width
866 typeKey = "Width[@type='";
867 start = path.indexOf(typeKey);
868 if (start != -1) {
869 width = path.substring(start+typeKey.length(), path.indexOf("']", start));
870 }
871
872 return calType + "." + toJDKKey(qName, context, width);
873 }
874
875 @Override
876 public void endElement(String uri, String localName, String qName) throws SAXException {
877 assert qName.equals(currentContainer.getqName()) : "current=" + currentContainer.getqName() + ", param=" + qName;
878 switch (qName) {
879 case "calendar":
880 assert !(currentContainer instanceof Entry);
881 currentCalendarType = null;
882 break;
883
884 case "defaultNumberingSystem":
885 if (currentContainer instanceof StringEntry) {
886 defaultNumberingSystem = ((StringEntry) currentContainer).getValue();
887 assert defaultNumberingSystem != null;
888 put(((StringEntry) currentContainer).getKey(), defaultNumberingSystem);
889 } else {
890 defaultNumberingSystem = null;
891 }
909 valmap.put(entry.getKey(), (String) entry.getValue());
910 }
911 }
912 break;
913
914 case "monthWidth":
915 case "dayWidth":
916 case "dayPeriodWidth":
917 case "quarterWidth":
918 currentWidth = "";
919 putIfEntry();
920 break;
921
922 case "monthContext":
923 case "dayContext":
924 case "dayPeriodContext":
925 case "quarterContext":
926 currentContext = "";
927 putIfEntry();
928 break;
929
930 default:
931 putIfEntry();
932 }
933 currentContainer = currentContainer.getParent();
934 }
935
936 private void putIfEntry() {
937 if (currentContainer instanceof AliasEntry) {
938 Entry<?> entry = (Entry<?>) currentContainer;
939 String containerqName = entry.getParent().getqName();
940 Set<String> keyNames = populateAliasKeys(containerqName, currentContext, currentWidth);
941 if (!keyNames.isEmpty()) {
942 for (String keyName : keyNames) {
943 String[] tmp = keyName.split(",", 3);
944 String calType = currentCalendarType.lname();
945 String src = calType+"."+tmp[0];
946 String target = getTarget(
947 entry.getKey(),
948 calType,
949 tmp[1].length()>0 ? tmp[1] : currentContext,
950 tmp[2].length()>0 ? tmp[2] : currentWidth);
951 if (target.substring(target.lastIndexOf('.')+1).equals(containerqName)) {
952 target = target.substring(0, target.indexOf('.'))+"."+tmp[0];
953 }
954 CLDRConverter.aliases.put(src.replaceFirst("^gregorian.", ""),
955 target.replaceFirst("^gregorian.", ""));
956 }
957 }
958 } else if (currentContainer instanceof Entry) {
959 Entry<?> entry = (Entry<?>) currentContainer;
960 Object value = entry.getValue();
961 if (value != null) {
962 String key = entry.getKey();
963 // Tweak for MonthNames for the root locale, Needed for
964 // SimpleDateFormat.format()/parse() roundtrip.
965 if (id.equals("root") && key.startsWith("MonthNames")) {
966 value = new DateFormatSymbols(Locale.US).getShortMonths();
967 }
968 put(entry.getKey(), value);
969 }
970 }
971 }
972
973 public String convertOldKeyName(String key) {
974 // Explicitly obtained from "alias" attribute in each "key" element.
975 switch (key) {
|
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 private String currentStyle = ""; // short, long for decimalFormat
56 private String compactCount = ""; // one or other for decimalFormat
57
58 LDMLParseHandler(String id) {
59 this.id = id;
60 }
61
62 @Override
63 public InputSource resolveEntity(String publicID, String systemID) throws IOException, SAXException {
64 // avoid HTTP traffic to unicode.org
65 if (systemID.startsWith(CLDRConverter.LDML_DTD_SYSTEM_ID)) {
66 return new InputSource((new File(CLDRConverter.LOCAL_LDML_DTD)).toURI().toString());
67 }
68 return null;
69 }
70
71 @Override
72 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
73 switch (qName) {
74 //
75 // Generic information
76 //
488 zoneNameStyle = "long";
489 pushContainer(qName, attributes);
490 break;
491 case "short":
492 zoneNameStyle = "short";
493 pushContainer(qName, attributes);
494 break;
495 case "generic": // generic name
496 case "standard": // standard time name
497 case "daylight": // daylight saving (summer) time name
498 pushStringEntry(qName, attributes, CLDRConverter.ZONE_NAME_PREFIX + qName + "." + zoneNameStyle);
499 break;
500 case "exemplarCity":
501 pushStringEntry(qName, attributes, CLDRConverter.EXEMPLAR_CITY_PREFIX);
502 break;
503
504 //
505 // Number format information
506 //
507 case "decimalFormatLength":
508 String type = attributes.getValue("type");
509 if (null == type) {
510 // format data for decimal number format
511 pushStringEntry(qName, attributes, "NumberPatterns/decimal");
512 currentStyle = type;
513 } else {
514 switch (type) {
515 case "short":
516 case "long":
517 // considering "short" and long for
518 // compact number formatting patterns
519 pushKeyContainer(qName, attributes, type);
520 currentStyle = type;
521 break;
522 default:
523 pushIgnoredContainer(qName);
524 break;
525 }
526 }
527 break;
528 case "decimalFormat":
529 if(currentStyle == null) {
530 pushContainer(qName, attributes);
531 } else {
532 switch (currentStyle) {
533 case "short":
534 pushStringListEntry(qName, attributes,
535 currentStyle+".CompactNumberPatterns");
536 break;
537 case "long":
538 pushStringListEntry(qName, attributes,
539 currentStyle+".CompactNumberPatterns");
540 break;
541 default:
542 pushIgnoredContainer(qName);
543 break;
544 }
545 }
546 break;
547 case "pattern":
548 String containerName = currentContainer.getqName();
549 if (containerName.equals("decimalFormat")) {
550 if (currentStyle == null) {
551 pushContainer(qName, attributes);
552 } else {
553 // The compact number patterns parsing assumes that the order
554 // of patterns are always in the increasing order of their
555 // type attribute i.e. type = 1000...
556 // Between the inflectional forms for a type (e.g.
557 // count = "one" and count = "other" for type = 1000), it is
558 // assumed that the count = "one" always appears before
559 // count = "other"
560 switch (currentStyle) {
561 case "short":
562 case "long":
563 String count = attributes.getValue("count");
564 // first pattern of count = "one" or count = "other"
565 if ((count.equals("one") || count.equals("other"))
566 && compactCount.equals("")) {
567 compactCount = count;
568 pushStringListElement(qName, attributes,
569 (int) Math.log10(Double.parseDouble(attributes.getValue("type"))));
570 } else if ((count.equals("one") || count.equals("other"))
571 && compactCount.equals(count)) {
572 // extract patterns with similar "count"
573 // attribute value
574 pushStringListElement(qName, attributes,
575 (int) Math.log10(Double.parseDouble(attributes.getValue("type"))));
576 } else {
577 pushIgnoredContainer(qName);
578 }
579 break;
580 default:
581 pushIgnoredContainer(qName);
582 break;
583 }
584 }
585 } else {
586 pushContainer(qName, attributes);
587 }
588 break;
589 case "currencyFormatLength":
590 if (attributes.getValue("type") == null) {
591 // skipping type="short" data
592 // for FormatData
593 pushContainer(qName, attributes);
594 } else {
595 pushIgnoredContainer(qName);
596 }
597 break;
598 case "currencyFormat":
599 // for FormatData
600 // copy string for later assembly into NumberPatterns
601 if (attributes.getValue("type").equals("standard")) {
602 pushStringEntry(qName, attributes, "NumberPatterns/currency");
603 } else {
604 pushIgnoredContainer(qName);
605 }
606 break;
733 case "dateTimeFormatLength":
734 {
735 // for FormatData
736 // copy string for later assembly into DateTimePatterns
737 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName();
738 pushStringEntry(qName, attributes, prefix + "DateTimePatterns/" + attributes.getValue("type") + "-dateTime");
739 }
740 break;
741 case "localizedPatternChars":
742 {
743 // for FormatData
744 // copy string for later adaptation to JRE use
745 String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName();
746 pushStringEntry(qName, attributes, prefix + "DateTimePatternChars");
747 }
748 break;
749
750 // "alias" for root
751 case "alias":
752 {
753 if (id.equals("root") && !isIgnored(attributes)
754 && ((currentContainer.getqName().equals("decimalFormatLength"))
755 || (currentCalendarType != null && !currentCalendarType.lname().startsWith("islamic-")))) { // ignore islamic variants
756 pushAliasEntry(qName, attributes, attributes.getValue("path"));
757 } else {
758 pushIgnoredContainer(qName);
759 }
760 }
761 break;
762
763 default:
764 // treat anything else as a container
765 pushContainer(qName, attributes);
766 break;
767 }
768 }
769
770 private static final String[] CONTEXTS = {"stand-alone", "format"};
771 private static final String[] WIDTHS = {"wide", "narrow", "abbreviated"};
772 private static final String[] LENGTHS = {"full", "long", "medium", "short"};
773
774 private void populateWidthAlias(String type, Set<String> keys) {
775 for (String context : CONTEXTS) {
887 type + "-" +
888 keyName.substring(0, keyName.indexOf("FormatLength"));
889 break;
890 case "eraNames":
891 keyName = "long.Eras";
892 break;
893 case "eraAbbr":
894 keyName = "Eras";
895 break;
896 case "eraNarrow":
897 keyName = "narrow.Eras";
898 break;
899 case "dateFormats":
900 case "timeFormats":
901 case "days":
902 case "months":
903 case "quarters":
904 case "dayPeriods":
905 case "eras":
906 break;
907 case "decimalFormatLength": // used for compact number formatting patterns
908 keyName = type + ".CompactNumberPatterns";
909 break;
910 default:
911 keyName = "";
912 break;
913 }
914
915 return keyName;
916 }
917
918 private String getTarget(String path, String calType, String context, String width) {
919 // Target qName
920 int lastSlash = path.lastIndexOf('/');
921 String qName = path.substring(lastSlash+1);
922 int bracket = qName.indexOf('[');
923 if (bracket != -1) {
924 qName = qName.substring(0, bracket);
925 }
926
927 // calType
928 String typeKey = "/calendar[@type='";
929 int start = path.indexOf(typeKey);
930 if (start != -1) {
931 calType = path.substring(start+typeKey.length(), path.indexOf("']", start));
932 }
933
934 // context
935 typeKey = "Context[@type='";
936 start = path.indexOf(typeKey);
937 if (start != -1) {
938 context = (path.substring(start+typeKey.length(), path.indexOf("']", start)));
939 }
940
941 // width
942 typeKey = "Width[@type='";
943 start = path.indexOf(typeKey);
944 if (start != -1) {
945 width = path.substring(start+typeKey.length(), path.indexOf("']", start));
946 }
947
948 // used for compact number formatting patterns aliases
949 typeKey = "decimalFormatLength[@type='";
950 start = path.indexOf(typeKey);
951 if (start != -1) {
952 String style = path.substring(start + typeKey.length(), path.indexOf("']", start));
953 return toJDKKey(qName, "", style);
954 }
955
956 return calType + "." + toJDKKey(qName, context, width);
957 }
958
959 @Override
960 public void endElement(String uri, String localName, String qName) throws SAXException {
961 assert qName.equals(currentContainer.getqName()) : "current=" + currentContainer.getqName() + ", param=" + qName;
962 switch (qName) {
963 case "calendar":
964 assert !(currentContainer instanceof Entry);
965 currentCalendarType = null;
966 break;
967
968 case "defaultNumberingSystem":
969 if (currentContainer instanceof StringEntry) {
970 defaultNumberingSystem = ((StringEntry) currentContainer).getValue();
971 assert defaultNumberingSystem != null;
972 put(((StringEntry) currentContainer).getKey(), defaultNumberingSystem);
973 } else {
974 defaultNumberingSystem = null;
975 }
993 valmap.put(entry.getKey(), (String) entry.getValue());
994 }
995 }
996 break;
997
998 case "monthWidth":
999 case "dayWidth":
1000 case "dayPeriodWidth":
1001 case "quarterWidth":
1002 currentWidth = "";
1003 putIfEntry();
1004 break;
1005
1006 case "monthContext":
1007 case "dayContext":
1008 case "dayPeriodContext":
1009 case "quarterContext":
1010 currentContext = "";
1011 putIfEntry();
1012 break;
1013 case "decimalFormatLength":
1014 currentStyle = "";
1015 compactCount = "";
1016 putIfEntry();
1017 break;
1018 default:
1019 putIfEntry();
1020 }
1021 currentContainer = currentContainer.getParent();
1022 }
1023
1024 private void putIfEntry() {
1025 if (currentContainer instanceof AliasEntry) {
1026 Entry<?> entry = (Entry<?>) currentContainer;
1027 String containerqName = entry.getParent().getqName();
1028 if (containerqName.equals("decimalFormatLength")) {
1029 String srcKey = toJDKKey(containerqName, "", currentStyle);
1030 String targetKey = getTarget(entry.getKey(), "", "", "");
1031 CLDRConverter.aliases.put(srcKey, targetKey);
1032 } else {
1033 Set<String> keyNames = populateAliasKeys(containerqName, currentContext, currentWidth);
1034 if (!keyNames.isEmpty()) {
1035 for (String keyName : keyNames) {
1036 String[] tmp = keyName.split(",", 3);
1037 String calType = currentCalendarType.lname();
1038 String src = calType+"."+tmp[0];
1039 String target = getTarget(
1040 entry.getKey(),
1041 calType,
1042 tmp[1].length()>0 ? tmp[1] : currentContext,
1043 tmp[2].length()>0 ? tmp[2] : currentWidth);
1044 if (target.substring(target.lastIndexOf('.')+1).equals(containerqName)) {
1045 target = target.substring(0, target.indexOf('.'))+"."+tmp[0];
1046 }
1047 CLDRConverter.aliases.put(src.replaceFirst("^gregorian.", ""),
1048 target.replaceFirst("^gregorian.", ""));
1049 }
1050 }
1051 }
1052 } else if (currentContainer instanceof Entry) {
1053 Entry<?> entry = (Entry<?>) currentContainer;
1054 Object value = entry.getValue();
1055 if (value != null) {
1056 String key = entry.getKey();
1057 // Tweak for MonthNames for the root locale, Needed for
1058 // SimpleDateFormat.format()/parse() roundtrip.
1059 if (id.equals("root") && key.startsWith("MonthNames")) {
1060 value = new DateFormatSymbols(Locale.US).getShortMonths();
1061 }
1062 put(entry.getKey(), value);
1063 }
1064 }
1065 }
1066
1067 public String convertOldKeyName(String key) {
1068 // Explicitly obtained from "alias" attribute in each "key" element.
1069 switch (key) {
|