mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2025-07-01 15:36:45 +00:00
585 lines
13 KiB
Java
585 lines
13 KiB
Java
package ninja.bytecode.shuriken.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 shall be used for Good, not Evil.
|
|
|
|
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 2014-05-03
|
|
*/
|
|
public class XML
|
|
{
|
|
|
|
/** The Character '&'. */
|
|
public static final Character AMP = '&';
|
|
|
|
/** The Character '''. */
|
|
public static final Character APOS = '\'';
|
|
|
|
/** The Character '!'. */
|
|
public static final Character BANG = '!';
|
|
|
|
/** The Character '='. */
|
|
public static final Character EQ = '=';
|
|
|
|
/** The Character '>'. */
|
|
public static final Character GT = '>';
|
|
|
|
/** The Character '<'. */
|
|
public static final Character LT = '<';
|
|
|
|
/** The Character '?'. */
|
|
public static final Character QUEST = '?';
|
|
|
|
/** The Character '"'. */
|
|
public static final Character QUOT = '"';
|
|
|
|
/** The Character '/'. */
|
|
public static final Character SLASH = '/';
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
StringBuilder sb = new StringBuilder(string.length());
|
|
for(int i = 0, length = string.length(); i < length; i++)
|
|
{
|
|
char c = string.charAt(i);
|
|
switch(c)
|
|
{
|
|
case '&':
|
|
sb.append("&");
|
|
break;
|
|
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
|
|
* A 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;
|
|
JSONObject jsonobject = null;
|
|
String string;
|
|
String tagName;
|
|
Object token;
|
|
|
|
// Test for and skip past these forms:
|
|
// <!-- ... -->
|
|
// <! ... >
|
|
// <![ ... ]]>
|
|
// <? ... ?>
|
|
// Report errors for these forms:
|
|
// <>
|
|
// <=
|
|
// <<
|
|
|
|
token = x.nextToken();
|
|
|
|
// <!
|
|
|
|
if(token == BANG)
|
|
{
|
|
c = x.next();
|
|
if(c == '-')
|
|
{
|
|
if(x.next() == '-')
|
|
{
|
|
x.skipPast("-->");
|
|
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 '<!'.");
|
|
} else if(token == LT)
|
|
{
|
|
i += 1;
|
|
} else if(token == GT)
|
|
{
|
|
i -= 1;
|
|
}
|
|
} while(i > 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, XML.stringToValue((String) token));
|
|
token = null;
|
|
} else
|
|
{
|
|
jsonobject.accumulate(string, "");
|
|
}
|
|
|
|
// Empty tag <.../>
|
|
|
|
} else if(token == SLASH)
|
|
{
|
|
if(x.nextToken() != GT)
|
|
{
|
|
throw x.syntaxError("Misshaped tag");
|
|
}
|
|
if(jsonobject.length() > 0)
|
|
{
|
|
context.accumulate(tagName, jsonobject);
|
|
} else
|
|
{
|
|
context.accumulate(tagName, "");
|
|
}
|
|
return false;
|
|
|
|
// Content, between <...> and </...>
|
|
|
|
} else if(token == GT)
|
|
{
|
|
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", XML.stringToValue(string));
|
|
}
|
|
|
|
// Nested element
|
|
|
|
} else if(token == LT)
|
|
{
|
|
if(parse(x, jsonobject, tagName))
|
|
{
|
|
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");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Try to convert a string into a number, boolean, or null. If the string
|
|
* can't be converted, return the string. This is much less ambitious than
|
|
* JSONObject.stringToValue, especially because it does not attempt to
|
|
* convert plus forms, octal forms, hex forms, or E forms lacking decimal
|
|
* points.
|
|
*
|
|
* @param string
|
|
* A String.
|
|
* @return A simple JSON value.
|
|
*/
|
|
public static Object stringToValue(String string)
|
|
{
|
|
if("true".equalsIgnoreCase(string))
|
|
{
|
|
return Boolean.TRUE;
|
|
}
|
|
if("false".equalsIgnoreCase(string))
|
|
{
|
|
return Boolean.FALSE;
|
|
}
|
|
if("null".equalsIgnoreCase(string))
|
|
{
|
|
return JSONObject.NULL;
|
|
}
|
|
|
|
// If it might be a number, try converting it, first as a Long, and then
|
|
// as a
|
|
// Double. If that doesn't work, return the string.
|
|
|
|
try
|
|
{
|
|
char initial = string.charAt(0);
|
|
if(initial == '-' || (initial >= '0' && initial <= '9'))
|
|
{
|
|
Long value = new Long(string);
|
|
if(value.toString().equals(string))
|
|
{
|
|
return value;
|
|
}
|
|
}
|
|
} catch(Exception ignore)
|
|
{
|
|
try
|
|
{
|
|
Double value = new Double(string);
|
|
if(value.toString().equals(string))
|
|
{
|
|
return value;
|
|
}
|
|
} catch(Exception ignoreAlso)
|
|
{
|
|
}
|
|
}
|
|
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 <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 jo = new JSONObject();
|
|
XMLTokener x = new XMLTokener(string);
|
|
while(x.more() && x.skipPast("<"))
|
|
{
|
|
parse(x, jo, null);
|
|
}
|
|
return jo;
|
|
}
|
|
|
|
/**
|
|
* Convert a JSONObject into a well-formed, element-normal XML string.
|
|
*
|
|
* @param object
|
|
* A JSONObject.
|
|
* @return A string.
|
|
* @throws JSONException
|
|
*/
|
|
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
|
|
*/
|
|
public static String toString(Object object, String tagName) throws JSONException
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
int i;
|
|
JSONArray ja;
|
|
JSONObject jo;
|
|
String key;
|
|
Iterator<String> keys;
|
|
int length;
|
|
String string;
|
|
Object value;
|
|
if(object instanceof JSONObject)
|
|
{
|
|
|
|
// Emit <tagName>
|
|
|
|
if(tagName != null)
|
|
{
|
|
sb.append('<');
|
|
sb.append(tagName);
|
|
sb.append('>');
|
|
}
|
|
|
|
// Loop thru the keys.
|
|
|
|
jo = (JSONObject) object;
|
|
keys = jo.keys();
|
|
while(keys.hasNext())
|
|
{
|
|
key = keys.next();
|
|
value = jo.opt(key);
|
|
if(value == null)
|
|
{
|
|
value = "";
|
|
}
|
|
string = value instanceof String ? (String) value : null;
|
|
|
|
// Emit content in body
|
|
|
|
if("content".equals(key))
|
|
{
|
|
if(value instanceof JSONArray)
|
|
{
|
|
ja = (JSONArray) value;
|
|
length = ja.length();
|
|
for(i = 0; i < length; i += 1)
|
|
{
|
|
if(i > 0)
|
|
{
|
|
sb.append('\n');
|
|
}
|
|
sb.append(escape(ja.get(i).toString()));
|
|
}
|
|
} else
|
|
{
|
|
sb.append(escape(value.toString()));
|
|
}
|
|
|
|
// Emit an array of similar keys
|
|
|
|
} else if(value instanceof JSONArray)
|
|
{
|
|
ja = (JSONArray) value;
|
|
length = ja.length();
|
|
for(i = 0; i < length; i += 1)
|
|
{
|
|
value = ja.get(i);
|
|
if(value instanceof JSONArray)
|
|
{
|
|
sb.append('<');
|
|
sb.append(key);
|
|
sb.append('>');
|
|
sb.append(toString(value));
|
|
sb.append("</");
|
|
sb.append(key);
|
|
sb.append('>');
|
|
} else
|
|
{
|
|
sb.append(toString(value, key));
|
|
}
|
|
}
|
|
} else if("".equals(value))
|
|
{
|
|
sb.append('<');
|
|
sb.append(key);
|
|
sb.append("/>");
|
|
|
|
// Emit a new tag <k>
|
|
|
|
} else
|
|
{
|
|
sb.append(toString(value, key));
|
|
}
|
|
}
|
|
if(tagName != null)
|
|
{
|
|
|
|
// Emit the </tagname> close tag
|
|
|
|
sb.append("</");
|
|
sb.append(tagName);
|
|
sb.append('>');
|
|
}
|
|
return sb.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(object.getClass().isArray())
|
|
{
|
|
object = new JSONArray(object);
|
|
}
|
|
if(object instanceof JSONArray)
|
|
{
|
|
ja = (JSONArray) object;
|
|
length = ja.length();
|
|
for(i = 0; i < length; i += 1)
|
|
{
|
|
sb.append(toString(ja.opt(i), tagName == null ? "array" : tagName));
|
|
}
|
|
return sb.toString();
|
|
} else
|
|
{
|
|
string = (object == null) ? "null" : escape(object.toString());
|
|
return (tagName == null) ? "\"" + string + "\"" : (string.length() == 0) ? "<" + tagName + "/>" : "<" + tagName + ">" + string + "</" + tagName + ">";
|
|
}
|
|
}
|
|
}
|
|
}
|