1 package org.unicode.cldr.util; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.IOException; 6 import java.util.Collections; 7 import java.util.HashMap; 8 import java.util.LinkedHashMap; 9 import java.util.LinkedHashSet; 10 import java.util.Map; 11 import java.util.Set; 12 import java.util.TreeMap; 13 import java.util.regex.Matcher; 14 15 import org.xml.sax.InputSource; 16 import org.xml.sax.SAXException; 17 import org.xml.sax.XMLReader; 18 import org.xml.sax.ext.DeclHandler; 19 20 import com.ibm.icu.impl.Relation; 21 import com.ibm.icu.impl.Row; 22 import com.ibm.icu.impl.Row.R2; 23 import com.ibm.icu.impl.Row.R3; 24 import com.ibm.icu.util.ICUUncheckedIOException; 25 26 public class ElementAttributeInfo { 27 28 private DtdType dtdType; 29 private Map<R2<String, String>, R3<Set<String>, String, String>> elementAttribute2Data = new TreeMap<>(); 30 private Relation<String, String> element2children = Relation.of(new LinkedHashMap<String, Set<String>>(), LinkedHashSet.class); 31 private Relation<String, String> element2parents = Relation.of(new LinkedHashMap<String, Set<String>>(), LinkedHashSet.class); 32 private Relation<String, String> element2attributes = Relation.of(new LinkedHashMap<String, Set<String>>(), LinkedHashSet.class); 33 34 static Map<String, Map<DtdType, ElementAttributeInfo>> cache = new HashMap<>(); // new 35 // HashMap<DtdType, 36 // Data>(); 37 getInstance(DtdType dtdType)38 public static final ElementAttributeInfo getInstance(DtdType dtdType) { 39 return getInstance(CLDRPaths.COMMON_DIRECTORY, dtdType); 40 } 41 getInstance(String commonDirectory, DtdType dtdType)42 public static final ElementAttributeInfo getInstance(String commonDirectory, DtdType dtdType) { 43 Map<DtdType, ElementAttributeInfo> result = cache.get(commonDirectory); 44 if (result == null) { 45 try { 46 File file = new File(commonDirectory); 47 String canonicalCommonDirectory; 48 canonicalCommonDirectory = file.getCanonicalFile().toString(); 49 if (!commonDirectory.equals(canonicalCommonDirectory)) { 50 result = cache.get(commonDirectory); 51 if (result != null) { 52 cache.put(commonDirectory, result); 53 } 54 } 55 if (result == null) { 56 result = new HashMap<>(); 57 // pick short files that are in repository 58 result.put(DtdType.ldml, new ElementAttributeInfo(canonicalCommonDirectory + "/main/root.xml", 59 DtdType.ldml)); 60 result.put(DtdType.supplementalData, new ElementAttributeInfo(canonicalCommonDirectory 61 + "/supplemental/plurals.xml", DtdType.supplementalData)); 62 result.put(DtdType.ldmlBCP47, new ElementAttributeInfo(canonicalCommonDirectory 63 + "/bcp47/calendar.xml", DtdType.ldmlBCP47)); 64 result.put(DtdType.keyboard, new ElementAttributeInfo(canonicalCommonDirectory 65 + "/../keyboards/android/ar-t-k0-android.xml", DtdType.keyboard)); 66 result.put(DtdType.platform, new ElementAttributeInfo(canonicalCommonDirectory 67 + "/../keyboards/android/_platform.xml", DtdType.keyboard)); 68 cache.put(commonDirectory, result); 69 cache.put(canonicalCommonDirectory, result); 70 } 71 } catch (IOException e) { 72 throw new ICUUncheckedIOException(e); 73 } 74 } 75 return result.get(dtdType); 76 } 77 78 // static { 79 // try { 80 // addFromDTD(CldrUtility.COMMON_DIRECTORY + "main/en.xml", DtdType.ldml); 81 // addFromDTD(CldrUtility.COMMON_DIRECTORY + "supplemental/characters.xml", DtdType.supplementalData); 82 // addFromDTD(CldrUtility.COMMON_DIRECTORY + "bcp47/calendar.xml", DtdType.ldmlBCP47); 83 // } catch (IOException e) { 84 // throw new IllegalArgumentException(e); 85 // } 86 // } 87 ElementAttributeInfo(String filename, DtdType type)88 private ElementAttributeInfo(String filename, DtdType type) throws IOException { 89 // StringBufferInputStream fis = new StringBufferInputStream( 90 // "<!DOCTYPE ldml SYSTEM \"http://www.unicode.org/cldr/dtd/1.2/ldml.dtd\"><ldml></ldml>"); 91 FileInputStream fis = new FileInputStream(filename); 92 try { 93 XMLReader xmlReader = CLDRFile.createXMLReader(true); 94 this.dtdType = type; 95 MyDeclHandler me = new MyDeclHandler(this); 96 xmlReader.setProperty("http://xml.org/sax/properties/declaration-handler", me); 97 InputSource is = new InputSource(fis); 98 is.setSystemId(filename); 99 // xmlReader.setContentHandler(me); 100 // xmlReader.setErrorHandler(me); 101 xmlReader.parse(is); 102 this.elementAttribute2Data = Collections.unmodifiableMap(getElementAttribute2Data()); // TODO, protect rows 103 getElement2Children().freeze(); 104 getElement2Parents().freeze(); 105 getElement2Attributes().freeze(); 106 } catch (Exception e) { 107 e.printStackTrace(); 108 } finally { 109 fis.close(); 110 } 111 } 112 getDtdType()113 private DtdType getDtdType() { 114 return dtdType; 115 } 116 getElementAttribute2Data()117 public Map<R2<String, String>, R3<Set<String>, String, String>> getElementAttribute2Data() { 118 return elementAttribute2Data; 119 } 120 getElement2Children()121 public Relation<String, String> getElement2Children() { 122 return element2children; 123 } 124 getElement2Parents()125 public Relation<String, String> getElement2Parents() { 126 return element2parents; 127 } 128 getElement2Attributes()129 public Relation<String, String> getElement2Attributes() { 130 return element2attributes; 131 } 132 133 static class MyDeclHandler implements DeclHandler { 134 private static final boolean SHOW = false; 135 private ElementAttributeInfo myData; 136 137 Matcher idmatcher = PatternCache.get("[a-zA-Z0-9][-_a-zA-Z0-9]*").matcher(""); 138 MyDeclHandler(ElementAttributeInfo indata)139 public MyDeclHandler(ElementAttributeInfo indata) { 140 myData = indata; 141 } 142 143 @Override attributeDecl(String eName, String aName, String type, String mode, String value)144 public void attributeDecl(String eName, String aName, String type, String mode, String value) 145 throws SAXException { 146 if (SHOW) 147 System.out.println(myData.getDtdType() + "\tAttributeDecl\t" + eName + "\t" + aName + "\t" + type 148 + "\t" + mode + "\t" + value); 149 R2<String, String> key = Row.of(eName, aName); 150 Set<String> typeSet = getIdentifiers(type); 151 R3<Set<String>, String, String> value2 = Row.of(typeSet, mode, value); 152 R3<Set<String>, String, String> oldValue = myData.getElementAttribute2Data().get(key); 153 if (oldValue != null && !oldValue.equals(value2)) { 154 throw new IllegalArgumentException("Conflict in data: " + key + "\told: " + oldValue + "\tnew: " 155 + value2); 156 } 157 myData.getElementAttribute2Data().put(key, value2); 158 myData.getElement2Attributes().put(eName, aName); 159 } 160 getIdentifiers(String type)161 private Set<String> getIdentifiers(String type) { 162 Set<String> result = new LinkedHashSet<>(); 163 idmatcher.reset(type); 164 while (idmatcher.find()) { 165 result.add(idmatcher.group()); 166 } 167 if (result.size() == 0) { 168 throw new IllegalArgumentException("No identifiers found in: " + type); 169 } 170 return result; 171 } 172 173 @Override elementDecl(String name, String model)174 public void elementDecl(String name, String model) throws SAXException { 175 if (SHOW) System.out.println(myData.getDtdType() + "\tElement\t" + name + "\t" + model); 176 Set<String> identifiers = getIdentifiers(model); 177 // identifiers.remove("special"); 178 // identifiers.remove("alias"); 179 if (identifiers.size() == 0) { 180 identifiers.add("EMPTY"); 181 } 182 myData.getElement2Children().putAll(name, identifiers); 183 for (String identifier : identifiers) { 184 myData.getElement2Parents().put(identifier, name); 185 } 186 } 187 188 @Override externalEntityDecl(String name, String publicId, String systemId)189 public void externalEntityDecl(String name, String publicId, String systemId) throws SAXException { 190 // TODO Auto-generated method stub 191 192 } 193 194 @Override internalEntityDecl(String name, String value)195 public void internalEntityDecl(String name, String value) throws SAXException { 196 // TODO Auto-generated method stub 197 198 } 199 } 200 } 201