1 package org.unicode.cldr.json; 2 3 import java.text.ParseException; 4 import java.util.HashMap; 5 import java.util.Map; 6 7 import com.ibm.icu.impl.Utility; 8 9 /** 10 * CldrNode represent a Element in XML as it appears in a CldrItem's path. 11 */ 12 public class CldrNode { 13 createNode(String parent, String pathSegment, String fullPathSegment)14 public static CldrNode createNode(String parent, String pathSegment, 15 String fullPathSegment) throws ParseException { 16 CldrNode node = new CldrNode(); 17 18 node.parent = parent; 19 node.name = extractAttrs(pathSegment, node.distinguishingAttributes); 20 String fullTrunk = extractAttrs(fullPathSegment, 21 node.nondistinguishingAttributes); 22 if (!node.name.equals(fullTrunk)) { 23 throw new ParseException("Error in parsing \"" + pathSegment + " \":\"" + 24 fullPathSegment, 0); 25 } 26 27 for (String key : node.distinguishingAttributes.keySet()) { 28 node.nondistinguishingAttributes.remove(key); 29 } 30 31 String[] suppressList = LdmlConvertRules.ATTR_SUPPRESS_LIST; 32 33 // let's check if there is anything that can be suppressed 34 for (int i = 0; i < suppressList.length; i += 3) { 35 if (node.name.equals(suppressList[i])) { 36 String key = suppressList[i + 2]; 37 String value = node.distinguishingAttributes.get(key); 38 if (value != null && value.equals(suppressList[i + 1])) { 39 node.distinguishingAttributes.remove(key); 40 41 } 42 43 } 44 } 45 return node; 46 } 47 48 /** 49 * Extract all the attributes and their value in the path. 50 * 51 * @param pathSegment 52 * A complete or partial path. 53 * @param attributes 54 * String map to receive attribute mapping. 55 * 56 * @return Part of the string before the first attribute. 57 * @throws ParseException 58 */ extractAttrs(String pathSegment, Map<String, String> attributes)59 private static String extractAttrs(String pathSegment, 60 Map<String, String> attributes) throws ParseException { 61 int start = 0; 62 63 String trunk = new String(); 64 while (true) { 65 int ind1 = pathSegment.indexOf("[@", start); 66 if (ind1 < 0) { 67 if (trunk.isEmpty()) { 68 trunk = pathSegment; 69 } 70 break; 71 } 72 if (trunk.isEmpty()) { 73 trunk = pathSegment.substring(0, ind1); 74 } 75 ind1 += 2; 76 int ind2 = pathSegment.indexOf("=", ind1); 77 if (ind2 < 0) { 78 throw new ParseException("Missing '=' in attribute specification.", 79 ind1); 80 } 81 String attr = pathSegment.substring(ind1, ind2); 82 83 ind1 = ind2 + 1; 84 if (pathSegment.charAt(ind1) == '"') { 85 ind1 += 1; 86 ind2 = pathSegment.indexOf("\"]", ind1); 87 } else { 88 ind2 = pathSegment.indexOf("]", ind1); 89 } 90 91 if (ind2 < 0) { 92 throw new ParseException( 93 "Unexpected end in attribute specification.", ind1); 94 } 95 96 String value = pathSegment.substring(ind1, ind2); 97 98 start = ind2; 99 100 attributes.put(attr, value); 101 } 102 103 return trunk; 104 } 105 106 /** 107 * distinguishing attributes as identified by CLDR tools. 108 */ 109 private Map<String, String> distinguishingAttributes; 110 111 /** 112 * non-distinguishing attributes as identified by CLDR tools. 113 */ 114 private Map<String, String> nondistinguishingAttributes; 115 116 /** 117 * name of the element. 118 */ 119 private String name; 120 121 /** 122 * parent element for this element. 123 */ 124 private String parent; 125 126 /** 127 * This name is derived from element name and attributes. Once it is 128 * calculated, it is cached in this variable. 129 */ 130 private String uniqueNodeName; 131 CldrNode()132 private CldrNode() { 133 distinguishingAttributes = new HashMap<String, String>(); 134 nondistinguishingAttributes = new HashMap<String, String>(); 135 } 136 137 /** 138 * Get the string map for attributes that should be treated as values. 139 * 140 * @return String map. 141 */ getAttrAsValueMap()142 public Map<String, String> getAttrAsValueMap() { 143 Map<String, String> attributesAsValues = new HashMap<String, String>(); 144 for (String key : distinguishingAttributes.keySet()) { 145 String keyStr = parent + ":" + name + ":" + key; 146 if (LdmlConvertRules.ATTR_AS_VALUE_SET.contains(keyStr)) { 147 if (LdmlConvertRules.COMPACTABLE_ATTR_AS_VALUE_SET.contains(keyStr)) { 148 attributesAsValues.put(LdmlConvertRules.ANONYMOUS_KEY, 149 distinguishingAttributes.get(key)); 150 } else { 151 attributesAsValues.put(key, distinguishingAttributes.get(key)); 152 } 153 } 154 } 155 156 for (String key : nondistinguishingAttributes.keySet()) { 157 if (LdmlConvertRules.IGNORABLE_NONDISTINGUISHING_ATTR_SET.contains(key)) { 158 continue; 159 } 160 String keyStr = parent + ":" + name + ":" + key; 161 if (LdmlConvertRules.COMPACTABLE_ATTR_AS_VALUE_SET.contains(keyStr)) { 162 attributesAsValues.put(LdmlConvertRules.ANONYMOUS_KEY, 163 nondistinguishingAttributes.get(key)); 164 } else { 165 attributesAsValues.put(key, nondistinguishingAttributes.get(key)); 166 } 167 } 168 return attributesAsValues; 169 } 170 setDistinguishingAttributes(Map<String, String> distinguishingAttributes)171 public void setDistinguishingAttributes(Map<String, String> distinguishingAttributes) { 172 this.distinguishingAttributes = distinguishingAttributes; 173 } 174 setNondistinguishingAttributes(Map<String, String> nondistinguishingAttributes)175 public void setNondistinguishingAttributes(Map<String, String> nondistinguishingAttributes) { 176 this.nondistinguishingAttributes = nondistinguishingAttributes; 177 } 178 getDistinguishingAttributes()179 public Map<String, String> getDistinguishingAttributes() { 180 return distinguishingAttributes; 181 } 182 getName()183 public String getName() { 184 return name; 185 } 186 getNondistinguishingAttributes()187 public Map<String, String> getNondistinguishingAttributes() { 188 return nondistinguishingAttributes; 189 } 190 191 /** 192 * Construct a name that can be used as key in its container (by 193 * incorporating distinguishing attributes). 194 * 195 * Each segment in CLDR path corresponding to a XML element. Element name 196 * itself can not be used as JSON key because it might not be unique in its 197 * container. A set of rules is used here to construct this key name. Some 198 * of the attributes will be used in constructing the key name, the remaining 199 * attributes are returned and should be used to fill the mapping. 200 * 201 * The basic mapping is from 202 * <element_name>[@<attr_name>=<attr_value>]+ 203 * to 204 * <element_name>-<attr_name>-<attr_value> 205 * 206 * @return A unique name that can be used as key in its container. 207 */ getNodeKeyName()208 public String getNodeKeyName() { 209 if (uniqueNodeName != null) { 210 return uniqueNodeName; 211 } 212 213 // decide the main name 214 StringBuffer strbuf = new StringBuffer(); 215 for (String key : distinguishingAttributes.keySet()) { 216 String attrIdStr = parent + ":" + name + ":" + key; 217 if (LdmlConvertRules.IsSuppresedAttr(attrIdStr)) { 218 continue; 219 } 220 if (LdmlConvertRules.ATTR_AS_VALUE_SET.contains(attrIdStr)) { 221 continue; 222 } 223 224 if (!key.equals("alt") && !key.equals("count") && 225 !LdmlConvertRules.NAME_PART_DISTINGUISHING_ATTR_SET.contains(attrIdStr)) { 226 if (strbuf.length() != 0) { 227 throw new IllegalArgumentException( 228 "Can not have more than 1 key values in name: " + 229 "both '" + strbuf + "' and '" + distinguishingAttributes.get(key) + 230 "'. attrIdStr=" + attrIdStr + " - check LdmlConvertRules.java"); 231 } 232 strbuf.append(distinguishingAttributes.get(key)); 233 } 234 } 235 if (strbuf.length() == 0) { 236 strbuf.append(name); 237 } 238 239 // append distinguishing attributes 240 for (String key : distinguishingAttributes.keySet()) { 241 String attrIdStr = parent + ":" + name + ":" + key; 242 if (LdmlConvertRules.IsSuppresedAttr(attrIdStr)) { 243 continue; 244 } 245 if (LdmlConvertRules.ATTR_AS_VALUE_SET.contains(attrIdStr)) { 246 continue; 247 } 248 249 if (!key.equals("alt") && 250 !LdmlConvertRules.NAME_PART_DISTINGUISHING_ATTR_SET.contains(attrIdStr)) { 251 continue; 252 } 253 strbuf.append("-"); 254 strbuf.append(key); 255 strbuf.append("-"); 256 strbuf.append(distinguishingAttributes.get(key)); 257 } 258 uniqueNodeName = strbuf.toString(); 259 260 if (uniqueNodeName.length() == 1 && name.equals("character")) { 261 // character attribute has value that can be any unicode character. Those 262 // might not be url safe and can be difficult for user to specify. It is 263 // converted to hex string here. 264 uniqueNodeName = "U+" + Utility.hex(uniqueNodeName.charAt(0), 4); 265 } else if (isTimezoneType()) { 266 // time zone name has GMT+9 type of thing. "+" need to be removed to make 267 // it URL safe. 268 uniqueNodeName = uniqueNodeName.replaceFirst("\\+", ""); 269 } 270 271 return uniqueNodeName; 272 } 273 274 /** 275 * Construct a name that has all distinguishing attributes that should not be 276 * ignored. 277 * 278 * Different from getNodeKeyName, this name has include those distinguishing 279 * attributes that will be treated as values. 280 * 281 * @return A distinguishing name for differentiating element. 282 */ getNodeDistinguishingName()283 public String getNodeDistinguishingName() { 284 // decide the main name 285 StringBuffer strbuf = new StringBuffer(); 286 strbuf.append(name); 287 288 // append distinguishing attributes 289 for (String key : distinguishingAttributes.keySet()) { 290 strbuf.append("-"); 291 strbuf.append(key); 292 strbuf.append("-"); 293 strbuf.append(distinguishingAttributes.get(key)); 294 } 295 return strbuf.toString(); 296 } 297 isTimezoneType()298 public boolean isTimezoneType() { 299 return LdmlConvertRules.TIMEZONE_ELEMENT_NAME_SET.contains(name); 300 } 301 } 302