()
{
private int nextIndex = 0;
private int length = string.length();
@Override
public boolean hasNext()
{
return this.nextIndex < this.length;
}
@Override
public Integer next()
{
int result = string.codePointAt(this.nextIndex);
this.nextIndex += Character.charCount(result);
return result;
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
};
}
/**
* Replace special characters with XML escapes:
*
*
* & (ampersand) is replaced by &
* < (less than) is replaced by <
* > (greater than) is replaced by >
* " (double quote) is replaced by "
* ' (single quote / apostrophe) is replaced by '
*
*
* @param string
* The string to be escaped.
* @return The escaped string.
*/
public static String escape(String string)
{
StringBuilder sb = new StringBuilder(string.length());
for (final int cp : codePointIterator(string))
{
switch (cp)
{
case '&':
sb.append("&");
break;
case '<':
sb.append("<");
break;
case '>':
sb.append(">");
break;
case '"':
sb.append(""");
break;
case '\'':
sb.append("'");
break;
default:
if (mustEscape(cp))
{
sb.append("");
sb.append(Integer.toHexString(cp));
sb.append(';');
}
else
{
sb.appendCodePoint(cp);
}
}
}
return sb.toString();
}
/**
* @param cp
* code point to test
* @return true if the code point is not valid for an XML
*/
private static boolean mustEscape(int cp)
{
/* Valid range from https://www.w3.org/TR/REC-xml/#charsets
*
* #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
*
* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.
*/
// isISOControl is true when (cp >= 0 && cp <= 0x1F) || (cp >= 0x7F && cp <=
// 0x9F)
// all ISO control characters are out of range except tabs and new lines
return (Character.isISOControl(cp) && cp != 0x9 && cp != 0xA
&& cp != 0xD) || !(
// valid the range of acceptable characters that aren't control
(cp >= 0x20 && cp <= 0xD7FF) || (cp >= 0xE000 && cp <= 0xFFFD)
|| (cp >= 0x10000 && cp <= 0x10FFFF));
}
/**
* Removes XML escapes from the string.
*
* @param string
* string to remove escapes from
* @return string with converted entities
*/
public static String unescape(String string)
{
StringBuilder sb = new StringBuilder(string.length());
for (int i = 0, length = string.length(); i < length; i++)
{
char c = string.charAt(i);
if (c == '&')
{
final int semic = string.indexOf(';', i);
if (semic > i)
{
final String entity = string.substring(i + 1, semic);
sb.append(XMLTokener.unescapeEntity(entity));
// skip past the entity we just parsed.
i += entity.length() + 1;
}
else
{
// this shouldn't happen in most cases since the parser
// errors on unclosed entries.
sb.append(c);
}
}
else
{
// not part of an entity
sb.append(c);
}
}
return sb.toString();
}
/**
* Throw an exception if the string contains whitespace. Whitespace is not
* allowed in tagNames and attributes.
*
* @param string
* A string.
* @throws JSONException
* Thrown if the string contains whitespace or is empty.
*/
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, boolean keepStrings) throws JSONException
{
char c;
int i;
JSONObject jsonobject = null;
String string;
String tagName;
Object token;
// Test for and skip past these forms:
//
//
//
// ... ?>
// Report errors for these forms:
// <>
// <=
// <<
token = x.nextToken();
// ");
return false;
}
x.back();
}
else if (c == '[')
{
token = x.nextToken();
if ("CDATA".equals(token))
{
if (x.next() == '[')
{
string = x.nextCDATA();
if (string.length() > 0)
{
context.accumulate("content", string);
}
return false;
}
}
throw x.syntaxError("Expected 'CDATA['");
}
i = 1;
do
{
token = x.nextMeta();
if (token == null)
{
throw x.syntaxError("Missing '>' after ' 0);
return false;
}
else if (token == QUEST)
{
//
x.skipPast("?>");
return false;
}
else if (token == SLASH)
{
// Close tag
token = x.nextToken();
if (name == null)
{
throw x.syntaxError("Mismatched close tag " + token);
}
if (!token.equals(name))
{
throw x.syntaxError("Mismatched " + name + " and " + token);
}
if (x.nextToken() != GT)
{
throw x.syntaxError("Misshaped close tag");
}
return true;
}
else if (token instanceof Character)
{
throw x.syntaxError("Misshaped tag");
// Open tag <
}
else
{
tagName = (String) token;
token = null;
jsonobject = new JSONObject();
for (;;)
{
if (token == null)
{
token = x.nextToken();
}
// attribute = value
if (token instanceof String)
{
string = (String) token;
token = x.nextToken();
if (token == EQ)
{
token = x.nextToken();
if (!(token instanceof String))
{
throw x.syntaxError("Missing value");
}
jsonobject.accumulate(string, keepStrings ? ((String) token)
: stringToValue((String) token));
token = null;
}
else
{
jsonobject.accumulate(string, "");
}
}
else if (token == SLASH)
{
// Empty tag <.../>
if (x.nextToken() != GT)
{
throw x.syntaxError("Misshaped tag");
}
if (jsonobject.length() > 0)
{
context.accumulate(tagName, jsonobject);
}
else
{
context.accumulate(tagName, "");
}
return false;
}
else if (token == GT)
{
// Content, between <...> and
for (;;)
{
token = x.nextContent();
if (token == null)
{
if (tagName != null)
{
throw x.syntaxError("Unclosed tag " + tagName);
}
return false;
}
else if (token instanceof String)
{
string = (String) token;
if (string.length() > 0)
{
jsonobject.accumulate("content",
keepStrings ? string : stringToValue(string));
}
}
else if (token == LT)
{
// Nested element
if (parse(x, jsonobject, tagName, keepStrings))
{
if (jsonobject.length() == 0)
{
context.accumulate(tagName, "");
}
else if (jsonobject.length() == 1
&& jsonobject.opt("content") != null)
{
context.accumulate(tagName, jsonobject.opt("content"));
}
else
{
context.accumulate(tagName, jsonobject);
}
return false;
}
}
}
}
else
{
throw x.syntaxError("Misshaped tag");
}
}
}
}
/**
* This method is the same as {@link JSONObject#stringToValue(String)}.
*
* @param string
* String to convert
* @return JSON value of this string or the string
*/
// To maintain compatibility with the Android API, this method is a direct
// copy of
// the one in JSONObject. Changes made here should be reflected there.
public static Object stringToValue(String string)
{
if (string.equals(""))
{
return string;
}
if (string.equalsIgnoreCase("true"))
{
return Boolean.TRUE;
}
if (string.equalsIgnoreCase("false"))
{
return Boolean.FALSE;
}
if (string.equalsIgnoreCase("null"))
{
return JSONObject.NULL;
}
/*
* If it might be a number, try converting it. If a number cannot be
* produced, then the value will just be a string.
*/
char initial = string.charAt(0);
if ((initial >= '0' && initial <= '9') || initial == '-')
{
try
{
// if we want full Big Number support this block can be replaced with:
// return stringToNumber(string);
if (string.indexOf('.') > -1 || string.indexOf('e') > -1
|| string.indexOf('E') > -1 || "-0".equals(string))
{
Double d = Double.valueOf(string);
if (!d.isInfinite() && !d.isNaN())
{
return d;
}
}
else
{
Long myLong = Long.valueOf(string);
if (string.equals(myLong.toString()))
{
if (myLong.longValue() == myLong.intValue())
{
return Integer.valueOf(myLong.intValue());
}
return myLong;
}
}
} catch (Exception ignore)
{
}
}
return string;
}
/**
* 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 <[ [ ]]>
are ignored.
*
* @param string
* The source string.
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException
* Thrown if there is an errors while parsing the string
*/
public static JSONObject toJSONObject(String string) throws JSONException
{
return toJSONObject(string, false);
}
/**
* Convert a well-formed (but not necessarily valid) XML 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 <[ [ ]]>
are ignored.
*
* @param reader
* The XML source reader.
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException
* Thrown if there is an errors while parsing the string
*/
public static JSONObject toJSONObject(Reader reader) throws JSONException
{
return toJSONObject(reader, false);
}
/**
* Convert a well-formed (but not necessarily valid) XML 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 <[ [ ]]>
are ignored.
*
* All values are converted as strings, for 1, 01, 29.0 will not be coerced to
* numbers but will instead be the exact value as seen in the XML document.
*
* @param reader
* The XML source reader.
* @param keepStrings
* If true, then values will not be coerced into boolean or numeric
* values and will instead be left as strings
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException
* Thrown if there is an errors while parsing the string
*/
public static JSONObject toJSONObject(Reader reader, boolean keepStrings)
throws JSONException
{
JSONObject jo = new JSONObject();
XMLTokener x = new XMLTokener(reader);
while (x.more())
{
x.skipPast("<");
if (x.more())
{
parse(x, jo, null, keepStrings);
}
}
return jo;
}
/**
* 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 <[ [ ]]>
are ignored.
*
* All values are converted as strings, for 1, 01, 29.0 will not be coerced to
* numbers but will instead be the exact value as seen in the XML document.
*
* @param string
* The source string.
* @param keepStrings
* If true, then values will not be coerced into boolean or numeric
* values and will instead be left as strings
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException
* Thrown if there is an errors while parsing the string
*/
public static JSONObject toJSONObject(String string, boolean keepStrings)
throws JSONException
{
return toJSONObject(new StringReader(string), keepStrings);
}
/**
* Convert a JSONObject into a well-formed, element-normal XML string.
*
* @param object
* A JSONObject.
* @return A string.
* @throws JSONException
* Thrown if there is an error parsing the string
*/
public static String toString(Object object) throws JSONException
{
return toString(object, null);
}
/**
* Convert a JSONObject into a well-formed, element-normal XML string.
*
* @param object
* A JSONObject.
* @param tagName
* The optional name of the enclosing tag.
* @return A string.
* @throws JSONException
* Thrown if there is an error parsing the string
*/
public static String toString(final Object object, final String tagName)
throws JSONException
{
StringBuilder sb = new StringBuilder();
JSONArray ja;
JSONObject jo;
String string;
if (object instanceof JSONObject)
{
// Emit
if (tagName != null)
{
sb.append('<');
sb.append(tagName);
sb.append('>');
}
// Loop thru the keys.
// don't use the new entrySet accessor to maintain Android Support
jo = (JSONObject) object;
for (final String key : jo.keySet())
{
Object value = jo.opt(key);
if (value == null)
{
value = "";
}
else if (value.getClass().isArray())
{
value = new JSONArray(value);
}
// Emit content in body
if ("content".equals(key))
{
if (value instanceof JSONArray)
{
ja = (JSONArray) value;
int jaLength = ja.length();
// don't use the new iterator API to maintain support for Android
for (int i = 0; i < jaLength; i++)
{
if (i > 0)
{
sb.append('\n');
}
Object val = ja.opt(i);
sb.append(escape(val.toString()));
}
}
else
{
sb.append(escape(value.toString()));
}
// Emit an array of similar keys
}
else if (value instanceof JSONArray)
{
ja = (JSONArray) value;
int jaLength = ja.length();
// don't use the new iterator API to maintain support for Android
for (int i = 0; i < jaLength; i++)
{
Object val = ja.opt(i);
if (val instanceof JSONArray)
{
sb.append('<');
sb.append(key);
sb.append('>');
sb.append(toString(val));
sb.append("");
sb.append(key);
sb.append('>');
}
else
{
sb.append(toString(val, key));
}
}
}
else if ("".equals(value))
{
sb.append('<');
sb.append(key);
sb.append("/>");
// Emit a new tag
}
else
{
sb.append(toString(value, key));
}
}
if (tagName != null)
{
// Emit the close tag
sb.append("");
sb.append(tagName);
sb.append('>');
}
return sb.toString();
}
if (object != null
&& (object instanceof JSONArray || object.getClass().isArray()))
{
if (object.getClass().isArray())
{
ja = new JSONArray(object);
}
else
{
ja = (JSONArray) object;
}
int jaLength = ja.length();
// don't use the new iterator API to maintain support for Android
for (int i = 0; i < jaLength; i++)
{
Object val = ja.opt(i);
// XML does not have good support for arrays. If an array
// appears in a place where XML is lacking, synthesize an
// element.
sb.append(toString(val, tagName == null ? "array" : tagName));
}
return sb.toString();
}
string = (object == null) ? "null" : escape(object.toString());
return (tagName == null) ? "\"" + string + "\""
: (string.length() == 0) ? "<" + tagName + "/>"
: "<" + tagName + ">" + string + "" + tagName + ">";
}
}