Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
XML |
|
| 13.333333333333334;13.333 |
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 | } |