1 package org.unicode.cldr.icu; 2 3 import java.io.File; 4 import java.io.FilenameFilter; 5 import java.io.IOException; 6 import java.util.ArrayList; 7 import java.util.Collection; 8 import java.util.Locale; 9 import java.util.Map; 10 import java.util.Map.Entry; 11 import java.util.TreeMap; 12 13 import org.unicode.cldr.icu.ICUResourceWriter.Resource; 14 import org.unicode.cldr.icu.ICUResourceWriter.ResourceAlias; 15 import org.unicode.cldr.icu.ICUResourceWriter.ResourceString; 16 import org.unicode.cldr.icu.ICUResourceWriter.ResourceTable; 17 import org.unicode.cldr.util.LDMLUtilities; 18 import org.w3c.dom.Document; 19 import org.w3c.dom.Node; 20 21 public class KeyTypeDataConverter { 22 private final ICULog log; 23 private final String bcp47Dir; 24 private final String[] externalTypeKeys; 25 private Collection<Document> documents; 26 27 private static final String SOURCE_INFO = "common/bcp47/*.xml"; 28 private static final String EXTERNAL_TYPES_SUFFIX = "Types"; 29 30 private static final boolean DEBUG = false; 31 KeyTypeDataConverter(ICULog log, String bcp47Dir, String[] externalTypeKeys)32 public KeyTypeDataConverter(ICULog log, String bcp47Dir, String[] externalTypeKeys) { 33 this.log = log; 34 this.bcp47Dir = bcp47Dir; 35 this.externalTypeKeys = (externalTypeKeys == null) ? new String[0] : externalTypeKeys; 36 } 37 convert(ICUWriter writer)38 public void convert(ICUWriter writer) { 39 // creating key mapping data 40 Map<String, String> keyMap = new TreeMap<String, String>(); 41 for (Document doc : getDocuments()) { 42 addKeyMap(keyMap, doc); 43 } 44 45 if (DEBUG) { 46 log.log("Key mappings ---------------------------------------------"); 47 dumpMap(keyMap); 48 } 49 50 // creating type mapping data 51 Map<String, Map<String, String>> typeMaps = new TreeMap<String, Map<String, String>>(); 52 Map<String, Map<String, String>> typeAliases = new TreeMap<String, Map<String, String>>(); 53 for (Document doc : getDocuments()) { 54 addTypeMaps(typeMaps, typeAliases, doc); 55 } 56 57 if (DEBUG) { 58 for (Map.Entry<String, Map<String, String>> e : typeMaps.entrySet()) { 59 log.log("\n\n\nType mappings for " + e.getKey() + " ---------------------------------------------"); 60 dumpMap(e.getValue()); 61 } 62 63 for (Map.Entry<String, Map<String, String>> e : typeAliases.entrySet()) { 64 log.log("\n\n\nAlias mappings for " + e.getKey() + " ---------------------------------------------"); 65 dumpMap(e.getValue()); 66 } 67 } 68 69 // write out keyTypeData.txt 70 Resource cur; 71 72 ResourceTable keyTypeDataRes = new ResourceTable(); 73 keyTypeDataRes.name = LDMLBCP47Constants.KEYTYPEDATA; 74 keyTypeDataRes.annotation = ResourceTable.NO_FALLBACK; 75 76 // keyMap 77 ResourceTable keyMapRes = new ResourceTable(); 78 keyMapRes.name = LDMLBCP47Constants.KEYMAP; 79 keyTypeDataRes.first = keyMapRes; 80 81 cur = null; 82 for (Entry<String, String> keyMapItem : keyMap.entrySet()) { 83 ResourceString keyRes = new ResourceString(); 84 keyRes.name = keyMapItem.getKey(); 85 keyRes.val = keyMapItem.getValue(); 86 87 if (cur == null) { 88 keyMapRes.first = keyRes; 89 } else { 90 cur.next = keyRes; 91 } 92 cur = keyRes; 93 } 94 95 // typeMap 96 Resource typeMapRes = createTypeMapResource(typeMaps, null); 97 keyMapRes.next = typeMapRes; 98 99 // typeAlias 100 Resource typeAliasRes = createTypeAliasResource(typeAliases, null); 101 typeMapRes.next = typeAliasRes; 102 103 writer.writeResource(keyTypeDataRes, SOURCE_INFO); 104 105 // externalized type/alias map data 106 for (String key : externalTypeKeys) { 107 ResourceTable extTypeDataRes = new ResourceTable(); 108 extTypeDataRes.name = key + EXTERNAL_TYPES_SUFFIX; 109 extTypeDataRes.annotation = ResourceTable.NO_FALLBACK; 110 111 // typeMap 112 Resource extTypeMapRes = createTypeMapResource(typeMaps, key); 113 extTypeDataRes.first = extTypeMapRes; 114 115 // typeAlias 116 Resource extTypeAliasRes = createTypeAliasResource(typeAliases, key); 117 extTypeMapRes.next = extTypeAliasRes; 118 119 writer.writeResource(extTypeDataRes, SOURCE_INFO); 120 } 121 122 } 123 getDocuments()124 private Collection<Document> getDocuments() { 125 if (documents != null) { 126 return documents; 127 } 128 129 documents = new ArrayList<Document>(); 130 131 FilenameFilter filter = new FilenameFilter() { 132 public boolean accept(File dir, String name) { 133 if (name.endsWith(".xml")) { 134 return true; 135 } 136 return false; 137 } 138 }; 139 140 File dir = new File(bcp47Dir); 141 String[] files = dir.list(filter); 142 if (files == null) { 143 String canonicalPath; 144 try { 145 canonicalPath = dir.getCanonicalPath(); 146 } catch (IOException e) { 147 canonicalPath = e.getMessage(); 148 } 149 log.error("BCP47 files are missing " + canonicalPath); 150 System.exit(-1); 151 } 152 153 String dirPath = dir.getAbsolutePath(); 154 for (String fileName : files) { 155 try { 156 log.info("Parsing document " + fileName); 157 String filePath = dirPath + File.separator + fileName; 158 Document doc = LDMLUtilities.parse(filePath, false); 159 documents.add(doc); 160 } catch (Throwable se) { 161 log.error("Parsing: " + fileName + " " + se.toString(), se); 162 System.exit(1); 163 } 164 } 165 return documents; 166 } 167 addKeyMap(Map<String, String> keyMap, Document root)168 private static void addKeyMap(Map<String, String> keyMap, Document root) { 169 for (Node node = root.getFirstChild(); node != null; node = node.getNextSibling()) { 170 if (node.getNodeType() != Node.ELEMENT_NODE) { 171 continue; 172 } 173 174 if (node.getNodeName().equals(LDMLBCP47Constants.LDMLBCP47)) { 175 // Stop iterating over top-level elements, restart iterating over elements 176 // under ldmlBCP47. 177 node = node.getFirstChild(); 178 continue; 179 } 180 181 if (node.getNodeName().equals(LDMLBCP47Constants.KEYWORD)) { 182 // iterating into elements under keyword 183 node = node.getFirstChild(); 184 continue; 185 } 186 187 if (node.getNodeName().equals(LDMLBCP47Constants.KEY)) { 188 String bcpKey = LDMLUtilities.getAttributeValue(node, LDMLBCP47Constants.NAME); 189 String key = LDMLUtilities.getAttributeValue(node, LDMLBCP47Constants.ALIAS); 190 if (key != null && !bcpKey.equals(key)) { 191 /* keys are case-insensitive */ 192 keyMap.put(escapeKey(key.toLowerCase(Locale.ROOT)), bcpKey); 193 } 194 } 195 } 196 } 197 addTypeMaps(Map<String, Map<String, String>> typeMaps, Map<String, Map<String, String>> typeAliases, Document root)198 private static void addTypeMaps(Map<String, Map<String, String>> typeMaps, 199 Map<String, Map<String, String>> typeAliases, Document root) { 200 for (Node node = root.getFirstChild(); node != null; node = node.getNextSibling()) { 201 if (node.getNodeType() != Node.ELEMENT_NODE) { 202 continue; 203 } 204 205 if (node.getNodeName().equals(LDMLBCP47Constants.LDMLBCP47)) { 206 // Stop iterating over top-level elements, restart iterating over elements 207 // under ldmlBCP47. 208 node = node.getFirstChild(); 209 continue; 210 } 211 212 if (node.getNodeName().equals(LDMLBCP47Constants.KEYWORD)) { 213 // iterating into elements under keyword 214 node = node.getFirstChild(); 215 continue; 216 } 217 218 if (node.getNodeName().equals(LDMLBCP47Constants.KEY)) { 219 String bcpKey = LDMLUtilities.getAttributeValue(node, LDMLBCP47Constants.NAME); 220 String key = LDMLUtilities.getAttributeValue(node, LDMLBCP47Constants.ALIAS); 221 if (key == null) { 222 key = bcpKey; 223 } 224 key = key.toLowerCase(Locale.ROOT); // keys are case-insensitive 225 for (Node node2 = node.getFirstChild(); node2 != null; node2 = node2.getNextSibling()) { 226 if (node2.getNodeType() != Node.ELEMENT_NODE) { 227 continue; 228 } 229 if (node2.getNodeName().equals(LDMLBCP47Constants.TYPE)) { 230 String bcpType = LDMLUtilities.getAttributeValue(node2, LDMLBCP47Constants.NAME); 231 String type = LDMLUtilities.getAttributeValue(node2, LDMLBCP47Constants.ALIAS); 232 if (type != null) { 233 // type may contain multiple values delimited by space character 234 String[] types = type.split(" "); 235 if (types.length > 1) { 236 type = types[0]; 237 238 // add 2nd and following type values into the alias map 239 Map<String, String> singleTypeAliases = typeAliases.get(key); 240 if (singleTypeAliases == null) { 241 singleTypeAliases = new TreeMap<String, String>(); 242 typeAliases.put(key, singleTypeAliases); 243 } 244 for (int i = 1; i < types.length; i++) { 245 singleTypeAliases.put(escapeKey(types[i]), type); 246 } 247 } 248 } 249 250 if (type != null && !bcpType.equals(type)) { 251 // only populating mapping data when bcp47 representation is different 252 Map<String, String> singleTypeMap = typeMaps.get(key); 253 if (singleTypeMap == null) { 254 singleTypeMap = new TreeMap<String, String>(); 255 typeMaps.put(key, singleTypeMap); 256 } 257 singleTypeMap.put(escapeKey(type), bcpType); 258 } 259 } 260 } 261 } 262 } 263 } 264 createTypeMapResource(Map<String, Map<String, String>> typeMaps, String key)265 private Resource createTypeMapResource(Map<String, Map<String, String>> typeMaps, String key) { 266 ResourceTable typeMapRes = new ResourceTable(); 267 typeMapRes.name = LDMLBCP47Constants.TYPEMAP; 268 269 Resource cur = null; 270 for (Entry<String, Map<String, String>> typesForKeyItem : typeMaps.entrySet()) { 271 String itemKey = typesForKeyItem.getKey(); 272 if (key != null && !itemKey.equals(key)) { 273 // skip this key 274 continue; 275 } 276 277 String aliasName = null; 278 if (key == null) { 279 for (String extKey : externalTypeKeys) { 280 if (extKey.equals(itemKey)) { 281 aliasName = "/ICUDATA/" + itemKey + EXTERNAL_TYPES_SUFFIX 282 + "/" + LDMLBCP47Constants.TYPEMAP 283 + "/" + itemKey; 284 break; 285 } 286 } 287 } 288 289 Resource res = null; 290 if (aliasName != null) { 291 // generating alias resource 292 ResourceAlias typeMapForKeyResAlias = new ResourceAlias(); 293 typeMapForKeyResAlias.name = itemKey; 294 typeMapForKeyResAlias.val = aliasName; 295 296 res = typeMapForKeyResAlias; 297 } else { 298 // generating type mapping container table per key 299 ResourceTable typeMapForKeyRes = new ResourceTable(); 300 typeMapForKeyRes.name = itemKey; 301 302 Resource curTypeRes = null; 303 for (Entry<String, String> typeItem : typesForKeyItem.getValue().entrySet()) { 304 // generating each type map data 305 ResourceString typeRes = new ResourceString(); 306 typeRes.name = typeItem.getKey(); 307 typeRes.val = typeItem.getValue(); 308 309 if (curTypeRes == null) { 310 typeMapForKeyRes.first = typeRes; 311 } else { 312 curTypeRes.next = typeRes; 313 } 314 curTypeRes = typeRes; 315 } 316 317 res = typeMapForKeyRes; 318 } 319 320 if (cur == null) { 321 typeMapRes.first = res; 322 } else { 323 cur.next = res; 324 } 325 cur = res; 326 } 327 328 return typeMapRes; 329 } 330 createTypeAliasResource(Map<String, Map<String, String>> typeAliases, String key)331 private Resource createTypeAliasResource(Map<String, Map<String, String>> typeAliases, String key) { 332 ResourceTable typeAliasRes = new ResourceTable(); 333 typeAliasRes.name = LDMLBCP47Constants.TYPEALIAS; 334 335 Resource cur = null; 336 for (Entry<String, Map<String, String>> aliasesForKeyItem : typeAliases.entrySet()) { 337 String itemKey = aliasesForKeyItem.getKey(); 338 if (key != null && !itemKey.equals(key)) { 339 // skip this key 340 continue; 341 } 342 343 String aliasName = null; 344 if (key == null) { 345 for (String extKey : externalTypeKeys) { 346 if (extKey.equals(itemKey)) { 347 aliasName = "/ICUDATA/" + itemKey + EXTERNAL_TYPES_SUFFIX 348 + "/" + LDMLBCP47Constants.TYPEALIAS 349 + "/" + itemKey; 350 break; 351 } 352 } 353 } 354 355 Resource res = null; 356 357 if (aliasName != null) { 358 // generating alias resource 359 ResourceAlias aliasesForKeyResAlias = new ResourceAlias(); 360 aliasesForKeyResAlias.name = itemKey; 361 aliasesForKeyResAlias.val = aliasName; 362 363 res = aliasesForKeyResAlias; 364 } else { 365 // generating alias mapping container table per key 366 ResourceTable aliasesForKeyRes = new ResourceTable(); 367 aliasesForKeyRes.name = itemKey; 368 369 Resource curAliasRes = null; 370 for (Entry<String, String> aliasItem : aliasesForKeyItem.getValue().entrySet()) { 371 // generating each alias map data 372 ResourceString aliasRes = new ResourceString(); 373 aliasRes.name = aliasItem.getKey(); 374 aliasRes.val = aliasItem.getValue(); 375 376 if (curAliasRes == null) { 377 aliasesForKeyRes.first = aliasRes; 378 } else { 379 curAliasRes.next = aliasRes; 380 } 381 curAliasRes = aliasRes; 382 } 383 384 res = aliasesForKeyRes; 385 } 386 387 if (cur == null) { 388 typeAliasRes.first = res; 389 } else { 390 cur.next = res; 391 } 392 cur = res; 393 } 394 395 return typeAliasRes; 396 } 397 escapeKey(String key)398 private static String escapeKey(String key) { 399 if (key.contains("/")) { 400 key = "\"" + key.replace('/', ':') + "\""; 401 } 402 return key; 403 } 404 dumpMap(Map<String, String> map)405 private void dumpMap(Map<String, String> map) { 406 for (Map.Entry<String, String> e : map.entrySet()) { 407 log.log(e.getKey() + " -> " + e.getValue()); 408 } 409 } 410 411 private static class LDMLBCP47Constants { 412 static final String LDMLBCP47 = "ldmlBCP47"; 413 static final String KEYWORD = "keyword"; 414 static final String KEY = "key"; 415 static final String NAME = "name"; 416 static final String ALIAS = "alias"; 417 static final String TYPE = "type"; 418 static final String KEYTYPEDATA = "keyTypeData"; 419 static final String KEYMAP = "keyMap"; 420 static final String TYPEMAP = "typeMap"; 421 static final String TYPEALIAS = "typeAlias"; 422 } 423 } 424