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

Add a config flag to disable whitespace trimming #832

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 46 additions & 1 deletion src/main/java/org/json/XML.java
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,9 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
&& jsonObject.opt(config.getcDataTagName()) != null) {
context.accumulate(tagName, jsonObject.opt(config.getcDataTagName()));
} else {
if (!config.shouldTrimWhiteSpace()) {
stleary marked this conversation as resolved.
Show resolved Hide resolved
removeEmpty(jsonObject, config);
}
context.accumulate(tagName, jsonObject);
}
}
Expand All @@ -445,6 +448,48 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
}
}
}
/**
stleary marked this conversation as resolved.
Show resolved Hide resolved
* This method removes any JSON entry which has the key set by XMLParserConfiguration.cDataTagName
* and contains whitespace as this is caused by whitespace between tags. See test XMLTest.testNestedWithWhitespaceTrimmingDisabled.
* @param jsonObject JSONObject which may require deletion
* @param config The XMLParserConfiguration which includes the cDataTagName
*/
private static void removeEmpty(final JSONObject jsonObject, final XMLParserConfiguration config) {
stleary marked this conversation as resolved.
Show resolved Hide resolved
if (jsonObject.has(config.getcDataTagName())) {
final Object s = jsonObject.get(config.getcDataTagName());
if (s instanceof String) {
stleary marked this conversation as resolved.
Show resolved Hide resolved
if (isStringAllWhiteSpace(s.toString())) {
jsonObject.remove(config.getcDataTagName());
}
}
else if (s instanceof JSONArray) {
final JSONArray sArray = (JSONArray) s;
for (int k = sArray.length()-1; k >= 0; k--){
final Object eachString = sArray.get(k);
if (eachString instanceof String) {
String s1 = (String) eachString;
if (isStringAllWhiteSpace(s1)) {
sArray.remove(k);
}
}
}
if (sArray.isEmpty()) {
jsonObject.remove(config.getcDataTagName());
stleary marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}

private static boolean isStringAllWhiteSpace(final String s) {
for (int k = 0; k<s.length(); k++){
final char eachChar = s.charAt(k);
if (!Character.isWhitespace(eachChar)) {
return false;
}
}
return true;
}


/**
* This method tries to convert the given string value to the target object
Expand Down Expand Up @@ -594,7 +639,7 @@ public static JSONObject toJSONObject(Reader reader, boolean keepStrings) throws
*/
public static JSONObject toJSONObject(Reader reader, XMLParserConfiguration config) throws JSONException {
JSONObject jo = new JSONObject();
XMLTokener x = new XMLTokener(reader);
XMLTokener x = new XMLTokener(reader, config);
while (x.more()) {
x.skipPast("<");
if(x.more()) {
Expand Down
32 changes: 30 additions & 2 deletions src/main/java/org/json/XMLParserConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,26 @@ public class XMLParserConfiguration extends ParserConfiguration {
*/
private Set<String> forceList;


/**
* Flag to indicate whether white space should be trimmed when parsing XML.
* The default behaviour is to trim white space. When this is set to false, inputting XML
* with tags that are the same as the value of cDataTagName is unsupported. It is recommended to set cDataTagName
* to a distinct value in this case.
*/
private boolean shouldTrimWhiteSpace;
stleary marked this conversation as resolved.
Show resolved Hide resolved

/**
* Default parser configuration. Does not keep strings (tries to implicitly convert
* values), and the CDATA Tag Name is "content".
* values), and the CDATA Tag Name is "content". Trims whitespace.
*/
public XMLParserConfiguration () {
super();
this.cDataTagName = "content";
this.convertNilAttributeToNull = false;
this.xsiTypeMap = Collections.emptyMap();
this.forceList = Collections.emptySet();
this.shouldTrimWhiteSpace = true;
stleary marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down Expand Up @@ -172,7 +182,7 @@ protected XMLParserConfiguration clone() {
// 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(
final XMLParserConfiguration config = new XMLParserConfiguration(
this.keepStrings,
this.cDataTagName,
this.convertNilAttributeToNull,
Expand All @@ -181,6 +191,8 @@ protected XMLParserConfiguration clone() {
this.maxNestingDepth,
this.closeEmptyTag
);
config.shouldTrimWhiteSpace = this.shouldTrimWhiteSpace;
return config;
}

/**
Expand Down Expand Up @@ -327,7 +339,23 @@ public XMLParserConfiguration withCloseEmptyTag(boolean closeEmptyTag){
return clonedConfiguration;
}

/**
* Sets whether whitespace should be trimmed inside of tags. *NOTE* Do not use this if
* you expect your XML tags to have names that are the same as cDataTagName as this is unsupported.
* cDataTagName should be set to a distinct value in these cases.
* @param shouldTrimWhiteSpace boolean to set trimming on or off. Off is default.
* @return same instance of configuration with empty tag config updated
*/
public XMLParserConfiguration withShouldTrimWhitespace(boolean shouldTrimWhiteSpace){
XMLParserConfiguration clonedConfiguration = this.clone();
clonedConfiguration.shouldTrimWhiteSpace = shouldTrimWhiteSpace;
return clonedConfiguration;
}

public boolean isCloseEmptyTag() {
return this.closeEmptyTag;
}
public boolean shouldTrimWhiteSpace() {
return this.shouldTrimWhiteSpace;
}
}
18 changes: 16 additions & 2 deletions src/main/java/org/json/XMLTokener.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public class XMLTokener extends JSONTokener {
*/
public static final java.util.HashMap<String, Character> entity;

private XMLParserConfiguration configuration = XMLParserConfiguration.ORIGINAL;

stleary marked this conversation as resolved.
Show resolved Hide resolved
static {
entity = new java.util.HashMap<String, Character>(8);
entity.put("amp", XML.AMP);
Expand All @@ -45,6 +47,16 @@ public XMLTokener(String s) {
super(s);
}

/**
* Construct an XMLTokener from a Reader and an XMLParserConfiguration.
* @param r A source reader.
* @param configuration the configuration that can be used to set certain flags
*/
public XMLTokener(Reader r, XMLParserConfiguration configuration) {
stleary marked this conversation as resolved.
Show resolved Hide resolved
super(r);
this.configuration = configuration;
}

/**
* Get the text in the CDATA block.
* @return The string up to the <code>]]&gt;</code>.
Expand Down Expand Up @@ -83,7 +95,7 @@ public Object nextContent() throws JSONException {
StringBuilder sb;
do {
c = next();
} while (Character.isWhitespace(c));
} while (Character.isWhitespace(c) && configuration.shouldTrimWhiteSpace());
stleary marked this conversation as resolved.
Show resolved Hide resolved
if (c == 0) {
return null;
}
Expand All @@ -97,7 +109,9 @@ public Object nextContent() throws JSONException {
}
if (c == '<') {
back();
return sb.toString().trim();
if (configuration.shouldTrimWhiteSpace()) {
return sb.toString().trim();
} else return sb.toString();
}
if (c == '&') {
sb.append(nextEntity(c));
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/org/json/junit/XMLConfigurationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1181,4 +1181,4 @@ private void compareFileToJSONObject(String xmlStr, String expectedStr) {
assertTrue("Error: " +e.getMessage(), false);
}
}
}
}
74 changes: 74 additions & 0 deletions src/test/java/org/json/junit/XMLTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1323,6 +1323,80 @@ public void testMaxNestingDepthWithValidFittingXML() {
"parameter of the XMLParserConfiguration used");
}
}
@Test
public void testWithWhitespaceTrimmingDisabled() {
String originalXml = "<testXml> Test Whitespace String \t </testXml>";

JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(false));
String expectedJsonString = "{\"testXml\":\" Test Whitespace String \t \"}";
JSONObject expectedJson = new JSONObject(expectedJsonString);
Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
}
@Test
public void testNestedWithWhitespaceTrimmingDisabled() {
String originalXml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
"<addresses>\n"+
" <address>\n"+
" <name> Sherlock Holmes </name>\n"+
" </address>\n"+
"</addresses>";

JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(false));
String expectedJsonString = "{\"addresses\":{\"address\":{\"name\":\" Sherlock Holmes \"}}}";
JSONObject expectedJson = new JSONObject(expectedJsonString);
Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
}
@Test
public void shouldTrimWhitespaceDoesNotSupportTagsEqualingCDataTagName() {
// When using withShouldTrimWhitespace = true, input containing tags with same name as cDataTagName is unsupported and should not be used in conjunction
String originalXml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
"<addresses>\n"+
" <address>\n"+
" <content> Sherlock Holmes </content>\n"+
" </address>\n"+
"</addresses>";

JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(false).withcDataTagName("content"));
String expectedJsonString = "{\"addresses\":{\"address\":[[\"\\n \",\" Sherlock Holmes \",\"\\n \"]]}}";
JSONObject expectedJson = new JSONObject(expectedJsonString);
Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
}
@Test
public void shouldTrimWhitespaceEnabledDropsTagsEqualingCDataTagNameButValueRemains() {
String originalXml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
"<addresses>\n"+
" <address>\n"+
" <content> Sherlock Holmes </content>\n"+
" </address>\n"+
"</addresses>";

JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(true).withcDataTagName("content"));
String expectedJsonString = "{\"addresses\":{\"address\":\"Sherlock Holmes\"}}";
JSONObject expectedJson = new JSONObject(expectedJsonString);
Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
}
@Test
public void testWithWhitespaceTrimmingEnabled() {
String originalXml = "<testXml> Test Whitespace String \t </testXml>";

JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(true));
String expectedJsonString = "{\"testXml\":\"Test Whitespace String\"}";
JSONObject expectedJson = new JSONObject(expectedJsonString);
Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
}
@Test
public void testWithWhitespaceTrimmingEnabledByDefault() {
String originalXml = "<testXml> Test Whitespace String \t </testXml>";

JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration());
String expectedJsonString = "{\"testXml\":\"Test Whitespace String\"}";
JSONObject expectedJson = new JSONObject(expectedJsonString);
Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
}

}


Expand Down