Skip to content

Commit

Permalink
Merge pull request #832 from keatontaylor10/feature-disable-whitespac…
Browse files Browse the repository at this point in the history
…e-trim

Add a config flag to disable whitespace trimming
  • Loading branch information
stleary authored Jan 27, 2024
2 parents 55b824d + 7915d85 commit f2d2098
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 6 deletions.
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()) {
removeEmpty(jsonObject, config);
}
context.accumulate(tagName, jsonObject);
}
}
Expand All @@ -445,6 +448,48 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
}
}
}
/**
* 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) {
if (jsonObject.has(config.getcDataTagName())) {
final Object s = jsonObject.get(config.getcDataTagName());
if (s instanceof String) {
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());
}
}
}
}

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;

/**
* 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;
}

/**
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;

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) {
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());
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

0 comments on commit f2d2098

Please sign in to comment.