Class |
Line # |
Actions |
|||
---|---|---|---|---|---|
XML | 39 | 234 | 105 |
1 | package org.json; | |
2 | ||
3 | /* | |
4 | Copyright (c) 2015 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.io.Reader; | |
28 | import java.io.StringReader; | |
29 | import java.util.Iterator; | |
30 | ||
31 | /** | |
32 | * This provides static methods to convert an XML text into a JSONObject, and to | |
33 | * covert a JSONObject into an XML text. | |
34 | * | |
35 | * @author JSON.org | |
36 | * @version 2016-08-10 | |
37 | */ | |
38 | @SuppressWarnings("boxing") | |
39 | public class XML | |
40 | { | |
41 | /** The Character '&'. */ | |
42 | public static final Character AMP = '&'; | |
43 | ||
44 | /** The Character '''. */ | |
45 | public static final Character APOS = '\''; | |
46 | ||
47 | /** The Character '!'. */ | |
48 | public static final Character BANG = '!'; | |
49 | ||
50 | /** The Character '='. */ | |
51 | public static final Character EQ = '='; | |
52 | ||
53 | /** The Character '>'. */ | |
54 | public static final Character GT = '>'; | |
55 | ||
56 | /** The Character '<'. */ | |
57 | public static final Character LT = '<'; | |
58 | ||
59 | /** The Character '?'. */ | |
60 | public static final Character QUEST = '?'; | |
61 | ||
62 | /** The Character '"'. */ | |
63 | public static final Character QUOT = '"'; | |
64 | ||
65 | /** The Character '/'. */ | |
66 | public static final Character SLASH = '/'; | |
67 | ||
68 | /** | |
69 | * Creates an iterator for navigating Code Points in a string instead of | |
70 | * characters. Once Java7 support is dropped, this can be replaced with <code> | |
71 | * string.codePoints() | |
72 | * </code> which is available in Java8 and above. | |
73 | * | |
74 | * @see <a href= | |
75 | * "http://stackoverflow.com/a/21791059/6030888">http://stackoverflow.com/a/21791059/6030888</a> | |
76 | */ | |
77 | 0 | private static Iterable<Integer> codePointIterator(final String string) |
78 | { | |
79 | 0 | return new Iterable<Integer>() |
80 | { | |
81 | 0 | @Override |
82 | public Iterator<Integer> iterator() | |
83 | { | |
84 | 0 | return new Iterator<Integer>() |
85 | { | |
86 | private int nextIndex = 0; | |
87 | ||
88 | private int length = string.length(); | |
89 | ||
90 | 0 | @Override |
91 | public boolean hasNext() | |
92 | { | |
93 | 0 | return this.nextIndex < this.length; |
94 | } | |
95 | ||
96 | 0 | @Override |
97 | public Integer next() | |
98 | { | |
99 | 0 | int result = string.codePointAt(this.nextIndex); |
100 | 0 | this.nextIndex += Character.charCount(result); |
101 | 0 | return result; |
102 | } | |
103 | ||
104 | 0 | @Override |
105 | public void remove() | |
106 | { | |
107 | 0 | throw new UnsupportedOperationException(); |
108 | } | |
109 | }; | |
110 | } | |
111 | }; | |
112 | } | |
113 | ||
114 | /** | |
115 | * Replace special characters with XML escapes: | |
116 | * | |
117 | * <pre> | |
118 | * & <small>(ampersand)</small> is replaced by &amp; | |
119 | * < <small>(less than)</small> is replaced by &lt; | |
120 | * > <small>(greater than)</small> is replaced by &gt; | |
121 | * " <small>(double quote)</small> is replaced by &quot; | |
122 | * ' <small>(single quote / apostrophe)</small> is replaced by &apos; | |
123 | * </pre> | |
124 | * | |
125 | * @param string | |
126 | * The string to be escaped. | |
127 | * @return The escaped string. | |
128 | */ | |
129 | 0 | public static String escape(String string) |
130 | { | |
131 | 0 | StringBuilder sb = new StringBuilder(string.length()); |
132 | 0 | for (final int cp : codePointIterator(string)) |
133 | { | |
134 | 0 | switch (cp) |
135 | { | |
136 | 0 | case '&': |
137 | 0 | sb.append("&"); |
138 | 0 | break; |
139 | 0 | case '<': |
140 | 0 | sb.append("<"); |
141 | 0 | break; |
142 | 0 | case '>': |
143 | 0 | sb.append(">"); |
144 | 0 | break; |
145 | 0 | case '"': |
146 | 0 | sb.append("""); |
147 | 0 | break; |
148 | 0 | case '\'': |
149 | 0 | sb.append("'"); |
150 | 0 | break; |
151 | 0 | default: |
152 | 0 | if (mustEscape(cp)) |
153 | { | |
154 | 0 | sb.append("&#x"); |
155 | 0 | sb.append(Integer.toHexString(cp)); |
156 | 0 | sb.append(';'); |
157 | } | |
158 | else | |
159 | { | |
160 | 0 | sb.appendCodePoint(cp); |
161 | } | |
162 | } | |
163 | } | |
164 | 0 | return sb.toString(); |
165 | } | |
166 | ||
167 | /** | |
168 | * @param cp | |
169 | * code point to test | |
170 | * @return true if the code point is not valid for an XML | |
171 | */ | |
172 | 0 | private static boolean mustEscape(int cp) |
173 | { | |
174 | /* Valid range from https://www.w3.org/TR/REC-xml/#charsets | |
175 | * | |
176 | * #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] | |
177 | * | |
178 | * any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. | |
179 | */ | |
180 | // isISOControl is true when (cp >= 0 && cp <= 0x1F) || (cp >= 0x7F && cp <= | |
181 | // 0x9F) | |
182 | // all ISO control characters are out of range except tabs and new lines | |
183 | 0 | return (Character.isISOControl(cp) && cp != 0x9 && cp != 0xA |
184 | && cp != 0xD) || !( | |
185 | // valid the range of acceptable characters that aren't control | |
186 | (cp >= 0x20 && cp <= 0xD7FF) || (cp >= 0xE000 && cp <= 0xFFFD) | |
187 | || (cp >= 0x10000 && cp <= 0x10FFFF)); | |
188 | } | |
189 | ||
190 | /** | |
191 | * Removes XML escapes from the string. | |
192 | * | |
193 | * @param string | |
194 | * string to remove escapes from | |
195 | * @return string with converted entities | |
196 | */ | |
197 | 0 | public static String unescape(String string) |
198 | { | |
199 | 0 | StringBuilder sb = new StringBuilder(string.length()); |
200 | 0 | for (int i = 0, length = string.length(); i < length; i++) |
201 | { | |
202 | 0 | char c = string.charAt(i); |
203 | 0 | if (c == '&') |
204 | { | |
205 | 0 | final int semic = string.indexOf(';', i); |
206 | 0 | if (semic > i) |
207 | { | |
208 | 0 | final String entity = string.substring(i + 1, semic); |
209 | 0 | sb.append(XMLTokener.unescapeEntity(entity)); |
210 | // skip past the entity we just parsed. | |
211 | 0 | i += entity.length() + 1; |
212 | } | |
213 | else | |
214 | { | |
215 | // this shouldn't happen in most cases since the parser | |
216 | // errors on unclosed entries. | |
217 | 0 | sb.append(c); |
218 | } | |
219 | } | |
220 | else | |
221 | { | |
222 | // not part of an entity | |
223 | 0 | sb.append(c); |
224 | } | |
225 | } | |
226 | 0 | return sb.toString(); |
227 | } | |
228 | ||
229 | /** | |
230 | * Throw an exception if the string contains whitespace. Whitespace is not | |
231 | * allowed in tagNames and attributes. | |
232 | * | |
233 | * @param string | |
234 | * A string. | |
235 | * @throws JSONException | |
236 | * Thrown if the string contains whitespace or is empty. | |
237 | */ | |
238 | 0 | public static void noSpace(String string) throws JSONException |
239 | { | |
240 | 0 | int i, length = string.length(); |
241 | 0 | if (length == 0) |
242 | { | |
243 | 0 | throw new JSONException("Empty string."); |
244 | } | |
245 | 0 | for (i = 0; i < length; i += 1) |
246 | { | |
247 | 0 | if (Character.isWhitespace(string.charAt(i))) |
248 | { | |
249 | 0 | throw new JSONException( |
250 | "'" + string + "' contains a space character."); | |
251 | } | |
252 | } | |
253 | } | |
254 | ||
255 | /** | |
256 | * Scan the content following the named tag, attaching it to the context. | |
257 | * | |
258 | * @param x | |
259 | * The XMLTokener containing the source string. | |
260 | * @param context | |
261 | * The JSONObject that will include the new material. | |
262 | * @param name | |
263 | * The tag name. | |
264 | * @return true if the close tag is processed. | |
265 | * @throws JSONException | |
266 | */ | |
267 | 0 | private static boolean parse(XMLTokener x, JSONObject context, |
268 | String name, boolean keepStrings) throws JSONException | |
269 | { | |
270 | 0 | char c; |
271 | 0 | int i; |
272 | 0 | JSONObject jsonobject = null; |
273 | 0 | String string; |
274 | 0 | String tagName; |
275 | 0 | Object token; |
276 | ||
277 | // Test for and skip past these forms: | |
278 | // <!-- ... --> | |
279 | // <! ... > | |
280 | // <![ ... ]]> | |
281 | // <? ... ?> | |
282 | // Report errors for these forms: | |
283 | // <> | |
284 | // <= | |
285 | // << | |
286 | ||
287 | 0 | token = x.nextToken(); |
288 | ||
289 | // <! | |
290 | ||
291 | 0 | if (token == BANG) |
292 | { | |
293 | 0 | c = x.next(); |
294 | 0 | if (c == '-') |
295 | { | |
296 | 0 | if (x.next() == '-') |
297 | { | |
298 | 0 | x.skipPast("-->"); |
299 | 0 | return false; |
300 | } | |
301 | 0 | x.back(); |
302 | } | |
303 | 0 | else if (c == '[') |
304 | { | |
305 | 0 | token = x.nextToken(); |
306 | 0 | if ("CDATA".equals(token)) |
307 | { | |
308 | 0 | if (x.next() == '[') |
309 | { | |
310 | 0 | string = x.nextCDATA(); |
311 | 0 | if (string.length() > 0) |
312 | { | |
313 | 0 | context.accumulate("content", string); |
314 | } | |
315 | 0 | return false; |
316 | } | |
317 | } | |
318 | 0 | throw x.syntaxError("Expected 'CDATA['"); |
319 | } | |
320 | 0 | i = 1; |
321 | 0 | do |
322 | { | |
323 | 0 | token = x.nextMeta(); |
324 | 0 | if (token == null) |
325 | { | |
326 | 0 | throw x.syntaxError("Missing '>' after '<!'."); |
327 | } | |
328 | 0 | else if (token == LT) |
329 | { | |
330 | 0 | i += 1; |
331 | } | |
332 | 0 | else if (token == GT) |
333 | { | |
334 | 0 | i -= 1; |
335 | } | |
336 | 0 | } while (i > 0); |
337 | 0 | return false; |
338 | } | |
339 | 0 | else if (token == QUEST) |
340 | { | |
341 | ||
342 | // <? | |
343 | 0 | x.skipPast("?>"); |
344 | 0 | return false; |
345 | } | |
346 | 0 | else if (token == SLASH) |
347 | { | |
348 | ||
349 | // Close tag </ | |
350 | ||
351 | 0 | token = x.nextToken(); |
352 | 0 | if (name == null) |
353 | { | |
354 | 0 | throw x.syntaxError("Mismatched close tag " + token); |
355 | } | |
356 | 0 | if (!token.equals(name)) |
357 | { | |
358 | 0 | throw x.syntaxError("Mismatched " + name + " and " + token); |
359 | } | |
360 | 0 | if (x.nextToken() != GT) |
361 | { | |
362 | 0 | throw x.syntaxError("Misshaped close tag"); |
363 | } | |
364 | 0 | return true; |
365 | ||
366 | } | |
367 | 0 | else if (token instanceof Character) |
368 | { | |
369 | 0 | throw x.syntaxError("Misshaped tag"); |
370 | ||
371 | // Open tag < | |
372 | ||
373 | } | |
374 | else | |
375 | { | |
376 | 0 | tagName = (String) token; |
377 | 0 | token = null; |
378 | 0 | jsonobject = new JSONObject(); |
379 | 0 | for (;;) |
380 | { | |
381 | 0 | if (token == null) |
382 | { | |
383 | 0 | token = x.nextToken(); |
384 | } | |
385 | // attribute = value | |
386 | 0 | if (token instanceof String) |
387 | { | |
388 | 0 | string = (String) token; |
389 | 0 | token = x.nextToken(); |
390 | 0 | if (token == EQ) |
391 | { | |
392 | 0 | token = x.nextToken(); |
393 | 0 | if (!(token instanceof String)) |
394 | { | |
395 | 0 | throw x.syntaxError("Missing value"); |
396 | } | |
397 | 0 | jsonobject.accumulate(string, keepStrings ? ((String) token) |
398 | : stringToValue((String) token)); | |
399 | 0 | token = null; |
400 | } | |
401 | else | |
402 | { | |
403 | 0 | jsonobject.accumulate(string, ""); |
404 | } | |
405 | ||
406 | } | |
407 | 0 | else if (token == SLASH) |
408 | { | |
409 | // Empty tag <.../> | |
410 | 0 | if (x.nextToken() != GT) |
411 | { | |
412 | 0 | throw x.syntaxError("Misshaped tag"); |
413 | } | |
414 | 0 | if (jsonobject.length() > 0) |
415 | { | |
416 | 0 | context.accumulate(tagName, jsonobject); |
417 | } | |
418 | else | |
419 | { | |
420 | 0 | context.accumulate(tagName, ""); |
421 | } | |
422 | 0 | return false; |
423 | ||
424 | } | |
425 | 0 | else if (token == GT) |
426 | { | |
427 | // Content, between <...> and </...> | |
428 | 0 | for (;;) |
429 | { | |
430 | 0 | token = x.nextContent(); |
431 | 0 | if (token == null) |
432 | { | |
433 | 0 | if (tagName != null) |
434 | { | |
435 | 0 | throw x.syntaxError("Unclosed tag " + tagName); |
436 | } | |
437 | 0 | return false; |
438 | } | |
439 | 0 | else if (token instanceof String) |
440 | { | |
441 | 0 | string = (String) token; |
442 | 0 | if (string.length() > 0) |
443 | { | |
444 | 0 | jsonobject.accumulate("content", |
445 | 0 | keepStrings ? string : stringToValue(string)); |
446 | } | |
447 | ||
448 | } | |
449 | 0 | else if (token == LT) |
450 | { | |
451 | // Nested element | |
452 | 0 | if (parse(x, jsonobject, tagName, keepStrings)) |
453 | { | |
454 | 0 | if (jsonobject.length() == 0) |
455 | { | |
456 | 0 | context.accumulate(tagName, ""); |
457 | } | |
458 | 0 | else if (jsonobject.length() == 1 |
459 | && jsonobject.opt("content") != null) | |
460 | { | |
461 | 0 | context.accumulate(tagName, jsonobject.opt("content")); |
462 | } | |
463 | else | |
464 | { | |
465 | 0 | context.accumulate(tagName, jsonobject); |
466 | } | |
467 | 0 | return false; |
468 | } | |
469 | } | |
470 | } | |
471 | } | |
472 | else | |
473 | { | |
474 | 0 | throw x.syntaxError("Misshaped tag"); |
475 | } | |
476 | } | |
477 | } | |
478 | } | |
479 | ||
480 | /** | |
481 | * This method is the same as {@link JSONObject#stringToValue(String)}. | |
482 | * | |
483 | * @param string | |
484 | * String to convert | |
485 | * @return JSON value of this string or the string | |
486 | */ | |
487 | // To maintain compatibility with the Android API, this method is a direct | |
488 | // copy of | |
489 | // the one in JSONObject. Changes made here should be reflected there. | |
490 | 0 | public static Object stringToValue(String string) |
491 | { | |
492 | 0 | if (string.equals("")) |
493 | { | |
494 | 0 | return string; |
495 | } | |
496 | 0 | if (string.equalsIgnoreCase("true")) |
497 | { | |
498 | 0 | return Boolean.TRUE; |
499 | } | |
500 | 0 | if (string.equalsIgnoreCase("false")) |
501 | { | |
502 | 0 | return Boolean.FALSE; |
503 | } | |
504 | 0 | if (string.equalsIgnoreCase("null")) |
505 | { | |
506 | 0 | return JSONObject.NULL; |
507 | } | |
508 | ||
509 | /* | |
510 | * If it might be a number, try converting it. If a number cannot be | |
511 | * produced, then the value will just be a string. | |
512 | */ | |
513 | ||
514 | 0 | char initial = string.charAt(0); |
515 | 0 | if ((initial >= '0' && initial <= '9') || initial == '-') |
516 | { | |
517 | 0 | try |
518 | { | |
519 | // if we want full Big Number support this block can be replaced with: | |
520 | // return stringToNumber(string); | |
521 | 0 | if (string.indexOf('.') > -1 || string.indexOf('e') > -1 |
522 | || string.indexOf('E') > -1 || "-0".equals(string)) | |
523 | { | |
524 | 0 | Double d = Double.valueOf(string); |
525 | 0 | if (!d.isInfinite() && !d.isNaN()) |
526 | { | |
527 | 0 | return d; |
528 | } | |
529 | } | |
530 | else | |
531 | { | |
532 | 0 | Long myLong = Long.valueOf(string); |
533 | 0 | if (string.equals(myLong.toString())) |
534 | { | |
535 | 0 | if (myLong.longValue() == myLong.intValue()) |
536 | { | |
537 | 0 | return Integer.valueOf(myLong.intValue()); |
538 | } | |
539 | 0 | return myLong; |
540 | } | |
541 | } | |
542 | } catch (Exception ignore) | |
543 | { | |
544 | } | |
545 | } | |
546 | 0 | return string; |
547 | } | |
548 | ||
549 | /** | |
550 | * Convert a well-formed (but not necessarily valid) XML string into a | |
551 | * JSONObject. Some information may be lost in this transformation because | |
552 | * JSON is a data format and XML is a document format. XML uses elements, | |
553 | * attributes, and content text, while JSON uses unordered collections of | |
554 | * name/value pairs and arrays of values. JSON does not does not like to | |
555 | * distinguish between elements and attributes. Sequences of similar elements | |
556 | * are represented as JSONArrays. Content text may be placed in a "content" | |
557 | * member. Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored. | |
558 | * | |
559 | * @param string | |
560 | * The source string. | |
561 | * @return A JSONObject containing the structured data from the XML string. | |
562 | * @throws JSONException | |
563 | * Thrown if there is an errors while parsing the string | |
564 | */ | |
565 | 0 | public static JSONObject toJSONObject(String string) throws JSONException |
566 | { | |
567 | 0 | return toJSONObject(string, false); |
568 | } | |
569 | ||
570 | /** | |
571 | * Convert a well-formed (but not necessarily valid) XML into a JSONObject. | |
572 | * Some information may be lost in this transformation because JSON is a data | |
573 | * format and XML is a document format. XML uses elements, attributes, and | |
574 | * content text, while JSON uses unordered collections of name/value pairs and | |
575 | * arrays of values. JSON does not does not like to distinguish between | |
576 | * elements and attributes. Sequences of similar elements are represented as | |
577 | * JSONArrays. Content text may be placed in a "content" member. Comments, | |
578 | * prologs, DTDs, and <code><[ [ ]]></code> are ignored. | |
579 | * | |
580 | * @param reader | |
581 | * The XML source reader. | |
582 | * @return A JSONObject containing the structured data from the XML string. | |
583 | * @throws JSONException | |
584 | * Thrown if there is an errors while parsing the string | |
585 | */ | |
586 | 0 | public static JSONObject toJSONObject(Reader reader) throws JSONException |
587 | { | |
588 | 0 | return toJSONObject(reader, false); |
589 | } | |
590 | ||
591 | /** | |
592 | * Convert a well-formed (but not necessarily valid) XML into a JSONObject. | |
593 | * Some information may be lost in this transformation because JSON is a data | |
594 | * format and XML is a document format. XML uses elements, attributes, and | |
595 | * content text, while JSON uses unordered collections of name/value pairs and | |
596 | * arrays of values. JSON does not does not like to distinguish between | |
597 | * elements and attributes. Sequences of similar elements are represented as | |
598 | * JSONArrays. Content text may be placed in a "content" member. Comments, | |
599 | * prologs, DTDs, and <code><[ [ ]]></code> are ignored. | |
600 | * | |
601 | * All values are converted as strings, for 1, 01, 29.0 will not be coerced to | |
602 | * numbers but will instead be the exact value as seen in the XML document. | |
603 | * | |
604 | * @param reader | |
605 | * The XML source reader. | |
606 | * @param keepStrings | |
607 | * If true, then values will not be coerced into boolean or numeric | |
608 | * values and will instead be left as strings | |
609 | * @return A JSONObject containing the structured data from the XML string. | |
610 | * @throws JSONException | |
611 | * Thrown if there is an errors while parsing the string | |
612 | */ | |
613 | 0 | public static JSONObject toJSONObject(Reader reader, boolean keepStrings) |
614 | throws JSONException | |
615 | { | |
616 | 0 | JSONObject jo = new JSONObject(); |
617 | 0 | XMLTokener x = new XMLTokener(reader); |
618 | 0 | while (x.more()) |
619 | { | |
620 | 0 | x.skipPast("<"); |
621 | 0 | if (x.more()) |
622 | { | |
623 | 0 | parse(x, jo, null, keepStrings); |
624 | } | |
625 | } | |
626 | 0 | return jo; |
627 | } | |
628 | ||
629 | /** | |
630 | * Convert a well-formed (but not necessarily valid) XML string into a | |
631 | * JSONObject. Some information may be lost in this transformation because | |
632 | * JSON is a data format and XML is a document format. XML uses elements, | |
633 | * attributes, and content text, while JSON uses unordered collections of | |
634 | * name/value pairs and arrays of values. JSON does not does not like to | |
635 | * distinguish between elements and attributes. Sequences of similar elements | |
636 | * are represented as JSONArrays. Content text may be placed in a "content" | |
637 | * member. Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored. | |
638 | * | |
639 | * All values are converted as strings, for 1, 01, 29.0 will not be coerced to | |
640 | * numbers but will instead be the exact value as seen in the XML document. | |
641 | * | |
642 | * @param string | |
643 | * The source string. | |
644 | * @param keepStrings | |
645 | * If true, then values will not be coerced into boolean or numeric | |
646 | * values and will instead be left as strings | |
647 | * @return A JSONObject containing the structured data from the XML string. | |
648 | * @throws JSONException | |
649 | * Thrown if there is an errors while parsing the string | |
650 | */ | |
651 | 0 | public static JSONObject toJSONObject(String string, boolean keepStrings) |
652 | throws JSONException | |
653 | { | |
654 | 0 | return toJSONObject(new StringReader(string), keepStrings); |
655 | } | |
656 | ||
657 | /** | |
658 | * Convert a JSONObject into a well-formed, element-normal XML string. | |
659 | * | |
660 | * @param object | |
661 | * A JSONObject. | |
662 | * @return A string. | |
663 | * @throws JSONException | |
664 | * Thrown if there is an error parsing the string | |
665 | */ | |
666 | 0 | public static String toString(Object object) throws JSONException |
667 | { | |
668 | 0 | return toString(object, null); |
669 | } | |
670 | ||
671 | /** | |
672 | * Convert a JSONObject into a well-formed, element-normal XML string. | |
673 | * | |
674 | * @param object | |
675 | * A JSONObject. | |
676 | * @param tagName | |
677 | * The optional name of the enclosing tag. | |
678 | * @return A string. | |
679 | * @throws JSONException | |
680 | * Thrown if there is an error parsing the string | |
681 | */ | |
682 | 0 | public static String toString(final Object object, final String tagName) |
683 | throws JSONException | |
684 | { | |
685 | 0 | StringBuilder sb = new StringBuilder(); |
686 | 0 | JSONArray ja; |
687 | 0 | JSONObject jo; |
688 | 0 | String string; |
689 | ||
690 | 0 | if (object instanceof JSONObject) |
691 | { | |
692 | ||
693 | // Emit <tagName> | |
694 | 0 | if (tagName != null) |
695 | { | |
696 | 0 | sb.append('<'); |
697 | 0 | sb.append(tagName); |
698 | 0 | sb.append('>'); |
699 | } | |
700 | ||
701 | // Loop thru the keys. | |
702 | // don't use the new entrySet accessor to maintain Android Support | |
703 | 0 | jo = (JSONObject) object; |
704 | 0 | for (final String key : jo.keySet()) |
705 | { | |
706 | 0 | Object value = jo.opt(key); |
707 | 0 | if (value == null) |
708 | { | |
709 | 0 | value = ""; |
710 | } | |
711 | 0 | else if (value.getClass().isArray()) |
712 | { | |
713 | 0 | value = new JSONArray(value); |
714 | } | |
715 | ||
716 | // Emit content in body | |
717 | 0 | if ("content".equals(key)) |
718 | { | |
719 | 0 | if (value instanceof JSONArray) |
720 | { | |
721 | 0 | ja = (JSONArray) value; |
722 | 0 | int jaLength = ja.length(); |
723 | // don't use the new iterator API to maintain support for Android | |
724 | 0 | for (int i = 0; i < jaLength; i++) |
725 | { | |
726 | 0 | if (i > 0) |
727 | { | |
728 | 0 | sb.append('\n'); |
729 | } | |
730 | 0 | Object val = ja.opt(i); |
731 | 0 | sb.append(escape(val.toString())); |
732 | } | |
733 | } | |
734 | else | |
735 | { | |
736 | 0 | sb.append(escape(value.toString())); |
737 | } | |
738 | ||
739 | // Emit an array of similar keys | |
740 | ||
741 | } | |
742 | 0 | else if (value instanceof JSONArray) |
743 | { | |
744 | 0 | ja = (JSONArray) value; |
745 | 0 | int jaLength = ja.length(); |
746 | // don't use the new iterator API to maintain support for Android | |
747 | 0 | for (int i = 0; i < jaLength; i++) |
748 | { | |
749 | 0 | Object val = ja.opt(i); |
750 | 0 | if (val instanceof JSONArray) |
751 | { | |
752 | 0 | sb.append('<'); |
753 | 0 | sb.append(key); |
754 | 0 | sb.append('>'); |
755 | 0 | sb.append(toString(val)); |
756 | 0 | sb.append("</"); |
757 | 0 | sb.append(key); |
758 | 0 | sb.append('>'); |
759 | } | |
760 | else | |
761 | { | |
762 | 0 | sb.append(toString(val, key)); |
763 | } | |
764 | } | |
765 | } | |
766 | 0 | else if ("".equals(value)) |
767 | { | |
768 | 0 | sb.append('<'); |
769 | 0 | sb.append(key); |
770 | 0 | sb.append("/>"); |
771 | ||
772 | // Emit a new tag <k> | |
773 | ||
774 | } | |
775 | else | |
776 | { | |
777 | 0 | sb.append(toString(value, key)); |
778 | } | |
779 | } | |
780 | 0 | if (tagName != null) |
781 | { | |
782 | ||
783 | // Emit the </tagname> close tag | |
784 | 0 | sb.append("</"); |
785 | 0 | sb.append(tagName); |
786 | 0 | sb.append('>'); |
787 | } | |
788 | 0 | return sb.toString(); |
789 | ||
790 | } | |
791 | ||
792 | 0 | if (object != null |
793 | && (object instanceof JSONArray || object.getClass().isArray())) | |
794 | { | |
795 | 0 | if (object.getClass().isArray()) |
796 | { | |
797 | 0 | ja = new JSONArray(object); |
798 | } | |
799 | else | |
800 | { | |
801 | 0 | ja = (JSONArray) object; |
802 | } | |
803 | 0 | int jaLength = ja.length(); |
804 | // don't use the new iterator API to maintain support for Android | |
805 | 0 | for (int i = 0; i < jaLength; i++) |
806 | { | |
807 | 0 | Object val = ja.opt(i); |
808 | // XML does not have good support for arrays. If an array | |
809 | // appears in a place where XML is lacking, synthesize an | |
810 | // <array> element. | |
811 | 0 | sb.append(toString(val, tagName == null ? "array" : tagName)); |
812 | } | |
813 | 0 | return sb.toString(); |
814 | } | |
815 | ||
816 | 0 | string = (object == null) ? "null" : escape(object.toString()); |
817 | 0 | return (tagName == null) ? "\"" + string + "\"" |
818 | 0 | : (string.length() == 0) ? "<" + tagName + "/>" |
819 | : "<" + tagName + ">" + string + "</" + tagName + ">"; | |
820 | ||
821 | } | |
822 | } |