Skip to content

Commit

Permalink
Initial implementation of XMLParserConfig object for more flexible XM…
Browse files Browse the repository at this point in the history
…L parsing
  • Loading branch information
John J. Aylward committed Mar 22, 2018
1 parent 06e9ad2 commit 5fe48ef
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 18 deletions.
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
60 changes: 60 additions & 0 deletions XMLParserConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.json;
/**
* Configuration object for the XML parser.
* @author AylwardJ
*
*/
public class XMLParserConfiguration {
public static final XMLParserConfiguration ORIGINAL = new XMLParserConfiguration();
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 <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;
}
}

0 comments on commit 5fe48ef

Please sign in to comment.