4 Copyright (c) 2015 JSON.org
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:
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
16 The Software shall be used for Good, not Evil.
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
27 import java.io.Reader;
28 import java.io.StringReader;
29 import java.util.Iterator;
32 * This provides static methods to convert an XML text into a JSONObject, and to
33 * covert a JSONObject into an XML text.
38 @SuppressWarnings("boxing")
41 /** The Character '&'. */
42 public static final Character AMP = '&';
44 /** The Character '''. */
45 public static final Character APOS = '\'';
47 /** The Character '!'. */
48 public static final Character BANG = '!';
50 /** The Character '='. */
51 public static final Character EQ = '=';
53 /** The Character '>'. */
54 public static final Character GT = '>';
56 /** The Character '<'. */
57 public static final Character LT = '<';
59 /** The Character '?'. */
60 public static final Character QUEST = '?';
62 /** The Character '"'. */
63 public static final Character QUOT = '"';
65 /** The Character '/'. */
66 public static final Character SLASH = '/';
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>
72 * </code> which is available in Java8 and above.
75 * "http://stackoverflow.com/a/21791059/6030888">http://stackoverflow.com/a/21791059/6030888</a>
77 private static Iterable<Integer> codePointIterator(final String string)
79 return new Iterable<Integer>()
82 public Iterator<Integer> iterator()
84 return new Iterator<Integer>()
86 private int nextIndex = 0;
88 private int length = string.length();
91 public boolean hasNext()
93 return this.nextIndex < this.length;
99 int result = string.codePointAt(this.nextIndex);
100 this.nextIndex += Character.charCount(result);
107 throw new UnsupportedOperationException();
115 * Replace special characters with XML escapes:
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;
126 * The string to be escaped.
127 * @return The escaped string.
129 public static String escape(String string)
131 StringBuilder sb = new StringBuilder(string.length());
132 for (final int cp : codePointIterator(string))
155 sb.append(Integer.toHexString(cp));
160 sb.appendCodePoint(cp);
164 return sb.toString();
170 * @return true if the code point is not valid for an XML
172 private static boolean mustEscape(int cp)
174 /* Valid range from https://www.w3.org/TR/REC-xml/#charsets
176 * #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
178 * any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.
180 // isISOControl is true when (cp >= 0 && cp <= 0x1F) || (cp >= 0x7F && cp <=
182 // all ISO control characters are out of range except tabs and new lines
183 return (Character.isISOControl(cp) && cp != 0x9 && cp != 0xA
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));
191 * Removes XML escapes from the string.
194 * string to remove escapes from
195 * @return string with converted entities
197 public static String unescape(String string)
199 StringBuilder sb = new StringBuilder(string.length());
200 for (int i = 0, length = string.length(); i < length; i++)
202 char c = string.charAt(i);
205 final int semic = string.indexOf(';', i);
208 final String entity = string.substring(i + 1, semic);
209 sb.append(XMLTokener.unescapeEntity(entity));
210 // skip past the entity we just parsed.
211 i += entity.length() + 1;
215 // this shouldn't happen in most cases since the parser
216 // errors on unclosed entries.
222 // not part of an entity
226 return sb.toString();
230 * Throw an exception if the string contains whitespace. Whitespace is not
231 * allowed in tagNames and attributes.
235 * @throws JSONException
236 * Thrown if the string contains whitespace or is empty.
238 public static void noSpace(String string) throws JSONException
240 int i, length = string.length();
243 throw new JSONException("Empty string.");
245 for (i = 0; i < length; i += 1)
247 if (Character.isWhitespace(string.charAt(i)))
249 throw new JSONException(
250 "'" + string + "' contains a space character.");
256 * Scan the content following the named tag, attaching it to the context.
259 * The XMLTokener containing the source string.
261 * The JSONObject that will include the new material.
264 * @return true if the close tag is processed.
265 * @throws JSONException
267 private static boolean parse(XMLTokener x, JSONObject context,
268 String name, boolean keepStrings) throws JSONException
272 JSONObject jsonobject = null;
277 // Test for and skip past these forms:
282 // Report errors for these forms:
287 token = x.nextToken();
305 token = x.nextToken();
306 if ("CDATA".equals(token))
310 string = x.nextCDATA();
311 if (string.length() > 0)
313 context.accumulate("content", string);
318 throw x.syntaxError("Expected 'CDATA['");
323 token = x.nextMeta();
326 throw x.syntaxError("Missing '>' after '<!'.");
328 else if (token == LT)
332 else if (token == GT)
339 else if (token == QUEST)
346 else if (token == SLASH)
351 token = x.nextToken();
354 throw x.syntaxError("Mismatched close tag " + token);
356 if (!token.equals(name))
358 throw x.syntaxError("Mismatched " + name + " and " + token);
360 if (x.nextToken() != GT)
362 throw x.syntaxError("Misshaped close tag");
367 else if (token instanceof Character)
369 throw x.syntaxError("Misshaped tag");
376 tagName = (String) token;
378 jsonobject = new JSONObject();
383 token = x.nextToken();
386 if (token instanceof String)
388 string = (String) token;
389 token = x.nextToken();
392 token = x.nextToken();
393 if (!(token instanceof String))
395 throw x.syntaxError("Missing value");
397 jsonobject.accumulate(string, keepStrings ? ((String) token)
398 : stringToValue((String) token));
403 jsonobject.accumulate(string, "");
407 else if (token == SLASH)
410 if (x.nextToken() != GT)
412 throw x.syntaxError("Misshaped tag");
414 if (jsonobject.length() > 0)
416 context.accumulate(tagName, jsonobject);
420 context.accumulate(tagName, "");
425 else if (token == GT)
427 // Content, between <...> and </...>
430 token = x.nextContent();
435 throw x.syntaxError("Unclosed tag " + tagName);
439 else if (token instanceof String)
441 string = (String) token;
442 if (string.length() > 0)
444 jsonobject.accumulate("content",
445 keepStrings ? string : stringToValue(string));
449 else if (token == LT)
452 if (parse(x, jsonobject, tagName, keepStrings))
454 if (jsonobject.length() == 0)
456 context.accumulate(tagName, "");
458 else if (jsonobject.length() == 1
459 && jsonobject.opt("content") != null)
461 context.accumulate(tagName, jsonobject.opt("content"));
465 context.accumulate(tagName, jsonobject);
474 throw x.syntaxError("Misshaped tag");
481 * This method is the same as {@link JSONObject#stringToValue(String)}.
485 * @return JSON value of this string or the string
487 // To maintain compatibility with the Android API, this method is a direct
489 // the one in JSONObject. Changes made here should be reflected there.
490 public static Object stringToValue(String string)
492 if (string.equals(""))
496 if (string.equalsIgnoreCase("true"))
500 if (string.equalsIgnoreCase("false"))
502 return Boolean.FALSE;
504 if (string.equalsIgnoreCase("null"))
506 return JSONObject.NULL;
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.
514 char initial = string.charAt(0);
515 if ((initial >= '0' && initial <= '9') || initial == '-')
519 // if we want full Big Number support this block can be replaced with:
520 // return stringToNumber(string);
521 if (string.indexOf('.') > -1 || string.indexOf('e') > -1
522 || string.indexOf('E') > -1 || "-0".equals(string))
524 Double d = Double.valueOf(string);
525 if (!d.isInfinite() && !d.isNaN())
532 Long myLong = Long.valueOf(string);
533 if (string.equals(myLong.toString()))
535 if (myLong.longValue() == myLong.intValue())
537 return Integer.valueOf(myLong.intValue());
542 } catch (Exception ignore)
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.
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
565 public static JSONObject toJSONObject(String string) throws JSONException
567 return toJSONObject(string, false);
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.
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
586 public static JSONObject toJSONObject(Reader reader) throws JSONException
588 return toJSONObject(reader, false);
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.
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.
605 * The XML source reader.
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
613 public static JSONObject toJSONObject(Reader reader, boolean keepStrings)
616 JSONObject jo = new JSONObject();
617 XMLTokener x = new XMLTokener(reader);
623 parse(x, jo, null, keepStrings);
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.
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.
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
651 public static JSONObject toJSONObject(String string, boolean keepStrings)
654 return toJSONObject(new StringReader(string), keepStrings);
658 * Convert a JSONObject into a well-formed, element-normal XML string.
663 * @throws JSONException
664 * Thrown if there is an error parsing the string
666 public static String toString(Object object) throws JSONException
668 return toString(object, null);
672 * Convert a JSONObject into a well-formed, element-normal XML string.
677 * The optional name of the enclosing tag.
679 * @throws JSONException
680 * Thrown if there is an error parsing the string
682 public static String toString(final Object object, final String tagName)
685 StringBuilder sb = new StringBuilder();
690 if (object instanceof JSONObject)
701 // Loop thru the keys.
702 // don't use the new entrySet accessor to maintain Android Support
703 jo = (JSONObject) object;
704 for (final String key : jo.keySet())
706 Object value = jo.opt(key);
711 else if (value.getClass().isArray())
713 value = new JSONArray(value);
716 // Emit content in body
717 if ("content".equals(key))
719 if (value instanceof JSONArray)
721 ja = (JSONArray) value;
722 int jaLength = ja.length();
723 // don't use the new iterator API to maintain support for Android
724 for (int i = 0; i < jaLength; i++)
730 Object val = ja.opt(i);
731 sb.append(escape(val.toString()));
736 sb.append(escape(value.toString()));
739 // Emit an array of similar keys
742 else if (value instanceof JSONArray)
744 ja = (JSONArray) value;
745 int jaLength = ja.length();
746 // don't use the new iterator API to maintain support for Android
747 for (int i = 0; i < jaLength; i++)
749 Object val = ja.opt(i);
750 if (val instanceof JSONArray)
755 sb.append(toString(val));
762 sb.append(toString(val, key));
766 else if ("".equals(value))
772 // Emit a new tag <k>
777 sb.append(toString(value, key));
783 // Emit the </tagname> close tag
788 return sb.toString();
793 && (object instanceof JSONArray || object.getClass().isArray()))
795 if (object.getClass().isArray())
797 ja = new JSONArray(object);
801 ja = (JSONArray) object;
803 int jaLength = ja.length();
804 // don't use the new iterator API to maintain support for Android
805 for (int i = 0; i < jaLength; i++)
807 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
811 sb.append(toString(val, tagName == null ? "array" : tagName));
813 return sb.toString();
816 string = (object == null) ? "null" : escape(object.toString());
817 return (tagName == null) ? "\"" + string + "\""
818 : (string.length() == 0) ? "<" + tagName + "/>"
819 : "<" + tagName + ">" + string + "</" + tagName + ">";