package org.json; | |
/******************************************************************************* | |
* Copyright (c) 2002 JSON.org | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining | |
* a copy of this software and associated documentation files (the | |
* "Software"), to deal in the Software without restriction, including | |
* without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to | |
* permit persons to whom the Software is furnished to do so, subject to | |
* the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be | |
* included in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
******************************************************************************/ | |
import java.util.Iterator; | |
/** | |
* This provides static methods to convert an XML text into a JSONObject, | |
* and to covert a JSONObject into an XML text. | |
* | |
* @author JSON.org | |
* @version 2008-10-14 | |
*/ | |
public class XML { | |
/** | |
* The Character '&'. | |
*/ | |
public static final Character AMP = new Character('&'); | |
/** | |
* The Character '''. | |
*/ | |
public static final Character APOS = new Character('\''); | |
/** | |
* The Character '!'. | |
*/ | |
public static final Character BANG = new Character('!'); | |
/** | |
* The Character '='. | |
*/ | |
public static final Character EQ = new Character('='); | |
/** | |
* The Character '>'. | |
*/ | |
public static final Character GT = new Character('>'); | |
/** | |
* The Character '<'. | |
*/ | |
public static final Character LT = new Character('<'); | |
/** | |
* The Character '?'. | |
*/ | |
public static final Character QUEST = new Character('?'); | |
/** | |
* The Character '"'. | |
*/ | |
public static final Character QUOT = new Character('"'); | |
/** | |
* The Character '/'. | |
*/ | |
public static final Character SLASH = new Character('/'); | |
/** | |
* Replace special characters with XML escapes: | |
* <pre> | |
* & <small>(ampersand)</small> is replaced by &amp; | |
* < <small>(less than)</small> is replaced by &lt; | |
* > <small>(greater than)</small> is replaced by &gt; | |
* " <small>(double quote)</small> is replaced by &quot; | |
* </pre> | |
* | |
* @param string The string to be escaped. | |
* @return The escaped string. | |
*/ | |
public static String escape(String string) { | |
StringBuffer sb = new StringBuffer(); | |
for (int i = 0, len = string.length(); i < len; i++) { | |
char c = string.charAt(i); | |
switch (c) { | |
case '&': | |
sb.append("&"); | |
break; | |
case '<': | |
sb.append("<"); | |
break; | |
case '>': | |
sb.append(">"); | |
break; | |
case '"': | |
sb.append("""); | |
break; | |
default: | |
sb.append(c); | |
} | |
} | |
return sb.toString(); | |
} | |
/** | |
* Throw an exception if the string contains whitespace. | |
* Whitespace is not allowed in tagNames and attributes. | |
* | |
* @param string | |
* @throws JSONException | |
*/ | |
public static void noSpace(String string) throws JSONException { | |
int i, length = string.length(); | |
if (length == 0) { | |
throw new JSONException("Empty string."); | |
} | |
for (i = 0; i < length; i += 1) { | |
if (Character.isWhitespace(string.charAt(i))) { | |
throw new JSONException("'" + string + "' contains a space character."); | |
} | |
} | |
} | |
/** | |
* Scan the content following the named tag, attaching it to the context. | |
* | |
* @param x The XMLTokener containing the source string. | |
* @param context The JSONObject that will include the new material. | |
* @param name The tag name. | |
* @return true if the close tag is processed. | |
* @throws JSONException | |
*/ | |
private static boolean parse(XMLTokener x, JSONObject context, String name) throws JSONException { | |
char c; | |
int i; | |
String n; | |
JSONObject o = null; | |
String s; | |
Object t; | |
// Test for and skip past these forms: | |
// <!-- ... --> | |
// <! ... > | |
// <![ ... ]]> | |
// <? ... ?> | |
// Report errors for these forms: | |
// <> | |
// <= | |
// << | |
t = x.nextToken(); | |
// <! | |
if (t == BANG) { | |
c = x.next(); | |
if (c == '-') { | |
if (x.next() == '-') { | |
x.skipPast("-->"); | |
return false; | |
} | |
x.back(); | |
} else if (c == '[') { | |
t = x.nextToken(); | |
if (t.equals("CDATA")) { | |
if (x.next() == '[') { | |
s = x.nextCDATA(); | |
if (s.length() > 0) { | |
context.accumulate("content", s); | |
} | |
return false; | |
} | |
} | |
throw x.syntaxError("Expected 'CDATA['"); | |
} | |
i = 1; | |
do { | |
t = x.nextMeta(); | |
if (t == null) { | |
throw x.syntaxError("Missing '>' after '<!'."); | |
} else if (t == LT) { | |
i += 1; | |
} else if (t == GT) { | |
i -= 1; | |
} | |
} while (i > 0); | |
return false; | |
} else if (t == QUEST) { | |
// <? | |
x.skipPast("?>"); | |
return false; | |
} else if (t == SLASH) { | |
// Close tag </ | |
t = x.nextToken(); | |
if (name == null) { | |
throw x.syntaxError("Mismatched close tag" + t); | |
} | |
if (!t.equals(name)) { | |
throw x.syntaxError("Mismatched " + name + " and " + t); | |
} | |
if (x.nextToken() != GT) { | |
throw x.syntaxError("Misshaped close tag"); | |
} | |
return true; | |
} else if (t instanceof Character) { | |
throw x.syntaxError("Misshaped tag"); | |
// Open tag < | |
} else { | |
n = (String) t; | |
t = null; | |
o = new JSONObject(); | |
for (; ; ) { | |
if (t == null) { | |
t = x.nextToken(); | |
} | |
// attribute = value | |
if (t instanceof String) { | |
s = (String) t; | |
t = x.nextToken(); | |
if (t == EQ) { | |
t = x.nextToken(); | |
if (!(t instanceof String)) { | |
throw x.syntaxError("Missing value"); | |
} | |
o.accumulate(s, JSONObject.stringToValue((String) t)); | |
t = null; | |
} else { | |
o.accumulate(s, ""); | |
} | |
// Empty tag <.../> | |
} else if (t == SLASH) { | |
if (x.nextToken() != GT) { | |
throw x.syntaxError("Misshaped tag"); | |
} | |
context.accumulate(n, o); | |
return false; | |
// Content, between <...> and </...> | |
} else if (t == GT) { | |
for (; ; ) { | |
t = x.nextContent(); | |
if (t == null) { | |
if (n != null) { | |
throw x.syntaxError("Unclosed tag " + n); | |
} | |
return false; | |
} else if (t instanceof String) { | |
s = (String) t; | |
if (s.length() > 0) { | |
o.accumulate("content", JSONObject.stringToValue(s)); | |
} | |
// Nested element | |
} else if (t == LT) { | |
if (parse(x, o, n)) { | |
if (o.length() == 0) { | |
context.accumulate(n, ""); | |
} else if (o.length() == 1 && o.opt("content") != null) { | |
context.accumulate(n, o.opt("content")); | |
} else { | |
context.accumulate(n, o); | |
} | |
return false; | |
} | |
} | |
} | |
} else { | |
throw x.syntaxError("Misshaped tag"); | |
} | |
} | |
} | |
} | |
/** | |
* Convert a well-formed (but not necessarily valid) XML string into a | |
* JSONObject. Some information may be lost in this transformation | |
* because JSON is a data format and XML is a document format. XML uses | |
* elements, attributes, and content text, while JSON uses unordered | |
* collections of name/value pairs and arrays of values. JSON does not | |
* does not like to distinguish between elements and attributes. | |
* Sequences of similar elements are represented as JSONArrays. Content | |
* text may be placed in a "content" member. Comments, prologs, DTDs, and | |
* <code><[ [ ]]></code> are ignored. | |
* | |
* @param string The source string. | |
* @return A JSONObject containing the structured data from the XML string. | |
* @throws JSONException | |
*/ | |
public static JSONObject toJSONObject(String string) throws JSONException { | |
JSONObject o = new JSONObject(); | |
XMLTokener x = new XMLTokener(string); | |
while (x.more() && x.skipPast("<")) { | |
parse(x, o, null); | |
} | |
return o; | |
} | |
/** | |
* Convert a JSONObject into a well-formed, element-normal XML string. | |
* | |
* @param o A JSONObject. | |
* @return A string. | |
* @throws JSONException | |
*/ | |
public static String toString(Object o) throws JSONException { | |
return toString(o, null); | |
} | |
/** | |
* Convert a JSONObject into a well-formed, element-normal XML string. | |
* | |
* @param o A JSONObject. | |
* @param tagName The optional name of the enclosing tag. | |
* @return A string. | |
* @throws JSONException | |
*/ | |
public static String toString(Object o, String tagName) throws JSONException { | |
StringBuffer b = new StringBuffer(); | |
int i; | |
JSONArray ja; | |
JSONObject jo; | |
String k; | |
Iterator keys; | |
int len; | |
String s; | |
Object v; | |
if (o instanceof JSONObject) { | |
// Emit <tagName> | |
if (tagName != null) { | |
b.append('<'); | |
b.append(tagName); | |
b.append('>'); | |
} | |
// Loop thru the keys. | |
jo = (JSONObject) o; | |
keys = jo.keys(); | |
while (keys.hasNext()) { | |
k = keys.next().toString(); | |
v = jo.opt(k); | |
if (v == null) { | |
v = ""; | |
} | |
if (v instanceof String) { | |
s = (String) v; | |
} else { | |
s = null; | |
} | |
// Emit content in body | |
if (k.equals("content")) { | |
if (v instanceof JSONArray) { | |
ja = (JSONArray) v; | |
len = ja.length(); | |
for (i = 0; i < len; i += 1) { | |
if (i > 0) { | |
b.append('\n'); | |
} | |
b.append(escape(ja.get(i).toString())); | |
} | |
} else { | |
b.append(escape(v.toString())); | |
} | |
// Emit an array of similar keys | |
} else if (v instanceof JSONArray) { | |
ja = (JSONArray) v; | |
len = ja.length(); | |
for (i = 0; i < len; i += 1) { | |
v = ja.get(i); | |
if (v instanceof JSONArray) { | |
b.append('<'); | |
b.append(k); | |
b.append('>'); | |
b.append(toString(v)); | |
b.append("</"); | |
b.append(k); | |
b.append('>'); | |
} else { | |
b.append(toString(v, k)); | |
} | |
} | |
} else if (v.equals("")) { | |
b.append('<'); | |
b.append(k); | |
b.append("/>"); | |
// Emit a new tag <k> | |
} else { | |
b.append(toString(v, k)); | |
} | |
} | |
if (tagName != null) { | |
// Emit the </tagname> close tag | |
b.append("</"); | |
b.append(tagName); | |
b.append('>'); | |
} | |
return b.toString(); | |
// XML does not have good support for arrays. If an array appears in a place | |
// where XML is lacking, synthesize an <array> element. | |
} else if (o instanceof JSONArray) { | |
ja = (JSONArray) o; | |
len = ja.length(); | |
for (i = 0; i < len; ++i) { | |
v = ja.opt(i); | |
b.append(toString(v, (tagName == null) ? "array" : tagName)); | |
} | |
return b.toString(); | |
} else { | |
s = (o == null) ? "null" : escape(o.toString()); | |
return (tagName == null) ? "\"" + s + "\"" : (s.length() == 0) ? "<" + tagName + "/>" : "<" + tagName + ">" + s + "</" + tagName + ">"; | |
} | |
} | |
} |