diff --git a/XML.java b/XML.java index 55362b274..73090fc98 100644 --- a/XML.java +++ b/XML.java @@ -37,6 +37,7 @@ of this software and associated documentation files (the "Software"), to deal */ @SuppressWarnings("boxing") public class XML { + /** The Character '&'. */ public static final Character AMP = '&'; @@ -241,7 +242,7 @@ public static void noSpace(String string) throws JSONException { * @return true if the close tag is processed. * @throws JSONException */ - private static boolean parse(XMLTokener x, JSONObject context, String name, boolean keepStrings) + private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config) throws JSONException { char c; int i; @@ -278,7 +279,7 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, bool if (x.next() == '[') { string = x.nextCDATA(); if (string.length() > 0) { - context.accumulate("content", string); + context.accumulate(config.cDataTagName, string); } return false; } @@ -341,7 +342,7 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, bool throw x.syntaxError("Missing value"); } jsonobject.accumulate(string, - keepStrings ? ((String)token) : stringToValue((String) token)); + config.keepStrings ? ((String)token) : stringToValue((String) token)); token = null; } else { jsonobject.accumulate(string, ""); @@ -372,19 +373,19 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, bool } else if (token instanceof String) { string = (String) token; if (string.length() > 0) { - jsonobject.accumulate("content", - keepStrings ? string : stringToValue(string)); + jsonobject.accumulate(config.cDataTagName, + config.keepStrings ? string : stringToValue(string)); } } else if (token == LT) { // Nested element - if (parse(x, jsonobject, tagName,keepStrings)) { + if (parse(x, jsonobject, tagName, config)) { if (jsonobject.length() == 0) { context.accumulate(tagName, ""); } else if (jsonobject.length() == 1 - && jsonobject.opt("content") != null) { + && jsonobject.opt(config.cDataTagName) != null) { context.accumulate(tagName, - jsonobject.opt("content")); + jsonobject.opt(config.cDataTagName)); } else { context.accumulate(tagName, jsonobject); } @@ -469,7 +470,7 @@ public static Object stringToValue(String 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); + return toJSONObject(string, XMLParserConfiguration.ORIGINAL); } /** @@ -488,7 +489,7 @@ public static JSONObject toJSONObject(String string) throws JSONException { * @throws JSONException Thrown if there is an errors while parsing the string */ public static JSONObject toJSONObject(Reader reader) throws JSONException { - return toJSONObject(reader, false); + return toJSONObject(reader, XMLParserConfiguration.ORIGINAL); } /** @@ -512,12 +513,38 @@ public static JSONObject toJSONObject(Reader reader) throws JSONException { * @throws JSONException Thrown if there is an errors while parsing the string */ public static JSONObject toJSONObject(Reader reader, boolean keepStrings) throws JSONException { + if(keepStrings) { + return toJSONObject(reader, XMLParserConfiguration.KEEP_STRINGS); + } + return toJSONObject(reader, XMLParserConfiguration.ORIGINAL); + } + + /** + * 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 config Configuration options for the parser + * @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, XMLParserConfiguration config) throws JSONException { JSONObject jo = new JSONObject(); XMLTokener x = new XMLTokener(reader); while (x.more()) { x.skipPast("<"); if(x.more()) { - parse(x, jo, null, keepStrings); + parse(x, jo, null, config); } } return jo; @@ -548,6 +575,30 @@ public static JSONObject toJSONObject(String string, boolean keepStrings) throws return toJSONObject(new StringReader(string), keepStrings); } + /** + * 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 config Configuration options for the parser. + * @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, XMLParserConfiguration config) throws JSONException { + return toJSONObject(new StringReader(string), config); + } + /** * Convert a JSONObject into a well-formed, element-normal XML string. * @@ -557,7 +608,21 @@ public static JSONObject toJSONObject(String string, boolean keepStrings) throws * @throws JSONException Thrown if there is an error parsing the string */ public static String toString(Object object) throws JSONException { - return toString(object, null); + return toString(object, null, XMLParserConfiguration.ORIGINAL); + } + + /** + * 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) { + return toString(object, tagName, XMLParserConfiguration.ORIGINAL); } /** @@ -567,10 +632,12 @@ public static String toString(Object object) throws JSONException { * A JSONObject. * @param tagName * The optional name of the enclosing tag. + * @param config + * Configuration that can control output to XML. * @return A string. * @throws JSONException Thrown if there is an error parsing the string */ - public static String toString(final Object object, final String tagName) + public static String toString(final Object object, final String tagName, final XMLParserConfiguration config) throws JSONException { StringBuilder sb = new StringBuilder(); JSONArray ja; @@ -598,7 +665,7 @@ public static String toString(final Object object, final String tagName) } // Emit content in body - if ("content".equals(key)) { + if (key.equals(config.cDataTagName)) { if (value instanceof JSONArray) { ja = (JSONArray) value; int jaLength = ja.length(); @@ -626,12 +693,12 @@ public static String toString(final Object object, final String tagName) sb.append('<'); sb.append(key); sb.append('>'); - sb.append(toString(val)); + sb.append(toString(val, null, config)); sb.append("'); } else { - sb.append(toString(val, key)); + sb.append(toString(val, key, config)); } } } else if ("".equals(value)) { @@ -642,7 +709,7 @@ public static String toString(final Object object, final String tagName) // Emit a new tag } else { - sb.append(toString(value, key)); + sb.append(toString(value, key, config)); } } if (tagName != null) { @@ -669,7 +736,7 @@ public static String toString(final Object object, final String tagName) // 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)); + sb.append(toString(val, tagName == null ? "array" : tagName, config)); } return sb.toString(); } diff --git a/XMLParserConfiguration.java b/XMLParserConfiguration.java new file mode 100644 index 000000000..f1f001bc4 --- /dev/null +++ b/XMLParserConfiguration.java @@ -0,0 +1,86 @@ +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 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. +*/ + +/** + * Configuration object for the XML parser. + * @author AylwardJ + * + */ +public class XMLParserConfiguration { + /** Original Configuration of the XML Parser. */ + public static final XMLParserConfiguration ORIGINAL = new XMLParserConfiguration(); + /** Original configuration of the XML Parser except that values are kept as strings. */ + public static final XMLParserConfiguration KEEP_STRINGS = new XMLParserConfiguration(true); + /** + * When parsing the XML into JSON, specifies if values should be kept as strings (true), or if + * they should try to be guessed into JSON values (numeric, boolean, string) + */ + public final boolean keepStrings; + /** + * The name of the key in a JSON Ojbect that indicates a CDATA section. Historically this has + * been the value "content" but can be changed. Use null to indicate no CDATA + * processing. + */ + public final String cDataTagName; + + /** + * Default parser configuration. Does not keep strings, and the CDATA Tag Name is "content". + */ + public XMLParserConfiguration () { + this(false, "content"); + } + + /** + * Configure the parser string processing and use the default CDATA Tag Name as "content". + * @param keepStrings true to parse all values as string. + * false to try and convert XML string values into a JSON value. + */ + public XMLParserConfiguration (final boolean keepStrings) { + this(keepStrings, "content"); + } + + /** + * Configure the parser string processing to try and convert XML values to JSON values and + * use the passed CDATA Tag Name the processing value. Pass null to + * disable CDATA processing + * @param cDataTagNamenull to disable CDATA processing. Any other value + * to use that value as the JSONObject key name to process as CDATA. + */ + public XMLParserConfiguration (final String cDataTagName) { + this(false, cDataTagName); + } + + /** + * Configure the parser to use custom settings. + * @param keepStrings true to parse all values as string. + * false to try and convert XML string values into a JSON value. + * @param cDataTagNamenull to disable CDATA processing. Any other value + * to use that value as the JSONObject key name to process as CDATA. + */ + public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName) { + this.keepStrings = keepStrings; + this.cDataTagName = cDataTagName; + } +}