Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
XML |
|
| 11.5;11.5 |
1 | package org.apache.tapestry.json; |
|
2 | ||
3 | /* |
|
4 | Copyright (c) 2002 JSON.org |
|
5 | ||
6 | Permission is hereby granted, free of charge, to any person obtaining a copy |
|
7 | of this software and associated documentation files (the "Software"), to deal |
|
8 | in the Software without restriction, including without limitation the rights |
|
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
10 | copies of the Software, and to permit persons to whom the Software is |
|
11 | furnished to do so, subject to the following conditions: |
|
12 | ||
13 | The above copyright notice and this permission notice shall be included in all |
|
14 | copies or substantial portions of the Software. |
|
15 | ||
16 | The Software shall be used for Good, not Evil. |
|
17 | ||
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
24 | SOFTWARE. |
|
25 | */ |
|
26 | ||
27 | import java.text.ParseException; |
|
28 | import java.util.Iterator; |
|
29 | ||
30 | ||
31 | /** |
|
32 | * This provides static methods to convert an XML text into a JSONObject, |
|
33 | * and to covert a JSONObject into an XML text. |
|
34 | * @author JSON.org |
|
35 | * @version 0.1 |
|
36 | */ |
|
37 | public final class XML { |
|
38 | ||
39 | /** The Character '&'. */ |
|
40 | 0 | public static final Character AMP = new Character('&'); |
41 | ||
42 | /** The Character '''. */ |
|
43 | 0 | public static final Character APOS = new Character('\''); |
44 | ||
45 | /** The Character '!'. */ |
|
46 | 0 | public static final Character BANG = new Character('!'); |
47 | ||
48 | /** The Character '='. */ |
|
49 | 0 | public static final Character EQ = new Character('='); |
50 | ||
51 | /** The Character '>'. */ |
|
52 | 0 | public static final Character GT = new Character('>'); |
53 | ||
54 | /** The Character '<'. */ |
|
55 | 0 | public static final Character LT = new Character('<'); |
56 | ||
57 | /** The Character '?'. */ |
|
58 | 0 | public static final Character QUEST = new Character('?'); |
59 | ||
60 | /** The Character '"'. */ |
|
61 | 0 | public static final Character QUOT = new Character('"'); |
62 | ||
63 | /** The Character '/'. */ |
|
64 | 0 | public static final Character SLASH = new Character('/'); |
65 | ||
66 | /* defeat instantiation */ |
|
67 | 0 | private XML() { } |
68 | ||
69 | /** |
|
70 | * Replace special characters with XML escapes. : |
|
71 | * <pre> |
|
72 | * & is replaced by &amp; |
|
73 | * < is replaced by &lt; |
|
74 | * > is replaced by &gt; |
|
75 | * " is replaced by &quot; |
|
76 | * </pre> |
|
77 | * @param string The string to be escaped. |
|
78 | * @return The escaped string. |
|
79 | */ |
|
80 | public static String escape(String string) { |
|
81 | 0 | return string |
82 | .replaceAll("&", "&") |
|
83 | .replaceAll("<", "<") |
|
84 | .replaceAll(">", ">") |
|
85 | .replaceAll("\"", """); |
|
86 | } |
|
87 | ||
88 | /** |
|
89 | * Scan the content following the named tag, attaching it to the context. |
|
90 | * @param x The XMLTokener containing the source string. |
|
91 | * @param context The JSONObject that will include the new material. |
|
92 | * @param name The tag name. |
|
93 | * @return true if the close tag is processed. |
|
94 | * @throws ParseException |
|
95 | */ |
|
96 | private static boolean parse(XMLTokener x, JSONObject context, |
|
97 | String name) throws ParseException { |
|
98 | char c; |
|
99 | int i; |
|
100 | String n; |
|
101 | JSONObject o; |
|
102 | String s; |
|
103 | Object t; |
|
104 | ||
105 | // Test for and skip past these forms: |
|
106 | // <!-- ... --> |
|
107 | // <! ... > |
|
108 | // <![ ... ]]> |
|
109 | // <? ... ?> |
|
110 | // Report errors for these forms: |
|
111 | // <> |
|
112 | // <= |
|
113 | // << |
|
114 | ||
115 | 0 | t = x.nextToken(); |
116 | ||
117 | // <! |
|
118 | ||
119 | 0 | if (t == BANG) { |
120 | 0 | c = x.next(); |
121 | 0 | if (c == '-') { |
122 | 0 | if (x.next() == '-') { |
123 | 0 | x.skipPast("-->"); |
124 | 0 | return false; |
125 | } |
|
126 | 0 | x.back(); |
127 | 0 | } else if (c == '[') { |
128 | 0 | x.skipPast("]]>"); |
129 | 0 | return false; |
130 | } |
|
131 | 0 | i = 1; |
132 | do { |
|
133 | 0 | t = x.nextMeta(); |
134 | 0 | if (t == null) { |
135 | 0 | throw x.syntaxError("Missing '>' after '<!'."); |
136 | 0 | } else if (t == LT) { |
137 | 0 | i += 1; |
138 | 0 | } else if (t == GT) { |
139 | 0 | i -= 1; |
140 | } |
|
141 | 0 | } while (i > 0); |
142 | 0 | return false; |
143 | 0 | } else if (t == QUEST) { |
144 | ||
145 | // <? |
|
146 | ||
147 | 0 | x.skipPast("?>"); |
148 | 0 | return false; |
149 | 0 | } else if (t == SLASH) { |
150 | ||
151 | // Close tag </ |
|
152 | ||
153 | 0 | if (name == null || !x.nextToken().equals(name)) { |
154 | 0 | throw x.syntaxError("Mismatched close tag"); |
155 | } |
|
156 | 0 | if (x.nextToken() != GT) { |
157 | 0 | throw x.syntaxError("Misshaped close tag"); |
158 | } |
|
159 | 0 | return true; |
160 | ||
161 | 0 | } else if (t instanceof Character) { |
162 | 0 | throw x.syntaxError("Misshaped tag"); |
163 | ||
164 | // Open tag < |
|
165 | ||
166 | } else { |
|
167 | 0 | n = (String)t; |
168 | 0 | t = null; |
169 | 0 | o = new JSONObject(); |
170 | while (true) { |
|
171 | 0 | if (t == null) { |
172 | 0 | t = x.nextToken(); |
173 | } |
|
174 | ||
175 | // attribute = value |
|
176 | ||
177 | 0 | if (t instanceof String) { |
178 | 0 | s = (String)t; |
179 | 0 | t = x.nextToken(); |
180 | 0 | if (t == EQ) { |
181 | 0 | t = x.nextToken(); |
182 | 0 | if (!(t instanceof String)) { |
183 | 0 | throw x.syntaxError("Missing value"); |
184 | } |
|
185 | 0 | o.accumulate(s, t); |
186 | 0 | t = null; |
187 | } else { |
|
188 | 0 | o.accumulate(s, Boolean.TRUE); |
189 | } |
|
190 | ||
191 | // Empty tag <.../> |
|
192 | ||
193 | 0 | } else if (t == SLASH) { |
194 | 0 | if (x.nextToken() != GT) { |
195 | 0 | throw x.syntaxError("Misshaped tag"); |
196 | } |
|
197 | 0 | if (o.length() == 0) { |
198 | 0 | context.accumulate(n, Boolean.TRUE); |
199 | } else { |
|
200 | 0 | context.accumulate(n, o); |
201 | } |
|
202 | 0 | return false; |
203 | ||
204 | // Content, between <...> and </...> |
|
205 | ||
206 | 0 | } else if (t == GT) { |
207 | while (true) { |
|
208 | 0 | t = x.nextContent(); |
209 | 0 | if (t == null) { |
210 | 0 | if (name != null) { |
211 | 0 | throw x.syntaxError("Unclosed tag " + name); |
212 | } |
|
213 | 0 | return false; |
214 | 0 | } else if (t instanceof String) { |
215 | 0 | s = (String)t; |
216 | 0 | if (s.length() > 0) { |
217 | 0 | o.accumulate("content", s); |
218 | } |
|
219 | ||
220 | // Nested element |
|
221 | ||
222 | 0 | } else if (t == LT) { |
223 | 0 | if (parse(x, o, n)) { |
224 | 0 | if (o.length() == 0) { |
225 | 0 | context.accumulate(n, Boolean.TRUE); |
226 | 0 | } else if (o.length() == 1 && |
227 | o.opt("content") != null) { |
|
228 | 0 | context.accumulate(n, o.opt("content")); |
229 | } else { |
|
230 | 0 | context.accumulate(n, o); |
231 | } |
|
232 | 0 | return false; |
233 | } |
|
234 | } |
|
235 | } |
|
236 | } else { |
|
237 | 0 | throw x.syntaxError("Misshaped tag"); |
238 | } |
|
239 | } |
|
240 | } |
|
241 | } |
|
242 | ||
243 | ||
244 | /** |
|
245 | * Convert a well-formed (but not necessarily valid) XML string into a |
|
246 | * JSONObject. Some information may be lost in this transformation |
|
247 | * because JSON is a data format and XML is a document format. XML uses |
|
248 | * elements, attributes, and content text, while JSON uses unordered |
|
249 | * collections of name/value pairs and arrays of values. JSON does not |
|
250 | * does not like to distinguish between elements and attributes. |
|
251 | * Sequences of similar elements are represented as JSONArrays. Content |
|
252 | * text may be placed in a "content" member. Comments, prologs, DTDs, and |
|
253 | * <code><[ [ ]]></code> are ignored. |
|
254 | * @param string The source string. |
|
255 | * @return A JSONObject containing the structured data from the XML string. |
|
256 | * @throws ParseException |
|
257 | */ |
|
258 | public static JSONObject toJSONObject(String string) throws ParseException { |
|
259 | 0 | JSONObject o = new JSONObject(); |
260 | 0 | XMLTokener x = new XMLTokener(string); |
261 | 0 | while (x.more()) { |
262 | 0 | x.skipPast("<"); |
263 | 0 | parse(x, o, null); |
264 | } |
|
265 | 0 | return o; |
266 | } |
|
267 | ||
268 | ||
269 | /** |
|
270 | * Convert a JSONObject into a well-formed XML string. |
|
271 | * @param o A JSONObject. |
|
272 | * @return A string. |
|
273 | */ |
|
274 | public static String toString(Object o) { |
|
275 | 0 | return toString(o, null); |
276 | } |
|
277 | ||
278 | ||
279 | /** |
|
280 | * Convert a JSONObject into a well-formed XML string. |
|
281 | * @param o A JSONObject. |
|
282 | * @param tagName The optional name of the enclosing tag. |
|
283 | * @return A string. |
|
284 | */ |
|
285 | public static String toString(Object o, String tagName) { |
|
286 | 0 | StringBuffer a = null; // attributes, inside the <...> |
287 | 0 | StringBuffer b = new StringBuffer(); // body, between <...> and </...> |
288 | int i; |
|
289 | JSONArray ja; |
|
290 | JSONObject jo; |
|
291 | String k; |
|
292 | Iterator keys; |
|
293 | int len; |
|
294 | String s; |
|
295 | Object v; |
|
296 | 0 | if (o instanceof JSONObject) { |
297 | ||
298 | // Emit <tagName |
|
299 | ||
300 | 0 | if (tagName != null) { |
301 | 0 | a = new StringBuffer(); |
302 | 0 | a.append('<'); |
303 | 0 | a.append(tagName); |
304 | } |
|
305 | ||
306 | // Loop thru the keys. Some keys will produce attribute material, others |
|
307 | // body material. |
|
308 | ||
309 | 0 | jo = (JSONObject)o; |
310 | 0 | keys = jo.keys(); |
311 | 0 | while (keys.hasNext()) { |
312 | 0 | k = keys.next().toString(); |
313 | 0 | v = jo.get(k); |
314 | 0 | if (v instanceof String) { |
315 | 0 | s = (String)v; |
316 | } else { |
|
317 | 0 | s = null; |
318 | } |
|
319 | ||
320 | // Emit a new tag <k... in body |
|
321 | ||
322 | 0 | if (tagName == null || v instanceof JSONObject || |
323 | (s != null && !k.equals("content") && (s.length() > 60 || |
|
324 | (s.indexOf('"') >= 0 && s.indexOf('\'') >= 0)))) { |
|
325 | 0 | b.append(toString(v, k)); |
326 | ||
327 | // Emit content in body |
|
328 | ||
329 | 0 | } else if (k.equals("content")) { |
330 | 0 | b.append(escape(v.toString())); |
331 | ||
332 | // Emit an array of similar keys in body |
|
333 | ||
334 | 0 | } else if (v instanceof JSONArray) { |
335 | 0 | ja = (JSONArray)v; |
336 | 0 | len = ja.length(); |
337 | 0 | for (i = 0; i < len; i += 1) { |
338 | 0 | b.append(toString(ja.get(i), k)); |
339 | } |
|
340 | ||
341 | // Emit an attribute |
|
342 | ||
343 | } else { |
|
344 | 0 | a.append(' '); |
345 | 0 | a.append(k); |
346 | 0 | a.append('='); |
347 | 0 | a.append(toString(v)); |
348 | } |
|
349 | } |
|
350 | 0 | if (tagName != null) { |
351 | ||
352 | // Close an empty element |
|
353 | ||
354 | 0 | if (b.length() == 0) { |
355 | 0 | a.append("/>"); |
356 | } else { |
|
357 | ||
358 | // Close the start tag and emit the body and the close tag |
|
359 | ||
360 | 0 | a.append('>'); |
361 | 0 | a.append(b); |
362 | 0 | a.append("</"); |
363 | 0 | a.append(tagName); |
364 | 0 | a.append('>'); |
365 | } |
|
366 | 0 | return a.toString(); |
367 | } |
|
368 | 0 | return b.toString(); |
369 | ||
370 | // XML does not have good support for arrays. If an array appears in a place |
|
371 | // where XML is lacking, synthesize an <array> element. |
|
372 | ||
373 | 0 | } else if (o instanceof JSONArray) { |
374 | 0 | ja = (JSONArray)o; |
375 | 0 | len = ja.length(); |
376 | 0 | for (i = 0; i < len; ++i) { |
377 | 0 | b.append(toString( |
378 | ja.opt(i), (tagName == null) ? "array" : tagName)); |
|
379 | } |
|
380 | 0 | return b.toString(); |
381 | } else { |
|
382 | 0 | s = (o == null) ? "null" : escape(o.toString()); |
383 | 0 | return (tagName == null) ? |
384 | "\"" + s + "\"" : |
|
385 | "<" + tagName + ">" + s + "</" + tagName + ">"; |
|
386 | } |
|
387 | } |
|
388 | } |