Updates XML configuration to use a builder pattern instead of
constructors with many parameters
This commit is contained in:
John J. Aylward 2020-07-21 11:08:40 -04:00
parent 6ddaa13c1e
commit 5a31f9ef5f
5 changed files with 178 additions and 38 deletions

View File

@ -286,7 +286,7 @@ public class XML {
if (x.next() == '[') {
string = x.nextCDATA();
if (string.length() > 0) {
context.accumulate(config.cDataTagName, string);
context.accumulate(config.getcDataTagName(), string);
}
return false;
}
@ -350,13 +350,13 @@ public class XML {
throw x.syntaxError("Missing value");
}
if (config.convertNilAttributeToNull
if (config.isConvertNilAttributeToNull()
&& NULL_ATTR.equals(string)
&& Boolean.parseBoolean((String) token)) {
nilAttributeFound = true;
} else if (!nilAttributeFound) {
jsonObject.accumulate(string,
config.keepStrings
config.isKeepStrings()
? ((String) token)
: stringToValue((String) token));
}
@ -392,8 +392,8 @@ public class XML {
} else if (token instanceof String) {
string = (String) token;
if (string.length() > 0) {
jsonObject.accumulate(config.cDataTagName,
config.keepStrings ? string : stringToValue(string));
jsonObject.accumulate(config.getcDataTagName(),
config.isKeepStrings() ? string : stringToValue(string));
}
} else if (token == LT) {
@ -402,8 +402,8 @@ public class XML {
if (jsonObject.length() == 0) {
context.accumulate(tagName, "");
} else if (jsonObject.length() == 1
&& jsonObject.opt(config.cDataTagName) != null) {
context.accumulate(tagName, jsonObject.opt(config.cDataTagName));
&& jsonObject.opt(config.getcDataTagName()) != null) {
context.accumulate(tagName, jsonObject.opt(config.getcDataTagName()));
} else {
context.accumulate(tagName, jsonObject);
}
@ -748,7 +748,7 @@ public class XML {
}
// Emit content in body
if (key.equals(config.cDataTagName)) {
if (key.equals(config.getcDataTagName())) {
if (value instanceof JSONArray) {
ja = (JSONArray) value;
int jaLength = ja.length();

View File

@ -24,44 +24,57 @@ SOFTWARE.
*/
/**
* Configuration object for the XML parser.
* Configuration object for the XML parser. The configuration is immutable.
* @author AylwardJ
*
*/
@SuppressWarnings({""})
public class XMLParserConfiguration {
/** Original Configuration of the XML Parser. */
public static final XMLParserConfiguration ORIGINAL = new XMLParserConfiguration();
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);
public static final XMLParserConfiguration KEEP_STRINGS
= new XMLParserConfiguration().withKeepStrings(true);
/**
* When parsing the XML into JSON, specifies if values should be kept as strings (true), or if
* When parsing the XML into JSON, specifies if values should be kept as strings (<code>true</code>), or if
* they should try to be guessed into JSON values (numeric, boolean, string)
*/
public final boolean keepStrings;
private 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;
private String cDataTagName;
/**
* When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
* should be kept as attribute(false), or they should be converted to null(true)
* should be kept as attribute(<code>false</code>), or they should be converted to
* <code>null</code>(<code>true</code>)
*/
public final boolean convertNilAttributeToNull;
private boolean convertNilAttributeToNull;
/**
* Default parser configuration. Does not keep strings, and the CDATA Tag Name is "content".
* Default parser configuration. Does not keep strings (tries to implicitly convert
* values), and the CDATA Tag Name is "content".
*/
public XMLParserConfiguration () {
this(false, "content", false);
this.keepStrings = false;
this.cDataTagName = "content";
this.convertNilAttributeToNull = false;
}
/**
* 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.
* @deprecated This constructor has been deprecated in favor of using the new builder
* pattern for the configuration.
* This constructor may be removed in a future release.
*/
@Deprecated
public XMLParserConfiguration (final boolean keepStrings) {
this(keepStrings, "content", false);
}
@ -72,7 +85,11 @@ public class XMLParserConfiguration {
* 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.
* @deprecated This constructor has been deprecated in favor of using the new builder
* pattern for the configuration.
* This constructor may be removed in a future release.
*/
@Deprecated
public XMLParserConfiguration (final String cDataTagName) {
this(false, cDataTagName, false);
}
@ -83,7 +100,11 @@ public class XMLParserConfiguration {
* <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.
* @deprecated This constructor has been deprecated in favor of using the new builder
* pattern for the configuration.
* This constructor may be removed in a future release.
*/
@Deprecated
public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName) {
this.keepStrings = keepStrings;
this.cDataTagName = cDataTagName;
@ -98,10 +119,110 @@ public class XMLParserConfiguration {
* to use that value as the JSONObject key name to process as CDATA.
* @param convertNilAttributeToNull <code>true</code> to parse values with attribute xsi:nil="true" as null.
* <code>false</code> to parse values with attribute xsi:nil="true" as {"xsi:nil":true}.
* @deprecated This constructor has been deprecated in favor of using the new builder
* pattern for the configuration.
* This constructor may be removed or marked private in a future release.
*/
@Deprecated
public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, final boolean convertNilAttributeToNull) {
this.keepStrings = keepStrings;
this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = convertNilAttributeToNull;
}
/**
* Provides a new instance of the same configuration.
*/
@Override
protected XMLParserConfiguration clone() {
// future modifications to this method should always ensure a "deep"
// clone in the case of collections. i.e. if a Map is added as a configuration
// item, a new map instance should be created and if possible each value in the
// map should be cloned as well. If the values of the map are known to also
// be immutable, then a shallow clone of the map is acceptable.
return new XMLParserConfiguration(
this.keepStrings,
this.cDataTagName,
this.convertNilAttributeToNull
);
}
/**
* When parsing the XML into JSON, specifies if values should be kept as strings (<code>true</code>), or if
* they should try to be guessed into JSON values (numeric, boolean, string)
*
* @return The {@link #keepStrings} configuration value.
*/
public boolean isKeepStrings() {
return this.keepStrings;
}
/**
* When parsing the XML into JSON, specifies if values should be kept as strings (<code>true</code>), or if
* they should try to be guessed into JSON values (numeric, boolean, string)
*
* @param newVal
* new value to use for the {@link #keepStrings} configuration option.
*
* @return The existing configuration will not be modified. A new configuration is returned.
*/
public XMLParserConfiguration withKeepStrings(final boolean newVal) {
XMLParserConfiguration newConfig = this.clone();
newConfig.keepStrings = newVal;
return newConfig;
}
/**
* 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.
*
* @return The {@link #cDataTagName} configuration value.
*/
public String getcDataTagName() {
return this.cDataTagName;
}
/**
* 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.
*
* @param newVal
* new value to use for the {@link #cDataTagName} configuration option.
*
* @return The existing configuration will not be modified. A new configuration is returned.
*/
public XMLParserConfiguration withcDataTagName(final String newVal) {
XMLParserConfiguration newConfig = this.clone();
newConfig.cDataTagName = newVal;
return newConfig;
}
/**
* When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
* should be kept as attribute(<code>false</code>), or they should be converted to
* <code>null</code>(<code>true</code>)
*
* @return The {@link #convertNilAttributeToNull} configuration value.
*/
public boolean isConvertNilAttributeToNull() {
return this.convertNilAttributeToNull;
}
/**
* When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
* should be kept as attribute(<code>false</code>), or they should be converted to
* <code>null</code>(<code>true</code>)
*
* @param newVal
* new value to use for the {@link #convertNilAttributeToNull} configuration option.
*
* @return The existing configuration will not be modified. A new configuration is returned.
*/
public XMLParserConfiguration withConvertNilAttributeToNull(final boolean newVal) {
XMLParserConfiguration newConfig = this.clone();
newConfig.convertNilAttributeToNull = newVal;
return newConfig;
}
}

View File

@ -1131,7 +1131,7 @@ public class JSONArrayTest {
assertNotNull(new JSONArray(5));
// Check Size -> Even though the capacity of the JSONArray can be specified using a positive
// integer but the length of JSONArray always reflects upon the items added into it.
assertEquals(0l, (long)new JSONArray(10).length());
assertEquals(0l, new JSONArray(10).length());
try {
assertNotNull("Should throw an exception", new JSONArray(-1));
} catch (JSONException e) {

View File

@ -209,7 +209,7 @@ public class XMLConfigurationTest {
"</addresses>";
try {
XMLParserConfiguration config =
new XMLParserConfiguration("altContent");
new XMLParserConfiguration().withcDataTagName("altContent");
XML.toJSONObject(xmlStr, config);
fail("Expecting a JSONException");
} catch (JSONException e) {
@ -300,7 +300,7 @@ public class XMLConfigurationTest {
"XMLSchema-instance\"}}";
XMLParserConfiguration config =
new XMLParserConfiguration("altContent");
new XMLParserConfiguration().withcDataTagName("altContent");
compareStringToJSONObject(xmlStr, expectedStr, config);
compareReaderToJSONObject(xmlStr, expectedStr, config);
compareFileToJSONObject(xmlStr, expectedStr);
@ -325,7 +325,7 @@ public class XMLConfigurationTest {
" </address>\n"+
"</addresses>";
XMLParserConfiguration config =
new XMLParserConfiguration("altContent");
new XMLParserConfiguration().withcDataTagName("altContent");
JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
String expectedStr = "{\"addresses\":{\"address\":{\"street\":\"Baker "+
"street 5\",\"name\":\"Joe Tester\",\"altContent\":\" this is -- "+
@ -378,7 +378,7 @@ public class XMLConfigurationTest {
String expectedStr =
"{\"addresses\":{\"altContent\":\">\"}}";
JSONObject expectedJsonObject = new JSONObject(expectedStr);
XMLParserConfiguration config = new XMLParserConfiguration("altContent");
XMLParserConfiguration config = new XMLParserConfiguration().withcDataTagName("altContent");
String finalStr = XML.toString(expectedJsonObject, null, config);
String expectedFinalStr = "<addresses>&gt;</addresses>";
assertTrue("Should handle expectedFinal: ["+expectedStr+"] final: ["+
@ -395,7 +395,7 @@ public class XMLConfigurationTest {
String expectedStr =
"{\"addresses\":{\"altContent\":[1, 2, 3]}}";
JSONObject expectedJsonObject = new JSONObject(expectedStr);
XMLParserConfiguration config = new XMLParserConfiguration("altContent");
XMLParserConfiguration config = new XMLParserConfiguration().withcDataTagName("altContent");
String finalStr = XML.toString(expectedJsonObject, null, config);
String expectedFinalStr = "<addresses>"+
"1\n2\n3"+
@ -595,7 +595,9 @@ public class XMLConfigurationTest {
// multiple consecutive standalone cdatas are accumulated into an array
xmlStr = "<tag1></tag1><![CDATA[if (a < b && a > 0) then return]]><tag2></tag2><![CDATA[here is another cdata]]>";
jsonObject = XML.toJSONObject(xmlStr,
new XMLParserConfiguration(true, "altContent"));
new XMLParserConfiguration()
.withKeepStrings(true)
.withcDataTagName("altContent"));
assertTrue("2. 3 items", 3 == jsonObject.length());
assertTrue("2. empty tag1", "".equals(jsonObject.get("tag1")));
assertTrue("2. empty tag2", "".equals(jsonObject.get("tag2")));
@ -612,7 +614,9 @@ public class XMLConfigurationTest {
*/
xmlStr = "<tag1>value 1</tag1>";
jsonObject = XML.toJSONObject(xmlStr,
new XMLParserConfiguration(true, "altContent"));
new XMLParserConfiguration()
.withKeepStrings(true)
.withcDataTagName("altContent"));
assertTrue("3. 2 items", 1 == jsonObject.length());
assertTrue("3. value tag1", "value 1".equals(jsonObject.get("tag1")));
@ -623,7 +627,9 @@ public class XMLConfigurationTest {
*/
xmlStr = "<tag1>value 1</tag1><tag1>2</tag1><tag1>true</tag1>";
jsonObject = XML.toJSONObject(xmlStr,
new XMLParserConfiguration(true, "altContent"));
new XMLParserConfiguration()
.withKeepStrings(true)
.withcDataTagName("altContent"));
assertTrue("4. 1 item", 1 == jsonObject.length());
assertTrue("4. content array found", jsonObject.get("tag1") instanceof JSONArray);
jsonArray = jsonObject.getJSONArray("tag1");
@ -639,7 +645,9 @@ public class XMLConfigurationTest {
*/
xmlStr = "<tag1>val1<tag2/>val2</tag1>";
jsonObject = XML.toJSONObject(xmlStr,
new XMLParserConfiguration(true, "altContent"));
new XMLParserConfiguration()
.withKeepStrings(true)
.withcDataTagName("altContent"));
assertTrue("5. 1 item", 1 == jsonObject.length());
assertTrue("5. jsonObject found", jsonObject.get("tag1")
instanceof JSONObject);
@ -659,7 +667,9 @@ public class XMLConfigurationTest {
*/
xmlStr = "<tag1>val1<tag2/></tag1>";
jsonObject = XML.toJSONObject(xmlStr,
new XMLParserConfiguration(true, "altContent"));
new XMLParserConfiguration()
.withKeepStrings(true)
.withcDataTagName("altContent"));
assertTrue("6. 1 item", 1 == jsonObject.length());
assertTrue("6. jsonObject found", jsonObject.get("tag1") instanceof JSONObject);
jsonObject = jsonObject.getJSONObject("tag1");
@ -674,7 +684,9 @@ public class XMLConfigurationTest {
*/
xmlStr = "<tag1>val1<altContent/></tag1>";
jsonObject = XML.toJSONObject(xmlStr,
new XMLParserConfiguration(true, "altContent"));
new XMLParserConfiguration()
.withKeepStrings(true)
.withcDataTagName("altContent"));
assertTrue("7. 1 item", 1 == jsonObject.length());
assertTrue("7. jsonArray found",
jsonObject.get("tag1") instanceof JSONArray);
@ -713,7 +725,9 @@ public class XMLConfigurationTest {
"}";
jsonObject = new JSONObject(jsonStr);
xmlStr = XML.toString(jsonObject, null,
new XMLParserConfiguration(true, "altContent"));
new XMLParserConfiguration()
.withKeepStrings(true)
.withcDataTagName("altContent"));
/*
* This is the created XML. Looks like content was mistaken for
* complex (child node + text) XML.
@ -739,7 +753,7 @@ public class XMLConfigurationTest {
final String originalXml = "<root><id>01</id><id>1</id><id>00</id><id>0</id><item id=\"01\"/><title>True</title></root>";
final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0],\"title\":true}}");
final JSONObject actualJsonOutput = XML.toJSONObject(originalXml,
new XMLParserConfiguration(false));
new XMLParserConfiguration().withKeepStrings(false));
Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);
}
@ -749,7 +763,7 @@ public class XMLConfigurationTest {
@Test
public void testToJSONArray_reversibility() {
final String originalXml = "<root><id>01</id><id>1</id><id>00</id><id>0</id><item id=\"01\"/><title>True</title></root>";
XMLParserConfiguration config = new XMLParserConfiguration(false);
XMLParserConfiguration config = new XMLParserConfiguration().withKeepStrings(false);
final String revertedXml =
XML.toString(XML.toJSONObject(originalXml, config),
null, config);
@ -765,7 +779,7 @@ public class XMLConfigurationTest {
final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\"],\"title\":\"True\"}}");
final JSONObject json = XML.toJSONObject(originalXml,
new XMLParserConfiguration(true));
new XMLParserConfiguration().withKeepStrings(true));
Util.compareActualVsExpectedJsonObjects(json, expected);
final String reverseXml = XML.toString(json);
@ -850,7 +864,9 @@ public class XMLConfigurationTest {
// keep strings, use the altContent tag
XMLParserConfiguration config =
new XMLParserConfiguration(true, "altContent");
new XMLParserConfiguration()
.withKeepStrings(true)
.withcDataTagName("altContent");
JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
// num is parsed as a string
assertEquals(jsonObject.getJSONObject("addresses").
@ -875,7 +891,7 @@ public class XMLConfigurationTest {
Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
// use alternate content name
config = new XMLParserConfiguration("altContent");
config = new XMLParserConfiguration().withcDataTagName("altContent");
jsonObject = XML.toJSONObject(xmlStr, config);
// num is parsed as a number
assertEquals(jsonObject.getJSONObject("addresses").

View File

@ -883,7 +883,10 @@ public class XMLTest {
final String originalXml = "<root><id xsi:nil=\"true\"/></root>";
final String expectedJsonString = "{\"root\":{\"id\":null}}";
final JSONObject json = XML.toJSONObject(originalXml, new XMLParserConfiguration(false, "content", true));
final JSONObject json = XML.toJSONObject(originalXml,
new XMLParserConfiguration()
.withKeepStrings(false).withcDataTagName("content")
.withConvertNilAttributeToNull(true));
assertEquals(expectedJsonString, json.toString());
}