• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.unicode.cldr.tool;
2 
3 import java.io.FileNotFoundException;
4 import java.io.IOException;
5 import java.util.ArrayList;
6 import java.util.Collections;
7 import java.util.EnumMap;
8 import java.util.EnumSet;
9 import java.util.HashSet;
10 import java.util.LinkedHashSet;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.Set;
14 
15 import org.unicode.cldr.util.CLDRConfig;
16 import org.unicode.cldr.util.CldrUtility;
17 import org.unicode.cldr.util.DtdData;
18 import org.unicode.cldr.util.DtdData.Attribute;
19 import org.unicode.cldr.util.DtdData.Element;
20 import org.unicode.cldr.util.DtdType;
21 import org.unicode.cldr.util.SupplementalDataInfo;
22 
23 import com.google.common.base.MoreObjects;
24 import com.google.common.collect.ImmutableMultimap;
25 import com.google.common.collect.ImmutableSet;
26 import com.google.common.collect.Multimap;
27 import com.ibm.icu.dev.util.CollectionUtilities;
28 import com.ibm.icu.impl.Utility;
29 import com.ibm.icu.util.VersionInfo;
30 
31 /**
32  * Changed ShowDtdDiffs into a chart.
33  * @author markdavis
34  */
35 public class ChartDtdDelta extends Chart {
36 
37     private static final String DEPRECATED_PREFIX = "⊖";
38 
39     private static final String NEW_PREFIX = "+";
40 
41     private static final Set<String> OMITTED_ATTRIBUTES = Collections.singleton("⊕");
42 
main(String[] args)43     public static void main(String[] args) {
44         new ChartDtdDelta().writeChart(null);
45     }
46 
47     @Override
getDirectory()48     public String getDirectory() {
49         return FormattedFileWriter.CHART_TARGET_DIR;
50     }
51 
52     @Override
getTitle()53     public String getTitle() {
54         return "DTD Deltas";
55     }
56 
57     @Override
getExplanation()58     public String getExplanation() {
59         return "<p>Shows changes to the LDML dtds over time. "
60             + "New elements or attributes are indicated with a + sign, and newly deprecated ones with a ⊖ sign. "
61             + "Element attributes are abbreviated as ⊕ if where is no change to them, but the element is newly the child of another. "
62             + "<p>";
63     }
64 
65     @Override
writeContents(FormattedFileWriter pw)66     public void writeContents(FormattedFileWriter pw) throws IOException {
67         TablePrinter tablePrinter = new TablePrinter()
68             .addColumn("Version", "class='source'", CldrUtility.getDoubleLinkMsg(), "class='source'", true)
69             .setSortPriority(0)
70             .setSortAscending(false)
71             .setBreakSpans(true)
72             .addColumn("Dtd Type", "class='source'", null, "class='source'", true)
73             .setSortPriority(1)
74 
75             .addColumn("Intermediate Path", "class='source'", null, "class='target'", true)
76             .setSortPriority(2)
77 
78             .addColumn("Element", "class='target'", null, "class='target'", true)
79             .setSpanRows(false)
80             .addColumn("Attributes", "class='target'", null, "class='target'", true)
81             .setSpanRows(false);
82 
83         String last = null;
84         LinkedHashSet<String> allVersions = new LinkedHashSet<>(ToolConstants.CLDR_VERSIONS);
85         allVersions.add(ToolConstants.LAST_CHART_VERSION);
86         for (String current : allVersions) {
87             System.out.println("DTD delta: " + current);
88             final boolean finalVersion = current.equals(ToolConstants.LAST_CHART_VERSION);
89             String currentName = finalVersion ? ToolConstants.CHART_DISPLAY_VERSION : current;
90             for (DtdType type : TYPES) {
91                 String firstVersion = type.firstVersion; // FIRST_VERSION.get(type);
92                 if (firstVersion != null && current != null && current.compareTo(firstVersion) < 0) {
93                     continue;
94                 }
95                 DtdData dtdCurrent = null;
96                 try {
97                     dtdCurrent = DtdData.getInstance(type,
98                         finalVersion && ToolConstants.CHART_STATUS != ToolConstants.ChartStatus.release ? null : current);
99                 } catch (Exception e) {
100                     if (!(e.getCause() instanceof FileNotFoundException)) {
101                         throw e;
102                     }
103                     System.out.println(e.getMessage() + ", " + e.getCause().getMessage());
104                     continue;
105                 }
106                 DtdData dtdLast = null;
107                 if (last != null) {
108                     try {
109                         dtdLast = DtdData.getInstance(type, last);
110                     } catch (Exception e) {
111                         if (!(e.getCause() instanceof FileNotFoundException)) {
112                             throw e;
113                         }
114                     }
115                 }
116                 diff(currentName, dtdLast, dtdCurrent);
117             }
118             last = current;
119         }
120 
121         for (DiffElement datum : data) {
122             tablePrinter.addRow()
123             .addCell(datum.getVersionString())
124             .addCell(datum.dtdType)
125             .addCell(datum.newPath)
126             .addCell(datum.newElement)
127             .addCell(datum.attributeNames)
128             .finishRow();
129         }
130         pw.write(tablePrinter.toTable());
131         pw.write(Utility.repeat("<br>", 50));
132     }
133 
134     static final String NONE = " ";
135 
136     static final SupplementalDataInfo SDI = CLDRConfig.getInstance().getSupplementalDataInfo();
137 
138     static Set<DtdType> TYPES = EnumSet.allOf(DtdType.class);
139     static {
140         TYPES.remove(DtdType.ldmlICU);
141     }
142 
143     static final Map<DtdType, String> FIRST_VERSION = new EnumMap<>(DtdType.class);
144     static {
FIRST_VERSION.put(DtdType.ldmlBCP47, "1.7.2")145         FIRST_VERSION.put(DtdType.ldmlBCP47, "1.7.2");
FIRST_VERSION.put(DtdType.keyboard, "22.1")146         FIRST_VERSION.put(DtdType.keyboard, "22.1");
FIRST_VERSION.put(DtdType.platform, "22.1")147         FIRST_VERSION.put(DtdType.platform, "22.1");
148     }
149 
diff(String prefix, DtdData dtdLast, DtdData dtdCurrent)150     private void diff(String prefix, DtdData dtdLast, DtdData dtdCurrent) {
151         Map<String, Element> oldNameToElement = dtdLast == null ? Collections.emptyMap() : dtdLast.getElementFromName();
152         checkNames(prefix, dtdCurrent, dtdLast, oldNameToElement, "/", dtdCurrent.ROOT, new HashSet<Element>(), false);
153     }
154 
155     static final DtdType DEBUG_DTD = null; // set to enable
156     static final String DEBUG_ELEMENT = "lias";
157     static final boolean SHOW = false;
158 
159     @SuppressWarnings("unused")
checkNames(String version, DtdData dtdCurrent, DtdData dtdLast, Map<String, Element> oldNameToElement, String path, Element element, HashSet<Element> seen, boolean showAnyway)160     private void checkNames(String version, DtdData dtdCurrent, DtdData dtdLast, Map<String, Element> oldNameToElement, String path, Element element,
161         HashSet<Element> seen, boolean showAnyway) {
162         String name = element.getName();
163 
164         if (SKIP_ELEMENTS.contains(name)) {
165             return;
166         }
167         if (SKIP_TYPE_ELEMENTS.containsEntry(dtdCurrent.dtdType, name)) {
168             return;
169         }
170 
171         String newPath = path + "/" + element.name;
172 
173         // if an element is newly a child of another but has already been seen, you'll have special indication
174         if (seen.contains(element)) {
175             if (showAnyway) {
176                 addData(dtdCurrent, NEW_PREFIX + name, version, newPath, OMITTED_ATTRIBUTES);
177             }
178             return;
179         }
180 
181         seen.add(element);
182         if (SHOW && ToolConstants.CHART_DISPLAY_VERSION.equals(version)) {
183             System.out.println(dtdCurrent.dtdType + "\t" + name);
184         }
185         if (DEBUG_DTD == dtdCurrent.dtdType && name.contains(DEBUG_ELEMENT)) {
186             int debug = 0;
187         }
188 
189 
190         Element oldElement = null;
191 
192         if (!oldNameToElement.containsKey(name)) {
193             Set<String> attributeNames = getAttributeNames(dtdCurrent, dtdLast, name, Collections.emptyMap(), element.getAttributes());
194             addData(dtdCurrent, NEW_PREFIX + name, version, newPath, attributeNames);
195         } else {
196             oldElement = oldNameToElement.get(name);
197             Set<String> attributeNames = getAttributeNames(dtdCurrent, dtdLast, name, oldElement.getAttributes(), element.getAttributes());
198             boolean currentDeprecated = element.isDeprecated();
199             boolean lastDeprecated = dtdLast == null ? false : oldElement.isDeprecated(); //  + (currentDeprecated ? "ⓓ" : "")
200             boolean newlyDeprecated = currentDeprecated && !lastDeprecated;
201             if (newlyDeprecated) {
202                 addData(dtdCurrent, DEPRECATED_PREFIX + name, version, newPath, Collections.emptySet());
203             }
204             if (!attributeNames.isEmpty()) {
205                 addData(dtdCurrent, (newlyDeprecated ? DEPRECATED_PREFIX : "") + name, version, newPath, attributeNames);
206             }
207         }
208         if (element.getName().equals("coordinateUnit")) {
209             System.out.println(version + "\toordinateUnit\t" + element.getChildren().keySet());
210         }
211         Set<Element> oldChildren = oldElement == null ? Collections.emptySet() : oldElement.getChildren().keySet();
212         for (Element child : element.getChildren().keySet()) {
213             showAnyway = true;
214             for (Element oldChild : oldChildren) {
215                 if (oldChild.getName().equals(child.getName())) {
216                     showAnyway = false;
217                     break;
218                 }
219             }
220             checkNames(version, dtdCurrent, dtdLast, oldNameToElement, newPath, child, seen, showAnyway);
221         }
222     }
223 
224     enum DiffType {
225         Element, Attribute, AttributeValue
226     }
227 
228     private static class DiffElement {
229 
230         final VersionInfo version;
231         final DtdType dtdType;
232         final boolean isBeta;
233         final String newPath;
234         final String newElement;
235         final String attributeNames;
236 
DiffElement(DtdData dtdCurrent, String version, String newPath, String newElement, Set<String> attributeNames2)237         public DiffElement(DtdData dtdCurrent, String version, String newPath, String newElement, Set<String> attributeNames2) {
238             isBeta = version.endsWith("β");
239             try {
240                 this.version = isBeta ? VersionInfo.getInstance(version.substring(0, version.length() - 1)) : VersionInfo.getInstance(version);
241             } catch (Exception e) {
242                 e.printStackTrace();
243                 throw e;
244             }
245             dtdType = dtdCurrent.dtdType;
246             this.newPath = fix(newPath);
247             this.attributeNames = attributeNames2.isEmpty() ? NONE : CollectionUtilities.join(attributeNames2, ", ");
248             this.newElement = newElement;
249         }
250 
fix(String substring)251         private String fix(String substring) {
252             int base = substring.indexOf('/', 2);
253             if (base < 0) return "";
254             int last = substring.lastIndexOf('/');
255             if (last <= base) return "/";
256             substring = substring.substring(base, last);
257             return substring.replace("/", "\u200B/") + "/";
258         }
259 
260         @Override
toString()261         public String toString() {
262             return MoreObjects.toStringHelper(this)
263                 .add("version", getVersionString())
264                 .add("dtdType", dtdType)
265                 .add("newPath", newPath)
266                 .add("newElement", newElement)
267                 .add("attributeNames", attributeNames)
268                 .toString();
269         }
270 
getVersionString()271         private String getVersionString() {
272             return version.getVersionString(2, 4) + (isBeta ? "β" : "");
273         }
274     }
275 
276     List<DiffElement> data = new ArrayList<>();
277 
addData(DtdData dtdCurrent, String element, String prefix, String newPath, Set<String> attributeNames)278     private void addData(DtdData dtdCurrent, String element, String prefix, String newPath, Set<String> attributeNames) {
279         DiffElement item = new DiffElement(dtdCurrent, prefix, newPath, element, attributeNames);
280         data.add(item);
281     }
282 
283     static final Set<String> SKIP_ELEMENTS = ImmutableSet.of("generation", "identity", "special"); // , "telephoneCodeData"
284 
285     static final Multimap<DtdType, String> SKIP_TYPE_ELEMENTS = ImmutableMultimap.of(DtdType.ldml, "alias");
286 
287     static final Set<String> SKIP_ATTRIBUTES = ImmutableSet.of("references", "standard", "draft", "alt");
288 
getAttributeNames(DtdData dtdCurrent, DtdData dtdLast, String elementName, Map<Attribute, Integer> attributesOld, Map<Attribute, Integer> attributes)289     private static Set<String> getAttributeNames(DtdData dtdCurrent, DtdData dtdLast, String elementName,
290         Map<Attribute, Integer> attributesOld,
291         Map<Attribute, Integer> attributes) {
292         Set<String> names = new LinkedHashSet<>();
293         if (elementName.equals("coordinateUnit")) {
294             int debug = 0;
295         }
296 
297         main:
298             // we want to add a name that is new or that becomes deprecated
299             for (Attribute attribute : attributes.keySet()) {
300                 String name = attribute.getName();
301                 if (SKIP_ATTRIBUTES.contains(name)) {
302                     continue;
303                 }
304                 String display = NEW_PREFIX + name;
305 //            if (isDeprecated(dtdCurrent, elementName, name)) { // SDI.isDeprecated(dtdCurrent, elementName, name, "*")) {
306 //                continue;
307 //            }
308                 for (Attribute attributeOld : attributesOld.keySet()) {
309                     if (attributeOld.name.equals(name)) {
310                         if (attribute.isDeprecated() && !attributeOld.isDeprecated()) {
311                             display = DEPRECATED_PREFIX + name;
312                         } else {
313                             continue main;
314                         }
315                     }
316                 }
317                 names.add(display);
318             }
319         return names;
320     }
321 
322 //    private static boolean isDeprecated(DtdData dtdCurrent, String elementName, String attributeName) {
323 //        try {
324 //            return dtdCurrent.isDeprecated(elementName, attributeName, "*");
325 //        } catch (DtdData.IllegalByDtdException e) {
326 //            return true;
327 //        }
328 //    }
329 }
330