1 package org.unicode.cldr.draft; 2 3 import java.io.File; 4 import java.io.IOException; 5 import java.io.PrintWriter; 6 import java.util.ArrayList; 7 import java.util.Arrays; 8 import java.util.Collection; 9 import java.util.Collections; 10 import java.util.HashSet; 11 import java.util.Iterator; 12 import java.util.LinkedHashMap; 13 import java.util.LinkedHashSet; 14 import java.util.List; 15 import java.util.Map; 16 import java.util.Set; 17 import java.util.TreeMap; 18 import java.util.TreeSet; 19 20 import org.unicode.cldr.util.CLDRFile; 21 import org.unicode.cldr.util.CLDRPaths; 22 import org.unicode.cldr.util.DtdType; 23 import org.unicode.cldr.util.ElementAttributeInfo; 24 import org.unicode.cldr.util.Factory; 25 import org.unicode.cldr.util.XPathParts; 26 27 import com.ibm.icu.impl.Relation; 28 import com.ibm.icu.impl.Row; 29 import com.ibm.icu.impl.Row.R2; 30 import com.ibm.icu.impl.Utility; 31 import com.ibm.icu.util.ICUUncheckedIOException; 32 33 public class JsonConverter { 34 35 private static final String FILES = "el.*"; 36 private static final String MAIN_DIRECTORY = CLDRPaths.MAIN_DIRECTORY;// CldrUtility.SUPPLEMENTAL_DIRECTORY; 37 // //CldrUtility.MAIN_DIRECTORY; 38 private static final String OUT_DIRECTORY = CLDRPaths.GEN_DIRECTORY + "/jason/"; // CldrUtility.MAIN_DIRECTORY; 39 private static boolean COMPACT = false; 40 static final Set<String> REPLACING_BASE = !COMPACT ? Collections.EMPTY_SET : new HashSet<>( 41 Arrays.asList("type id key count".split("\\s"))); 42 static final Set<String> EXTRA_DISTINGUISHING = new HashSet<>( 43 Arrays.asList("locales territory desired supported".split("\\s"))); 44 static final Relation<String, String> mainInfo = ElementAttributeInfo.getInstance(DtdType.ldml) 45 .getElement2Attributes(); 46 static final Relation<String, String> suppInfo = ElementAttributeInfo.getInstance(DtdType.supplementalData) 47 .getElement2Attributes(); 48 main(String[] args)49 public static void main(String[] args) throws IOException { 50 final String subdirectory = new File(MAIN_DIRECTORY).getName(); 51 final Factory cldrFactory = Factory.make(MAIN_DIRECTORY, FILES); 52 final Set<String> locales = new TreeSet<>(cldrFactory.getAvailable()); 53 /* 54 * TODO: "parts" is always empty, so all the code using it is wasted! 55 */ 56 final XPathParts parts = new XPathParts(); 57 for (String locale : locales) { 58 System.out.println("Converting:\t" + locale); 59 final CLDRFile file = cldrFactory.make(locale, false); 60 Relation<String, String> element2Attributes = file.isNonInheriting() ? suppInfo : mainInfo; 61 final Item main = new TableItem(null); 62 DtdType dtdType = null; 63 for (Iterator<String> it = file.iterator("", file.getComparator()); it.hasNext();) { 64 final String xpath = it.next(); 65 final String fullXpath = file.getFullXPath(xpath); 66 String value = file.getStringValue(xpath); 67 XPathParts oldParts = XPathParts.getFrozenInstance(fullXpath).cloneAsThawed(); // not frozen, rewrite can modify 68 if (dtdType == null) { 69 dtdType = DtdType.valueOf(parts.getElement(0)); 70 } 71 rewrite(dtdType, oldParts, value, element2Attributes, parts); 72 System.out.println(parts); 73 Item current = main; 74 int size = parts.size(); 75 76 for (int i = 0; i < size - 1; ++i) { 77 final String element = parts.getElement(i); 78 Map<String, String> actualAttributeKeys = parts.getAttributes(i); 79 Set<String> keySet = actualAttributeKeys.keySet(); 80 if (keySet.size() != 0) { 81 Item temp = current.makeSubItem(element, Item.Type.unorderedItem); 82 for (String attribute : keySet) { 83 temp.put(attribute, actualAttributeKeys.get(attribute)); 84 } 85 } 86 if (i < size - 2) { 87 current = current.makeSubItem(element, 88 actualAttributeKeys.containsKey("_q") ? Item.Type.orderedItem : Item.Type.unorderedItem); 89 } else { 90 current.put(element, parts.getElement(i + 1)); 91 } 92 } 93 } 94 PrintWriter out = FileUtilities.openUTF8Writer(OUT_DIRECTORY + subdirectory, locale + ".json"); 95 main.print(out, 0); 96 out.close(); 97 } 98 } 99 100 static Relation<String, String> extraDistinguishing = Relation.of(new TreeMap<String, Set<String>>(), LinkedHashSet.class); 101 static { putAll(extraDistinguishing, "dayPeriodRule", "earlyMorning", "before", "from")102 putAll(extraDistinguishing, "dayPeriodRule", "earlyMorning", "before", "from"); 103 } 104 putAll(Relation r, K key, V... values)105 static <K, V> void putAll(Relation r, K key, V... values) { 106 r.putAll(key, Arrays.asList(values)); 107 } 108 isDistinguishing(DtdType dtdType, final String element, final String attribute)109 private static boolean isDistinguishing(DtdType dtdType, final String element, final String attribute) { 110 // <mapZone other="Afghanistan" territory="001" type="Asia/Kabul"/> result is the type! 111 // <deprecatedItems elements="variant" attributes="type" values="BOKMAL NYNORSK AALAND POLYTONI"/> 112 // ugly: if there are values, then everything else is distinguishing, ow if there are attibutes, elements are 113 if (element.equals("deprecatedItems")) { 114 115 } 116 Set<String> extras = extraDistinguishing.getAll(element); 117 if (extras != null && extras.contains(attribute)) return true; 118 if (EXTRA_DISTINGUISHING.contains(attribute)) return true; 119 return CLDRFile.isDistinguishing(dtdType, element, attribute); 120 } 121 rewrite(DtdType dtdType, XPathParts parts, String value, Relation<String, String> element2Attributes, XPathParts out)122 private static void rewrite(DtdType dtdType, XPathParts parts, String value, 123 Relation<String, String> element2Attributes, XPathParts out) { 124 out.clear(); 125 int size = parts.size(); 126 for (int i = 1; i < size; ++i) { 127 final String element = parts.getElement(i); 128 out.addElement(element); 129 130 // turn a path into a revised path. All distinguished attributes (including those not currently on the 131 // string) 132 // get turned into extra element/element pairs, starting with _ 133 // all non-distinguishing attributes get turned into separate children 134 // a/b[@non="y"][@dist="x"]/w : z => 135 // a/b/_dist/x/_non=y 136 // a/b/_dist/x/w=z 137 Collection<String> actualAttributeKeys = parts.getAttributeKeys(i); 138 boolean isOrdered = actualAttributeKeys.contains("_q"); 139 Set<String> possibleAttributeKeys = element2Attributes.getAll(element); 140 141 for (final String attribute : actualAttributeKeys) { 142 String attributeValue = parts.getAttributeValue(i, attribute); 143 if (!isDistinguishing(dtdType, element, attribute)) { 144 out.addAttribute(attribute, attributeValue); 145 } 146 } 147 if (possibleAttributeKeys != null) { 148 for (final String attribute : possibleAttributeKeys) { 149 if (isDistinguishing(dtdType, element, attribute)) { 150 if (attribute.equals("alt")) { 151 // TODO fix 152 System.err.println("Warning: Unhandled ALT: " + parts.toString()); 153 } 154 String attributeValue = parts.getAttributeValue(i, attribute); 155 out.addElement("_" + attribute); 156 if (attributeValue == null) { 157 attributeValue = "?"; 158 } 159 out.addElement(attributeValue); 160 } 161 } 162 } 163 if (isOrdered) { 164 Map<String, String> lastAttributes = out.getAttributes(-2); 165 lastAttributes.put("_q", "_q"); 166 } 167 } 168 if (value.length() > 0) { 169 out.addElement(value); 170 } 171 172 if (!COMPACT) { 173 return; 174 } 175 if (parts.getElement(-1).equals("type")) { 176 String key = parts.getAttributeValue(-1, "key"); 177 if (key != null) { 178 parts.setElement(-2, key + "Key"); 179 parts.putAttributeValue(-1, "key", null); 180 } 181 // fall thru 182 } 183 if (parts.getElement(1).equals("localeDisplayNames")) { 184 String element2 = parts.getElement(2); 185 if (!element2.endsWith("Pattern")) { 186 if (element2.endsWith("s")) { 187 element2 = element2.substring(0, element2.length() - 1); 188 } 189 parts.setElement(2, element2 + "Names"); 190 } 191 parts.removeElement(1); 192 } 193 if (parts.getElement(1).equals("dates")) { 194 parts.removeElement(1); 195 String element1 = parts.getElement(1); 196 if (element1.equals("timeZoneNames")) { 197 String main = parts.getElement(2); 198 if (main.equals("zone") || main.equals("metazone")) { 199 parts.setElement(1, main + "Names"); 200 } 201 return; 202 } 203 } 204 if (parts.getElement(1).equals("numbers") && parts.getElement(2).equals("currencies")) { 205 parts.removeElement(1); 206 return; 207 } 208 } 209 210 static class ElementName { 211 String oldBase; 212 String base; 213 boolean replacedBase; 214 StringBuilder suffix = new StringBuilder(); 215 reset(String element)216 public void reset(String element) { 217 suffix.setLength(0); 218 base = oldBase = element; 219 replacedBase = false; 220 } 221 add(String attribute, String attributeValue)222 public void add(String attribute, String attributeValue) { 223 if (REPLACING_BASE.contains(attribute)) { 224 if (replacedBase) { 225 System.out.println("ERROR: Two replacement types on same element!!\t" + oldBase + "," + base + "," 226 + attribute + "," + attributeValue); 227 } else { 228 replacedBase = true; 229 base = attributeValue; 230 return; 231 } 232 } 233 suffix.append('$').append(attribute).append('=').append(attributeValue); 234 } 235 236 @Override toString()237 public String toString() { 238 if (suffix == null) { 239 return base; 240 } 241 return base + suffix; 242 } 243 } 244 245 static abstract class Item { 246 protected Item parent; 247 Item(Item parent)248 public Item(Item parent) { 249 this.parent = parent; 250 } 251 size()252 public abstract int size(); 253 254 enum Type { 255 unorderedItem, orderedItem 256 } 257 print(Appendable result, int i)258 public abstract Appendable print(Appendable result, int i); 259 indent(Appendable result, int i)260 protected Appendable indent(Appendable result, int i) throws IOException { 261 return result.append(getIndent(i)); 262 } 263 getIndent(int i)264 protected String getIndent(int i) { 265 return Utility.repeat(" ", i); 266 } 267 appendString(Appendable result, String string, int indent)268 public Appendable appendString(Appendable result, String string, int indent) throws IOException { 269 result.append('"'); 270 for (int i = 0; i < string.length(); ++i) { 271 // http://www.json.org/ 272 // any-Unicode-character-except-"-or-\-or-control-character 273 // uses UTF16 274 char ch = string.charAt(i); 275 switch (ch) { 276 case '\"': 277 result.append("\\\""); 278 break; 279 case '\\': 280 result.append("\\\\"); 281 break; 282 case '/': 283 result.append("\\/"); 284 break; 285 case '\b': 286 result.append("\\b"); 287 break; 288 case '\f': 289 result.append("\\f"); 290 break; 291 case '\n': 292 if (indent < 0) { 293 result.append("\\n"); 294 } else { 295 result.append('\n').append(getIndent(indent)); 296 } 297 break; 298 case '\r': 299 result.append("\\r"); 300 break; 301 case '\t': 302 result.append("\\t"); 303 break; 304 default: 305 if (ch <= 0x1F || 0x7F <= ch && ch <= 0x9F) { 306 result.append("\\u").append(Utility.hex(ch, 4)); 307 } else { 308 result.append(ch); 309 } 310 break; 311 } 312 } 313 return result.append('"'); 314 } 315 316 @Override toString()317 public String toString() { 318 return print(new StringBuilder(), 0).toString(); 319 } 320 create(Type ordered)321 protected Item create(Type ordered) { 322 switch (ordered) { 323 case unorderedItem: 324 return new TableItem(this); 325 case orderedItem: 326 return new ArrayItem(this); 327 default: 328 throw new UnsupportedOperationException(); 329 } 330 } 331 makeSubItem(String element, Type ordered)332 public abstract Item makeSubItem(String element, Type ordered); 333 put(String element, String value)334 public abstract void put(String element, String value); 335 getRoot()336 public Item getRoot() { 337 if (parent == null) { 338 return this; 339 } else { 340 return parent.getRoot(); 341 } 342 } 343 } 344 345 static class TableItem extends Item { TableItem(Item parent)346 public TableItem(Item parent) { 347 super(parent); 348 } 349 350 private Map<String, Item> map = new LinkedHashMap<>(); 351 get(String element)352 public Item get(String element) { 353 return map.get(element); 354 } 355 356 @Override put(String element, String value)357 public void put(String element, String value) { 358 Item old = map.get(element); 359 if (old != null) { 360 if (old instanceof StringItem) { 361 if (value.equals(((StringItem) old).value)) { 362 return; 363 } 364 } 365 throw new IllegalArgumentException("ERROR: Table already has object: " + element + ", " + old + ", " 366 + value + ", " + getRoot().toString()); 367 } 368 map.put(element, new StringItem(value)); 369 } 370 371 @Override makeSubItem(String element, Type ordered)372 public Item makeSubItem(String element, Type ordered) { 373 Item result = map.get(element); 374 if (result != null) { 375 return result; 376 } 377 result = create(ordered); 378 result.parent = this; 379 380 map.put(element, result); 381 return result; 382 } 383 384 @Override print(Appendable result, int i)385 public Appendable print(Appendable result, int i) { 386 try { 387 if (map.size() == 0) { 388 result.append("{}"); 389 return result; 390 } 391 result.append("{\n"); 392 boolean first = true; 393 for (String key : map.keySet()) { 394 Item value = map.get(key); 395 if (first) { 396 first = false; 397 } else { 398 result.append(",\n"); 399 } 400 indent(result, i + 1); 401 appendString(result, key, -1).append(" : "); 402 value.print(result, i + 1); 403 } 404 result.append("\n"); 405 indent(result, i).append("}"); 406 return result; 407 } catch (IOException e) { 408 throw new ICUUncheckedIOException(e); 409 } 410 } 411 412 @Override size()413 public int size() { 414 return map.size(); 415 } 416 } 417 418 static class ArrayItem extends Item { ArrayItem(Item parent)419 public ArrayItem(Item parent) { 420 super(parent); 421 } 422 423 private List<Row.R2<String, Item>> list = new ArrayList<>(); 424 425 @Override print(Appendable result, int i)426 public Appendable print(Appendable result, int i) { 427 try { 428 if (list.size() == 0) { 429 result.append("[]"); 430 return result; 431 } 432 433 result.append("[\n"); 434 for (int j = 0; j < list.size(); ++j) { 435 if (j != 0) { 436 result.append(",\n"); 437 } 438 indent(result, i + 1); 439 R2<String, Item> row = list.get(j); 440 result.append("{"); 441 appendString(result, row.get0(), i + 1); 442 result.append(" : "); 443 row.get1().print(result, i + 1); 444 result.append("}"); 445 } 446 result.append("\n"); 447 indent(result, i).append("]"); 448 return result; 449 } catch (IOException e) { 450 throw new IllegalArgumentException(e); 451 } 452 } 453 454 @Override makeSubItem(String element, Type ordered)455 public Item makeSubItem(String element, Type ordered) { 456 Item result = create(ordered); 457 list.add(Row.of(element, result)); 458 return result; 459 } 460 461 @Override put(String element, String value)462 public void put(String element, String value) { 463 list.add(Row.of(element, (Item) new StringItem(value))); 464 } 465 466 @Override size()467 public int size() { 468 return list.size(); 469 } 470 } 471 472 static class StringItem extends Item { 473 private String value; 474 StringItem(String value2)475 public StringItem(String value2) { 476 super(null); 477 value = value2; 478 } 479 480 @Override print(Appendable result, int i)481 public Appendable print(Appendable result, int i) { 482 try { 483 return appendString(result, value, i + 1); 484 } catch (IOException e) { 485 throw new IllegalArgumentException(e); 486 } 487 } 488 489 @Override makeSubItem(String element, Type ordered)490 public Item makeSubItem(String element, Type ordered) { 491 throw new UnsupportedOperationException(); 492 } 493 494 @Override put(String element, String value)495 public void put(String element, String value) { 496 throw new UnsupportedOperationException(); 497 } 498 499 @Override size()500 public int size() { 501 throw new UnsupportedOperationException(); 502 } 503 } 504 } 505