| package org.unicode.cldr.json; |
| |
| import java.text.ParseException; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import com.ibm.icu.impl.Utility; |
| |
| /** |
| * CldrNode represent a Element in XML as it appears in a CldrItem's path. |
| */ |
| public class CldrNode { |
| |
| public static CldrNode createNode(String parent, String pathSegment, |
| String fullPathSegment) throws ParseException { |
| CldrNode node = new CldrNode(); |
| |
| node.parent = parent; |
| node.name = extractAttrs(pathSegment, node.distinguishingAttributes); |
| String fullTrunk = extractAttrs(fullPathSegment, |
| node.nondistinguishingAttributes); |
| if (!node.name.equals(fullTrunk)) { |
| throw new ParseException("Error in parsing \"" + pathSegment + " \":\"" + |
| fullPathSegment, 0); |
| } |
| |
| for (String key : node.distinguishingAttributes.keySet()) { |
| node.nondistinguishingAttributes.remove(key); |
| } |
| |
| String[] suppressList = LdmlConvertRules.ATTR_SUPPRESS_LIST; |
| |
| // let's check if there is anything that can be suppressed |
| for (int i = 0; i < suppressList.length; i += 3) { |
| if (node.name.equals(suppressList[i])) { |
| String key = suppressList[i + 2]; |
| String value = node.distinguishingAttributes.get(key); |
| if (value != null && value.equals(suppressList[i + 1])) { |
| node.distinguishingAttributes.remove(key); |
| |
| } |
| |
| } |
| } |
| return node; |
| } |
| |
| /** |
| * Extract all the attributes and their value in the path. |
| * |
| * @param pathSegment |
| * A complete or partial path. |
| * @param attributes |
| * String map to receive attribute mapping. |
| * |
| * @return Part of the string before the first attribute. |
| * @throws ParseException |
| */ |
| private static String extractAttrs(String pathSegment, |
| Map<String, String> attributes) throws ParseException { |
| int start = 0; |
| |
| String trunk = new String(); |
| while (true) { |
| int ind1 = pathSegment.indexOf("[@", start); |
| if (ind1 < 0) { |
| if (trunk.isEmpty()) { |
| trunk = pathSegment; |
| } |
| break; |
| } |
| if (trunk.isEmpty()) { |
| trunk = pathSegment.substring(0, ind1); |
| } |
| ind1 += 2; |
| int ind2 = pathSegment.indexOf("=", ind1); |
| if (ind2 < 0) { |
| throw new ParseException("Missing '=' in attribute specification.", |
| ind1); |
| } |
| String attr = pathSegment.substring(ind1, ind2); |
| |
| ind1 = ind2 + 1; |
| if (pathSegment.charAt(ind1) == '"') { |
| ind1 += 1; |
| ind2 = pathSegment.indexOf("\"]", ind1); |
| } else { |
| ind2 = pathSegment.indexOf("]", ind1); |
| } |
| |
| if (ind2 < 0) { |
| throw new ParseException( |
| "Unexpected end in attribute specification.", ind1); |
| } |
| |
| String value = pathSegment.substring(ind1, ind2); |
| |
| start = ind2; |
| |
| attributes.put(attr, value); |
| } |
| |
| return trunk; |
| } |
| |
| /** |
| * distinguishing attributes as identified by CLDR tools. |
| */ |
| private Map<String, String> distinguishingAttributes; |
| |
| /** |
| * non-distinguishing attributes as identified by CLDR tools. |
| */ |
| private Map<String, String> nondistinguishingAttributes; |
| |
| /** |
| * name of the element. |
| */ |
| private String name; |
| |
| /** |
| * parent element for this element. |
| */ |
| private String parent; |
| |
| /** |
| * This name is derived from element name and attributes. Once it is |
| * calculated, it is cached in this variable. |
| */ |
| private String uniqueNodeName; |
| |
| private CldrNode() { |
| distinguishingAttributes = new HashMap<String, String>(); |
| nondistinguishingAttributes = new HashMap<String, String>(); |
| } |
| |
| /** |
| * Get the string map for attributes that should be treated as values. |
| * |
| * @return String map. |
| */ |
| public Map<String, String> getAttrAsValueMap() { |
| Map<String, String> attributesAsValues = new HashMap<String, String>(); |
| for (String key : distinguishingAttributes.keySet()) { |
| String keyStr = parent + ":" + name + ":" + key; |
| if (LdmlConvertRules.ATTR_AS_VALUE_SET.contains(keyStr)) { |
| if (LdmlConvertRules.COMPACTABLE_ATTR_AS_VALUE_SET.contains(keyStr)) { |
| attributesAsValues.put(LdmlConvertRules.ANONYMOUS_KEY, |
| distinguishingAttributes.get(key)); |
| } else { |
| attributesAsValues.put(key, distinguishingAttributes.get(key)); |
| } |
| } |
| } |
| |
| for (String key : nondistinguishingAttributes.keySet()) { |
| if (LdmlConvertRules.IGNORABLE_NONDISTINGUISHING_ATTR_SET.contains(key)) { |
| continue; |
| } |
| String keyStr = parent + ":" + name + ":" + key; |
| if (LdmlConvertRules.COMPACTABLE_ATTR_AS_VALUE_SET.contains(keyStr)) { |
| attributesAsValues.put(LdmlConvertRules.ANONYMOUS_KEY, |
| nondistinguishingAttributes.get(key)); |
| } else { |
| attributesAsValues.put(key, nondistinguishingAttributes.get(key)); |
| } |
| } |
| return attributesAsValues; |
| } |
| |
| public void setDistinguishingAttributes(Map<String, String> distinguishingAttributes) { |
| this.distinguishingAttributes = distinguishingAttributes; |
| } |
| |
| public void setNondistinguishingAttributes(Map<String, String> nondistinguishingAttributes) { |
| this.nondistinguishingAttributes = nondistinguishingAttributes; |
| } |
| |
| public Map<String, String> getDistinguishingAttributes() { |
| return distinguishingAttributes; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| public Map<String, String> getNondistinguishingAttributes() { |
| return nondistinguishingAttributes; |
| } |
| |
| /** |
| * Construct a name that can be used as key in its container (by |
| * incorporating distinguishing attributes). |
| * |
| * Each segment in CLDR path corresponding to a XML element. Element name |
| * itself can not be used as JSON key because it might not be unique in its |
| * container. A set of rules is used here to construct this key name. Some |
| * of the attributes will be used in constructing the key name, the remaining |
| * attributes are returned and should be used to fill the mapping. |
| * |
| * The basic mapping is from |
| * <element_name>[@<attr_name>=<attr_value>]+ |
| * to |
| * <element_name>-<attr_name>-<attr_value> |
| * |
| * @return A unique name that can be used as key in its container. |
| */ |
| public String getNodeKeyName() { |
| if (uniqueNodeName != null) { |
| return uniqueNodeName; |
| } |
| |
| // decide the main name |
| StringBuffer strbuf = new StringBuffer(); |
| for (String key : distinguishingAttributes.keySet()) { |
| String attrIdStr = parent + ":" + name + ":" + key; |
| if (LdmlConvertRules.IsSuppresedAttr(attrIdStr)) { |
| continue; |
| } |
| if (LdmlConvertRules.ATTR_AS_VALUE_SET.contains(attrIdStr)) { |
| continue; |
| } |
| |
| if (!key.equals("alt") && !key.equals("count") && |
| !LdmlConvertRules.NAME_PART_DISTINGUISHING_ATTR_SET.contains(attrIdStr)) { |
| if (strbuf.length() != 0) { |
| throw new IllegalArgumentException( |
| "Can not have more than 1 key values in name: " + |
| "both '" + strbuf + "' and '" + distinguishingAttributes.get(key) + |
| "'. attrIdStr=" + attrIdStr + " - check LdmlConvertRules.java"); |
| } |
| strbuf.append(distinguishingAttributes.get(key)); |
| } |
| } |
| if (strbuf.length() == 0) { |
| strbuf.append(name); |
| } |
| |
| // append distinguishing attributes |
| for (String key : distinguishingAttributes.keySet()) { |
| String attrIdStr = parent + ":" + name + ":" + key; |
| if (LdmlConvertRules.IsSuppresedAttr(attrIdStr)) { |
| continue; |
| } |
| if (LdmlConvertRules.ATTR_AS_VALUE_SET.contains(attrIdStr)) { |
| continue; |
| } |
| |
| if (!key.equals("alt") && |
| !LdmlConvertRules.NAME_PART_DISTINGUISHING_ATTR_SET.contains(attrIdStr)) { |
| continue; |
| } |
| strbuf.append("-"); |
| strbuf.append(key); |
| strbuf.append("-"); |
| strbuf.append(distinguishingAttributes.get(key)); |
| } |
| uniqueNodeName = strbuf.toString(); |
| |
| if (uniqueNodeName.length() == 1 && name.equals("character")) { |
| // character attribute has value that can be any unicode character. Those |
| // might not be url safe and can be difficult for user to specify. It is |
| // converted to hex string here. |
| uniqueNodeName = "U+" + Utility.hex(uniqueNodeName.charAt(0), 4); |
| } else if (isTimezoneType()) { |
| // time zone name has GMT+9 type of thing. "+" need to be removed to make |
| // it URL safe. |
| uniqueNodeName = uniqueNodeName.replaceFirst("\\+", ""); |
| } |
| |
| return uniqueNodeName; |
| } |
| |
| /** |
| * Construct a name that has all distinguishing attributes that should not be |
| * ignored. |
| * |
| * Different from getNodeKeyName, this name has include those distinguishing |
| * attributes that will be treated as values. |
| * |
| * @return A distinguishing name for differentiating element. |
| */ |
| public String getNodeDistinguishingName() { |
| // decide the main name |
| StringBuffer strbuf = new StringBuffer(); |
| strbuf.append(name); |
| |
| // append distinguishing attributes |
| for (String key : distinguishingAttributes.keySet()) { |
| strbuf.append("-"); |
| strbuf.append(key); |
| strbuf.append("-"); |
| strbuf.append(distinguishingAttributes.get(key)); |
| } |
| return strbuf.toString(); |
| } |
| |
| public boolean isTimezoneType() { |
| return LdmlConvertRules.TIMEZONE_ELEMENT_NAME_SET.contains(name); |
| } |
| } |