blob: 0992d17462dcfe049bcd137fed5761d7b301ea27 [file] [log] [blame]
package org.unicode.cldr.tool;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.unicode.cldr.util.CLDRConfig;
import org.unicode.cldr.util.CldrUtility;
import org.unicode.cldr.util.DtdData;
import org.unicode.cldr.util.DtdData.Attribute;
import org.unicode.cldr.util.DtdData.Element;
import org.unicode.cldr.util.DtdType;
import org.unicode.cldr.util.SupplementalDataInfo;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.ibm.icu.dev.util.CollectionUtilities;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.util.VersionInfo;
/**
* Changed ShowDtdDiffs into a chart.
* @author markdavis
*/
public class ChartDtdDelta extends Chart {
public static void main(String[] args) {
new ChartDtdDelta().writeChart(null);
}
@Override
public String getDirectory() {
return FormattedFileWriter.CHART_TARGET_DIR;
}
@Override
public String getTitle() {
return "DTD Deltas";
}
@Override
public String getExplanation() {
return "<p>Shows additions to the LDML dtds over time. New elements or attributes are indicated with a + sign. "
+ "Currently deprecated elements and attributes are omitted."
+ "<p>";
}
@Override
public void writeContents(FormattedFileWriter pw) throws IOException {
TablePrinter tablePrinter = new TablePrinter()
.addColumn("Version", "class='source'", CldrUtility.getDoubleLinkMsg(), "class='source'", true)
.setSortPriority(0)
.setSortAscending(false)
.setBreakSpans(true)
.addColumn("Dtd Type", "class='source'", null, "class='source'", true)
.setSortPriority(1)
.addColumn("Intermediate Path", "class='source'", null, "class='target'", true)
.setSortPriority(2)
.addColumn("Element", "class='target'", null, "class='target'", true)
.setSpanRows(false)
.addColumn("Attributes", "class='target'", null, "class='target'", true)
.setSpanRows(false);
String last = null;
LinkedHashSet<String> allVersions = new LinkedHashSet<>(ToolConstants.CLDR_VERSIONS);
allVersions.add(ToolConstants.LAST_CHART_VERSION);
for (String current : allVersions) {
System.out.println("DTD delta: " + current);
final boolean finalVersion = current.equals(ToolConstants.LAST_CHART_VERSION);
String currentName = finalVersion ? ToolConstants.CHART_DISPLAY_VERSION : current;
for (DtdType type : TYPES) {
String firstVersion = type.firstVersion; // FIRST_VERSION.get(type);
if (firstVersion != null && current != null && current.compareTo(firstVersion) < 0) {
continue;
}
DtdData dtdCurrent = null;
try {
dtdCurrent = DtdData.getInstance(type,
finalVersion && ToolConstants.CHART_STATUS != ToolConstants.ChartStatus.release ? null : current);
} catch (Exception e) {
if (!(e.getCause() instanceof FileNotFoundException)) {
throw e;
}
System.out.println(e.getMessage() + ", " + e.getCause().getMessage());
continue;
}
DtdData dtdLast = null;
if (last != null) {
try {
dtdLast = DtdData.getInstance(type, last);
} catch (Exception e) {
if (!(e.getCause() instanceof FileNotFoundException)) {
throw e;
}
}
}
diff(currentName, dtdLast, dtdCurrent);
}
last = current;
}
for (DiffElement datum : data) {
tablePrinter.addRow()
.addCell(datum.getVersionString())
.addCell(datum.dtdType)
.addCell(datum.newPath)
.addCell(datum.newElement)
.addCell(datum.attributeNames)
.finishRow();
}
pw.write(tablePrinter.toTable());
pw.write(Utility.repeat("<br>", 50));
}
static final String NONE = " ";
static final SupplementalDataInfo SDI = CLDRConfig.getInstance().getSupplementalDataInfo();
static Set<DtdType> TYPES = EnumSet.allOf(DtdType.class);
static {
TYPES.remove(DtdType.ldmlICU);
}
static final Map<DtdType, String> FIRST_VERSION = new EnumMap<>(DtdType.class);
static {
FIRST_VERSION.put(DtdType.ldmlBCP47, "1.7.2");
FIRST_VERSION.put(DtdType.keyboard, "22.1");
FIRST_VERSION.put(DtdType.platform, "22.1");
}
private void diff(String prefix, DtdData dtdLast, DtdData dtdCurrent) {
Map<String, Element> oldNameToElement = dtdLast == null ? Collections.EMPTY_MAP : dtdLast.getElementFromName();
checkNames(prefix, dtdCurrent, oldNameToElement, "/", dtdCurrent.ROOT, new HashSet<Element>());
}
static final DtdType DEBUG_DTD = null; // set to enable
static final String DEBUG_ELEMENT = "lias";
static final boolean SHOW = false;
@SuppressWarnings("unused")
private void checkNames(String version, DtdData dtdCurrent, Map<String, Element> oldNameToElement, String path, Element element, HashSet<Element> seen) {
if (seen.contains(element)) {
return;
}
seen.add(element);
String name = element.getName();
if (SHOW && ToolConstants.CHART_DISPLAY_VERSION.equals(version)) {
System.out.println(dtdCurrent.dtdType + "\t" + name);
}
if (DEBUG_DTD == dtdCurrent.dtdType && name.contains(DEBUG_ELEMENT)) {
int debug = 0;
}
if (SKIP_ELEMENTS.contains(name)) {
return;
}
if (SKIP_TYPE_ELEMENTS.containsEntry(dtdCurrent.dtdType, name)) {
return;
}
if (isDeprecated(dtdCurrent.dtdType, name, "*")) { // SDI.isDeprecated(dtdCurrent.dtdType, name, "*", "*")) {
return;
}
String newPath = path + "/" + element.name;
if (!oldNameToElement.containsKey(name)) {
Set<String> attributeNames = getAttributeNames(dtdCurrent, name, Collections.EMPTY_MAP, element.getAttributes());
addData(dtdCurrent, "+" + name, version, newPath, attributeNames);
} else {
Element oldElement = oldNameToElement.get(name);
Set<String> attributeNames = getAttributeNames(dtdCurrent, name, oldElement.getAttributes(), element.getAttributes());
if (!attributeNames.isEmpty()) {
addData(dtdCurrent, name, version, newPath, attributeNames);
}
}
for (Element child : element.getChildren().keySet()) {
checkNames(version, dtdCurrent, oldNameToElement, newPath, child, seen);
}
}
enum DiffType {
Element, Attribute, AttributeValue
}
private static class DiffElement {
final VersionInfo version;
final DtdType dtdType;
final boolean isBeta;
final String newPath;
final String newElement;
final String attributeNames;
public DiffElement(DtdData dtdCurrent, String version, String newPath, String newElement, Set<String> attributeNames2) {
isBeta = version.endsWith("β");
try {
this.version = isBeta ? VersionInfo.getInstance(version.substring(0, version.length()-1)) : VersionInfo.getInstance(version);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
dtdType = dtdCurrent.dtdType;
this.newPath = fix(newPath);
this.attributeNames = attributeNames2.isEmpty() ? NONE : "+" + CollectionUtilities.join(attributeNames2, ", +");
this.newElement = newElement;
}
private String fix(String substring) {
int base = substring.indexOf('/', 2);
if (base < 0) return "";
int last = substring.lastIndexOf('/');
if (last <= base) return "/";
substring = substring.substring(base, last);
return substring.replace("/", "\u200B/") + "/";
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("version", getVersionString())
.add("dtdType", dtdType)
.add("newPath", newPath)
.add("newElement", newElement)
.add("attributeNames", attributeNames)
.toString();
}
private String getVersionString() {
return version.getVersionString(2, 4) + (isBeta ? "β" : "");
}
}
List<DiffElement> data = new ArrayList<>();
private void addData(DtdData dtdCurrent, String element, String prefix, String newPath, Set<String> attributeNames) {
DiffElement item = new DiffElement(dtdCurrent, prefix, newPath, element, attributeNames);
data.add(item);
}
static final Set<String> SKIP_ELEMENTS = ImmutableSet.of("generation", "identity", "special", "telephoneCodeData");
static final Multimap<DtdType, String> SKIP_TYPE_ELEMENTS = ImmutableMultimap.of(DtdType.ldml, "alias");
static final Set<String> SKIP_ATTRIBUTES = ImmutableSet.of("references", "standard", "draft", "alt");
private static Set<String> getAttributeNames(DtdData dtdCurrent, String elementName, Map<Attribute, Integer> attributesOld,
Map<Attribute, Integer> attributes) {
Set<String> names = new LinkedHashSet<>();
main: for (Attribute attribute : attributes.keySet()) {
String name = attribute.getName();
if (SKIP_ATTRIBUTES.contains(name)) {
continue;
}
if (isDeprecated(dtdCurrent.dtdType, elementName, name)) { // SDI.isDeprecated(dtdCurrent, elementName, name, "*")) {
continue;
}
for (Attribute attributeOld : attributesOld.keySet()) {
if (attributeOld.name.equals(name)) {
continue main;
}
}
names.add(name);
}
return names;
}
private static boolean isDeprecated(DtdType dtdType, String elementName, String attributeName) {
try {
return DtdData.getInstance(dtdType).isDeprecated(elementName, attributeName, "*");
} catch (DtdData.IllegalByDtdException e) {
return true;
}
}
}