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