1 package org.unicode.cldr.tool;
2 
3 import java.io.FileNotFoundException;
4 import java.util.Arrays;
5 import java.util.Collections;
6 import java.util.EnumMap;
7 import java.util.EnumSet;
8 import java.util.HashSet;
9 import java.util.LinkedHashSet;
10 import java.util.Map;
11 import java.util.Set;
12 
13 import org.unicode.cldr.util.CLDRConfig;
14 import org.unicode.cldr.util.DtdData;
15 import org.unicode.cldr.util.DtdData.Attribute;
16 import org.unicode.cldr.util.DtdData.Element;
17 import org.unicode.cldr.util.DtdType;
18 import org.unicode.cldr.util.SupplementalDataInfo;
19 
20 import com.ibm.icu.dev.util.CollectionUtilities;
21 
22 public class ShowDtdDiffs {
23     static final SupplementalDataInfo SDI = CLDRConfig.getInstance().getSupplementalDataInfo();
24 
25     static Set<DtdType> TYPES = EnumSet.allOf(DtdType.class);
26     static {
27         TYPES.remove(DtdType.ldmlICU);
28     }
29 
30     static final Map<DtdType, String> FIRST_VERSION = new EnumMap<>(DtdType.class);
31     static {
FIRST_VERSION.put(DtdType.ldmlBCP47, "1.7.2")32         FIRST_VERSION.put(DtdType.ldmlBCP47, "1.7.2");
FIRST_VERSION.put(DtdType.keyboard, "22.1")33         FIRST_VERSION.put(DtdType.keyboard, "22.1");
FIRST_VERSION.put(DtdType.platform, "22.1")34         FIRST_VERSION.put(DtdType.platform, "22.1");
35     }
36 
main(String[] args)37     public static void main(String[] args) {
38         String last = null;
39         for (String current : ToolConstants.CLDR_VERSIONS) {
40             String currentName = current == null ? "trunk" : current;
41             for (DtdType type : TYPES) {
42                 String firstVersion = FIRST_VERSION.get(type);
43                 if (firstVersion != null && current != null && current.compareTo(firstVersion) < 0) {
44                     continue;
45                 }
46                 DtdData dtdCurrent = null;
47                 try {
48                     dtdCurrent = DtdData.getInstance(type, current);
49                 } catch (Exception e) {
50                     if (!(e.getCause() instanceof FileNotFoundException)) {
51                         throw e;
52                     }
53                     System.out.println(e.getMessage() + ", " + e.getCause().getMessage());
54                     continue;
55                 }
56                 DtdData dtdLast = null;
57                 if (last != null) {
58                     try {
59                         dtdLast = DtdData.getInstance(type, last);
60                     } catch (Exception e) {
61                         if (!(e.getCause() instanceof FileNotFoundException)) {
62                             throw e;
63                         }
64                     }
65                 }
66                 diff(currentName + "\t" + type, dtdLast, dtdCurrent);
67             }
68             last = current;
69         }
70     }
71 
diff(String prefix, DtdData dtdLast, DtdData dtdCurrent)72     private static void diff(String prefix, DtdData dtdLast, DtdData dtdCurrent) {
73         Map<String, Element> oldNameToElement = dtdLast == null ? Collections.EMPTY_MAP : dtdLast.getElementFromName();
74         checkNames(prefix, dtdCurrent, oldNameToElement, "/", dtdCurrent.ROOT, new HashSet<Element>());
75     }
76 
checkNames(String prefix, DtdData dtdCurrent, Map<String, Element> oldNameToElement, String path, Element element, HashSet<Element> seen)77     private static void checkNames(String prefix, DtdData dtdCurrent, Map<String, Element> oldNameToElement, String path, Element element,
78         HashSet<Element> seen) {
79         if (seen.contains(element)) {
80             return;
81         }
82         seen.add(element);
83         String name = element.getName();
84         if (SKIP_ELEMENTS.contains(name)) {
85             return;
86         }
87 
88         if (isDeprecated(dtdCurrent.dtdType, name, "*")) { // SDI.isDeprecated(dtdCurrent.dtdType, name, "*", "*")) {
89             return;
90         }
91         String newPath = path + "/" + element.name;
92         if (!oldNameToElement.containsKey(name)) {
93             String attributeNames = getAttributeNames(dtdCurrent, name, Collections.EMPTY_MAP, element.getAttributes());
94             System.out.println(prefix + "\tElement\t" + newPath + "\t" + attributeNames);
95         } else {
96             Element oldElement = oldNameToElement.get(name);
97             String attributeNames = getAttributeNames(dtdCurrent, name, oldElement.getAttributes(), element.getAttributes());
98             if (!attributeNames.isEmpty()) {
99                 System.out.println(prefix + "\tAttribute\t" + newPath + "\t" + attributeNames);
100             }
101         }
102         for (Element child : element.getChildren().keySet()) {
103             checkNames(prefix, dtdCurrent, oldNameToElement, newPath, child, seen);
104         }
105     }
106 
107 //    static class Parents {
108 //        final DtdData dtd;
109 //        final Relation<String,String> childToParents = Relation.of(new HashMap(), HashSet.class);
110 //        static Map<DtdData,Parents> cache = new ConcurrentHashMap<>();
111 //
112 //        public Parents(DtdData dtd) {
113 //           this.dtd = dtd;
114 //        }
115 //
116 //        static Parents getInstance(DtdData dtd) {
117 //            Parents result = cache.get(dtd);
118 //            if (result == null) {
119 //                result = new Parents(dtd);
120 //                result.addParents(dtd.ROOT, new HashSet<Element>());
121 //                result.childToParents.freeze();
122 //            }
123 //            return result;
124 //        }
125 //
126 //        private void addParents(Element element, HashSet<Element> seen) {
127 //            if (!seen.contains(element)) {
128 //                for (Element child : element.getChildren().keySet()) {
129 //                    childToParents.put(child.name, element.name);
130 //                    addParents(child, seen);
131 //                }
132 //            }
133 //        }
134 //    }
135 
136     static final Set<String> SKIP_ELEMENTS = new HashSet<>(Arrays.asList("generation", "identity", "alias", "special", "telephoneCodeData"));
137     static final Set<String> SKIP_ATTRIBUTES = new HashSet<>(Arrays.asList("references", "standard", "draft", "alt"));
138 
getAttributeNames(DtdData dtdCurrent, String elementName, Map<Attribute, Integer> attributesOld, Map<Attribute, Integer> attributes)139     private static String getAttributeNames(DtdData dtdCurrent, String elementName, Map<Attribute, Integer> attributesOld, Map<Attribute, Integer> attributes) {
140         Set<String> names = new LinkedHashSet<>();
141         main: for (Attribute attribute : attributes.keySet()) {
142             String name = attribute.getName();
143             if (SKIP_ATTRIBUTES.contains(name)) {
144                 continue;
145             }
146             if (isDeprecated(dtdCurrent.dtdType, elementName, name)) { // SDI.isDeprecated(dtdCurrent, elementName, name, "*")) {
147                 continue;
148             }
149             for (Attribute attributeOld : attributesOld.keySet()) {
150                 if (attributeOld.name.equals(name)) {
151                     continue main;
152                 }
153             }
154             names.add(name);
155         }
156         return names.isEmpty() ? "" : CollectionUtilities.join(names, ", ");
157     }
158 
isDeprecated(DtdType dtdType, String elementName, String attributeName)159     private static boolean isDeprecated(DtdType dtdType, String elementName, String attributeName) {
160         try {
161             return DtdData.getInstance(dtdType).isDeprecated(elementName, attributeName, "*");
162         } catch (DtdData.IllegalByDtdException e) {
163             return true;
164         }
165     }
166 }