• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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