0
|
1 package org.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.util.Iterator;
|
|
28
|
|
29
|
|
30 /**
|
|
31 * This provides static methods to convert an XML text into a JSONObject,
|
|
32 * and to covert a JSONObject into an XML text.
|
|
33 * @author JSON.org
|
|
34 * @version 2008-10-14
|
|
35 */
|
|
36 public class XML {
|
|
37
|
|
38 /** The Character '&'. */
|
|
39 public static final Character AMP = new Character('&');
|
|
40
|
|
41 /** The Character '''. */
|
|
42 public static final Character APOS = new Character('\'');
|
|
43
|
|
44 /** The Character '!'. */
|
|
45 public static final Character BANG = new Character('!');
|
|
46
|
|
47 /** The Character '='. */
|
|
48 public static final Character EQ = new Character('=');
|
|
49
|
|
50 /** The Character '>'. */
|
|
51 public static final Character GT = new Character('>');
|
|
52
|
|
53 /** The Character '<'. */
|
|
54 public static final Character LT = new Character('<');
|
|
55
|
|
56 /** The Character '?'. */
|
|
57 public static final Character QUEST = new Character('?');
|
|
58
|
|
59 /** The Character '"'. */
|
|
60 public static final Character QUOT = new Character('"');
|
|
61
|
|
62 /** The Character '/'. */
|
|
63 public static final Character SLASH = new Character('/');
|
|
64
|
|
65 /**
|
|
66 * Replace special characters with XML escapes:
|
|
67 * <pre>
|
|
68 * & <small>(ampersand)</small> is replaced by &amp;
|
|
69 * < <small>(less than)</small> is replaced by &lt;
|
|
70 * > <small>(greater than)</small> is replaced by &gt;
|
|
71 * " <small>(double quote)</small> is replaced by &quot;
|
|
72 * </pre>
|
|
73 * @param string The string to be escaped.
|
|
74 * @return The escaped string.
|
|
75 */
|
|
76 public static String escape(String string) {
|
|
77 StringBuffer sb = new StringBuffer();
|
|
78 for (int i = 0, len = string.length(); i < len; i++) {
|
|
79 char c = string.charAt(i);
|
|
80 switch (c) {
|
|
81 case '&':
|
|
82 sb.append("&");
|
|
83 break;
|
|
84 case '<':
|
|
85 sb.append("<");
|
|
86 break;
|
|
87 case '>':
|
|
88 sb.append(">");
|
|
89 break;
|
|
90 case '"':
|
|
91 sb.append(""");
|
|
92 break;
|
|
93 default:
|
|
94 sb.append(c);
|
|
95 }
|
|
96 }
|
|
97 return sb.toString();
|
|
98 }
|
|
99
|
|
100 /**
|
|
101 * Throw an exception if the string contains whitespace.
|
|
102 * Whitespace is not allowed in tagNames and attributes.
|
|
103 * @param string
|
|
104 * @throws JSONException
|
|
105 */
|
|
106 public static void noSpace(String string) throws JSONException {
|
|
107 int i, length = string.length();
|
|
108 if (length == 0) {
|
|
109 throw new JSONException("Empty string.");
|
|
110 }
|
|
111 for (i = 0; i < length; i += 1) {
|
|
112 if (Character.isWhitespace(string.charAt(i))) {
|
|
113 throw new JSONException("'" + string +
|
|
114 "' contains a space character.");
|
|
115 }
|
|
116 }
|
|
117 }
|
|
118
|
|
119 /**
|
|
120 * Scan the content following the named tag, attaching it to the context.
|
|
121 * @param x The XMLTokener containing the source string.
|
|
122 * @param context The JSONObject that will include the new material.
|
|
123 * @param name The tag name.
|
|
124 * @return true if the close tag is processed.
|
|
125 * @throws JSONException
|
|
126 */
|
|
127 private static boolean parse(XMLTokener x, JSONObject context,
|
|
128 String name) throws JSONException {
|
|
129 char c;
|
|
130 int i;
|
|
131 String n;
|
|
132 JSONObject o = null;
|
|
133 String s;
|
|
134 Object t;
|
|
135
|
|
136 // Test for and skip past these forms:
|
|
137 // <!-- ... -->
|
|
138 // <! ... >
|
|
139 // <![ ... ]]>
|
|
140 // <? ... ?>
|
|
141 // Report errors for these forms:
|
|
142 // <>
|
|
143 // <=
|
|
144 // <<
|
|
145
|
|
146 t = x.nextToken();
|
|
147
|
|
148 // <!
|
|
149
|
|
150 if (t == BANG) {
|
|
151 c = x.next();
|
|
152 if (c == '-') {
|
|
153 if (x.next() == '-') {
|
|
154 x.skipPast("-->");
|
|
155 return false;
|
|
156 }
|
|
157 x.back();
|
|
158 } else if (c == '[') {
|
|
159 t = x.nextToken();
|
|
160 if (t.equals("CDATA")) {
|
|
161 if (x.next() == '[') {
|
|
162 s = x.nextCDATA();
|
|
163 if (s.length() > 0) {
|
|
164 context.accumulate("content", s);
|
|
165 }
|
|
166 return false;
|
|
167 }
|
|
168 }
|
|
169 throw x.syntaxError("Expected 'CDATA['");
|
|
170 }
|
|
171 i = 1;
|
|
172 do {
|
|
173 t = x.nextMeta();
|
|
174 if (t == null) {
|
|
175 throw x.syntaxError("Missing '>' after '<!'.");
|
|
176 } else if (t == LT) {
|
|
177 i += 1;
|
|
178 } else if (t == GT) {
|
|
179 i -= 1;
|
|
180 }
|
|
181 } while (i > 0);
|
|
182 return false;
|
|
183 } else if (t == QUEST) {
|
|
184
|
|
185 // <?
|
|
186
|
|
187 x.skipPast("?>");
|
|
188 return false;
|
|
189 } else if (t == SLASH) {
|
|
190
|
|
191 // Close tag </
|
|
192
|
|
193 t = x.nextToken();
|
|
194 if (name == null) {
|
|
195 throw x.syntaxError("Mismatched close tag" + t);
|
|
196 }
|
|
197 if (!t.equals(name)) {
|
|
198 throw x.syntaxError("Mismatched " + name + " and " + t);
|
|
199 }
|
|
200 if (x.nextToken() != GT) {
|
|
201 throw x.syntaxError("Misshaped close tag");
|
|
202 }
|
|
203 return true;
|
|
204
|
|
205 } else if (t instanceof Character) {
|
|
206 throw x.syntaxError("Misshaped tag");
|
|
207
|
|
208 // Open tag <
|
|
209
|
|
210 } else {
|
|
211 n = (String)t;
|
|
212 t = null;
|
|
213 o = new JSONObject();
|
|
214 for (;;) {
|
|
215 if (t == null) {
|
|
216 t = x.nextToken();
|
|
217 }
|
|
218
|
|
219 // attribute = value
|
|
220
|
|
221 if (t instanceof String) {
|
|
222 s = (String)t;
|
|
223 t = x.nextToken();
|
|
224 if (t == EQ) {
|
|
225 t = x.nextToken();
|
|
226 if (!(t instanceof String)) {
|
|
227 throw x.syntaxError("Missing value");
|
|
228 }
|
|
229 o.accumulate(s, JSONObject.stringToValue((String)t));
|
|
230 t = null;
|
|
231 } else {
|
|
232 o.accumulate(s, "");
|
|
233 }
|
|
234
|
|
235 // Empty tag <.../>
|
|
236
|
|
237 } else if (t == SLASH) {
|
|
238 if (x.nextToken() != GT) {
|
|
239 throw x.syntaxError("Misshaped tag");
|
|
240 }
|
|
241 context.accumulate(n, o);
|
|
242 return false;
|
|
243
|
|
244 // Content, between <...> and </...>
|
|
245
|
|
246 } else if (t == GT) {
|
|
247 for (;;) {
|
|
248 t = x.nextContent();
|
|
249 if (t == null) {
|
|
250 if (n != null) {
|
|
251 throw x.syntaxError("Unclosed tag " + n);
|
|
252 }
|
|
253 return false;
|
|
254 } else if (t instanceof String) {
|
|
255 s = (String)t;
|
|
256 if (s.length() > 0) {
|
|
257 o.accumulate("content", JSONObject.stringToValue(s));
|
|
258 }
|
|
259
|
|
260 // Nested element
|
|
261
|
|
262 } else if (t == LT) {
|
|
263 if (parse(x, o, n)) {
|
|
264 if (o.length() == 0) {
|
|
265 context.accumulate(n, "");
|
|
266 } else if (o.length() == 1 &&
|
|
267 o.opt("content") != null) {
|
|
268 context.accumulate(n, o.opt("content"));
|
|
269 } else {
|
|
270 context.accumulate(n, o);
|
|
271 }
|
|
272 return false;
|
|
273 }
|
|
274 }
|
|
275 }
|
|
276 } else {
|
|
277 throw x.syntaxError("Misshaped tag");
|
|
278 }
|
|
279 }
|
|
280 }
|
|
281 }
|
|
282
|
|
283
|
|
284 /**
|
|
285 * Convert a well-formed (but not necessarily valid) XML string into a
|
|
286 * JSONObject. Some information may be lost in this transformation
|
|
287 * because JSON is a data format and XML is a document format. XML uses
|
|
288 * elements, attributes, and content text, while JSON uses unordered
|
|
289 * collections of name/value pairs and arrays of values. JSON does not
|
|
290 * does not like to distinguish between elements and attributes.
|
|
291 * Sequences of similar elements are represented as JSONArrays. Content
|
|
292 * text may be placed in a "content" member. Comments, prologs, DTDs, and
|
|
293 * <code><[ [ ]]></code> are ignored.
|
|
294 * @param string The source string.
|
|
295 * @return A JSONObject containing the structured data from the XML string.
|
|
296 * @throws JSONException
|
|
297 */
|
|
298 public static JSONObject toJSONObject(String string) throws JSONException {
|
|
299 JSONObject o = new JSONObject();
|
|
300 XMLTokener x = new XMLTokener(string);
|
|
301 while (x.more() && x.skipPast("<")) {
|
|
302 parse(x, o, null);
|
|
303 }
|
|
304 return o;
|
|
305 }
|
|
306
|
|
307
|
|
308 /**
|
|
309 * Convert a JSONObject into a well-formed, element-normal XML string.
|
|
310 * @param o A JSONObject.
|
|
311 * @return A string.
|
|
312 * @throws JSONException
|
|
313 */
|
|
314 public static String toString(Object o) throws JSONException {
|
|
315 return toString(o, null);
|
|
316 }
|
|
317
|
|
318
|
|
319 /**
|
|
320 * Convert a JSONObject into a well-formed, element-normal XML string.
|
|
321 * @param o A JSONObject.
|
|
322 * @param tagName The optional name of the enclosing tag.
|
|
323 * @return A string.
|
|
324 * @throws JSONException
|
|
325 */
|
|
326 public static String toString(Object o, String tagName)
|
|
327 throws JSONException {
|
|
328 StringBuffer b = new StringBuffer();
|
|
329 int i;
|
|
330 JSONArray ja;
|
|
331 JSONObject jo;
|
|
332 String k;
|
|
333 Iterator keys;
|
|
334 int len;
|
|
335 String s;
|
|
336 Object v;
|
|
337 if (o instanceof JSONObject) {
|
|
338
|
|
339 // Emit <tagName>
|
|
340
|
|
341 if (tagName != null) {
|
|
342 b.append('<');
|
|
343 b.append(tagName);
|
|
344 b.append('>');
|
|
345 }
|
|
346
|
|
347 // Loop thru the keys.
|
|
348
|
|
349 jo = (JSONObject)o;
|
|
350 keys = jo.keys();
|
|
351 while (keys.hasNext()) {
|
|
352 k = keys.next().toString();
|
|
353 v = jo.opt(k);
|
|
354 if (v == null) {
|
|
355 v = "";
|
|
356 }
|
|
357 if (v instanceof String) {
|
|
358 s = (String)v;
|
|
359 } else {
|
|
360 s = null;
|
|
361 }
|
|
362
|
|
363 // Emit content in body
|
|
364
|
|
365 if (k.equals("content")) {
|
|
366 if (v instanceof JSONArray) {
|
|
367 ja = (JSONArray)v;
|
|
368 len = ja.length();
|
|
369 for (i = 0; i < len; i += 1) {
|
|
370 if (i > 0) {
|
|
371 b.append('\n');
|
|
372 }
|
|
373 b.append(escape(ja.get(i).toString()));
|
|
374 }
|
|
375 } else {
|
|
376 b.append(escape(v.toString()));
|
|
377 }
|
|
378
|
|
379 // Emit an array of similar keys
|
|
380
|
|
381 } else if (v instanceof JSONArray) {
|
|
382 ja = (JSONArray)v;
|
|
383 len = ja.length();
|
|
384 for (i = 0; i < len; i += 1) {
|
|
385 v = ja.get(i);
|
|
386 if (v instanceof JSONArray) {
|
|
387 b.append('<');
|
|
388 b.append(k);
|
|
389 b.append('>');
|
|
390 b.append(toString(v));
|
|
391 b.append("</");
|
|
392 b.append(k);
|
|
393 b.append('>');
|
|
394 } else {
|
|
395 b.append(toString(v, k));
|
|
396 }
|
|
397 }
|
|
398 } else if (v.equals("")) {
|
|
399 b.append('<');
|
|
400 b.append(k);
|
|
401 b.append("/>");
|
|
402
|
|
403 // Emit a new tag <k>
|
|
404
|
|
405 } else {
|
|
406 b.append(toString(v, k));
|
|
407 }
|
|
408 }
|
|
409 if (tagName != null) {
|
|
410
|
|
411 // Emit the </tagname> close tag
|
|
412
|
|
413 b.append("</");
|
|
414 b.append(tagName);
|
|
415 b.append('>');
|
|
416 }
|
|
417 return b.toString();
|
|
418
|
|
419 // XML does not have good support for arrays. If an array appears in a place
|
|
420 // where XML is lacking, synthesize an <array> element.
|
|
421
|
|
422 } else if (o instanceof JSONArray) {
|
|
423 ja = (JSONArray)o;
|
|
424 len = ja.length();
|
|
425 for (i = 0; i < len; ++i) {
|
|
426 v = ja.opt(i);
|
|
427 b.append(toString(v, (tagName == null) ? "array" : tagName));
|
|
428 }
|
|
429 return b.toString();
|
|
430 } else {
|
|
431 s = (o == null) ? "null" : escape(o.toString());
|
|
432 return (tagName == null) ? "\"" + s + "\"" :
|
|
433 (s.length() == 0) ? "<" + tagName + "/>" :
|
|
434 "<" + tagName + ">" + s + "</" + tagName + ">";
|
|
435 }
|
|
436 }
|
|
437 } |