Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of XMLParserConfig object for flexible XML Parsing #412

Merged
merged 1 commit into from
Dec 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 85 additions & 18 deletions XML.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '&';

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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, "");
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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 <code>&lt;[ [ ]]></code>
* 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;
Expand Down Expand Up @@ -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 <code>&lt;[ [ ]]></code>
* 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.
*
Expand All @@ -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);
}

/**
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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("</");
sb.append(key);
sb.append('>');
} else {
sb.append(toString(val, key));
sb.append(toString(val, key, config));
}
}
} else if ("".equals(value)) {
Expand All @@ -642,7 +709,7 @@ public static String toString(final Object object, final String tagName)
// Emit a new tag <k>

} else {
sb.append(toString(value, key));
sb.append(toString(value, key, config));
}
}
if (tagName != null) {
Expand All @@ -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
// <array> element.
sb.append(toString(val, tagName == null ? "array" : tagName));
sb.append(toString(val, tagName == null ? "array" : tagName, config));
}
return sb.toString();
}
Expand Down
86 changes: 86 additions & 0 deletions XMLParserConfiguration.java
Original file line number Diff line number Diff line change
@@ -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 Object that indicates a CDATA section. Historically this has
* been the value "content" but can be changed. Use <code>null</code> 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 <code>true</code> to parse all values as string.
* <code>false</code> 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 <code>null</code> to
* disable CDATA processing
* @param cDataTagName<code>null</code> 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 <code>true</code> to parse all values as string.
* <code>false</code> to try and convert XML string values into a JSON value.
* @param cDataTagName<code>null</code> 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;
}
}