Skip to content

Commit 30f5b2d

Browse files
author
Keaton Taylor
committed
Add a config flag to disable whitespace trimming
1 parent 11c29c3 commit 30f5b2d

File tree

5 files changed

+160
-7
lines changed

5 files changed

+160
-7
lines changed

src/main/java/org/json/XML.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,9 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
431431
&& jsonObject.opt(config.getcDataTagName()) != null) {
432432
context.accumulate(tagName, jsonObject.opt(config.getcDataTagName()));
433433
} else {
434+
if (!config.shouldTrimWhiteSpace()) {
435+
removeEmpty(jsonObject, config);
436+
}
434437
context.accumulate(tagName, jsonObject);
435438
}
436439
}
@@ -445,6 +448,48 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
445448
}
446449
}
447450
}
451+
/**
452+
* This method removes any JSON entry which has the key set by XMLParserConfiguration.cDataTagName
453+
* and contains whitespace as this is caused by whitespace between tags. See test XMLTest.testNestedWithWhitespaceTrimmingDisabled.
454+
* @param jsonObject JSONObject which may require deletion
455+
* @param config The XMLParserConfiguration which includes the cDataTagName
456+
*/
457+
private static void removeEmpty(final JSONObject jsonObject, final XMLParserConfiguration config) {
458+
if (jsonObject.has(config.getcDataTagName())) {
459+
final Object s = jsonObject.get(config.getcDataTagName());
460+
if (s instanceof String) {
461+
if (isStringAllWhiteSpace(s.toString())) {
462+
jsonObject.remove(config.getcDataTagName());
463+
}
464+
}
465+
else if (s instanceof JSONArray) {
466+
final JSONArray sArray = (JSONArray) s;
467+
for (int k = sArray.length()-1; k >= 0; k--){
468+
final Object eachString = sArray.get(k);
469+
if (eachString instanceof String) {
470+
String s1 = (String) eachString;
471+
if (isStringAllWhiteSpace(s1)) {
472+
sArray.remove(k);
473+
}
474+
}
475+
}
476+
if (sArray.isEmpty()) {
477+
jsonObject.remove(config.getcDataTagName());
478+
}
479+
}
480+
}
481+
}
482+
483+
private static boolean isStringAllWhiteSpace(final String s) {
484+
for (int k = 0; k<s.length(); k++){
485+
final char eachChar = s.charAt(k);
486+
if (!Character.isWhitespace(eachChar)) {
487+
return false;
488+
}
489+
}
490+
return true;
491+
}
492+
448493

449494
/**
450495
* This method tries to convert the given string value to the target object
@@ -594,7 +639,7 @@ public static JSONObject toJSONObject(Reader reader, boolean keepStrings) throws
594639
*/
595640
public static JSONObject toJSONObject(Reader reader, XMLParserConfiguration config) throws JSONException {
596641
JSONObject jo = new JSONObject();
597-
XMLTokener x = new XMLTokener(reader);
642+
XMLTokener x = new XMLTokener(reader, config);
598643
while (x.more()) {
599644
x.skipPast("<");
600645
if(x.more()) {

src/main/java/org/json/XMLParserConfiguration.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,19 @@ public class XMLParserConfiguration extends ParserConfiguration {
6161
*/
6262
private Set<String> forceList;
6363

64+
private boolean shouldTrimWhiteSpace;
65+
6466
/**
6567
* Default parser configuration. Does not keep strings (tries to implicitly convert
66-
* values), and the CDATA Tag Name is "content".
68+
* values), and the CDATA Tag Name is "content". Trims whitespace.
6769
*/
6870
public XMLParserConfiguration () {
6971
super();
7072
this.cDataTagName = "content";
7173
this.convertNilAttributeToNull = false;
7274
this.xsiTypeMap = Collections.emptyMap();
7375
this.forceList = Collections.emptySet();
76+
this.shouldTrimWhiteSpace = true;
7477
}
7578

7679
/**
@@ -153,13 +156,14 @@ public XMLParserConfiguration (final boolean keepStrings, final String cDataTagN
153156
*/
154157
private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName,
155158
final boolean convertNilAttributeToNull, final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap, final Set<String> forceList,
156-
final int maxNestingDepth, final boolean closeEmptyTag) {
159+
final int maxNestingDepth, final boolean closeEmptyTag, final boolean shouldTrimWhiteSpace) {
157160
super(keepStrings, maxNestingDepth);
158161
this.cDataTagName = cDataTagName;
159162
this.convertNilAttributeToNull = convertNilAttributeToNull;
160163
this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap);
161164
this.forceList = Collections.unmodifiableSet(forceList);
162165
this.closeEmptyTag = closeEmptyTag;
166+
this.shouldTrimWhiteSpace = shouldTrimWhiteSpace;
163167
}
164168

165169
/**
@@ -179,7 +183,8 @@ protected XMLParserConfiguration clone() {
179183
this.xsiTypeMap,
180184
this.forceList,
181185
this.maxNestingDepth,
182-
this.closeEmptyTag
186+
this.closeEmptyTag,
187+
this.shouldTrimWhiteSpace
183188
);
184189
}
185190

@@ -325,7 +330,23 @@ public XMLParserConfiguration withCloseEmptyTag(boolean closeEmptyTag){
325330
return clonedConfiguration;
326331
}
327332

333+
/**
334+
* Sets whether whitespace should be trimmed inside of tags. *NOTE* Do not use this if
335+
* you expect your XML tags to have names that are the same as cDataTagName as this is unsupported.
336+
* cDataTagName should be set to a distinct value in these cases.
337+
* @param shouldTrimWhiteSpace boolean to set trimming on or off. Off is default.
338+
* @return same instance of configuration with empty tag config updated
339+
*/
340+
public XMLParserConfiguration withShouldTrimWhitespace(boolean shouldTrimWhiteSpace){
341+
XMLParserConfiguration clonedConfiguration = this.clone();
342+
clonedConfiguration.shouldTrimWhiteSpace = shouldTrimWhiteSpace;
343+
return clonedConfiguration;
344+
}
345+
328346
public boolean isCloseEmptyTag() {
329347
return this.closeEmptyTag;
330348
}
349+
public boolean shouldTrimWhiteSpace() {
350+
return this.shouldTrimWhiteSpace;
351+
}
331352
}

src/main/java/org/json/XMLTokener.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ public class XMLTokener extends JSONTokener {
2020
*/
2121
public static final java.util.HashMap<String, Character> entity;
2222

23+
private static XMLParserConfiguration configuration = XMLParserConfiguration.ORIGINAL;;
24+
2325
static {
2426
entity = new java.util.HashMap<String, Character>(8);
2527
entity.put("amp", XML.AMP);
@@ -45,6 +47,15 @@ public XMLTokener(String s) {
4547
super(s);
4648
}
4749

50+
public XMLTokener(Reader r, XMLParserConfiguration configuration) {
51+
super(r);
52+
XMLTokener.configuration = configuration;
53+
}
54+
public XMLTokener(String s, XMLParserConfiguration configuration) {
55+
super(s);
56+
XMLTokener.configuration = configuration;
57+
}
58+
4859
/**
4960
* Get the text in the CDATA block.
5061
* @return The string up to the <code>]]&gt;</code>.
@@ -83,7 +94,7 @@ public Object nextContent() throws JSONException {
8394
StringBuilder sb;
8495
do {
8596
c = next();
86-
} while (Character.isWhitespace(c));
97+
} while (Character.isWhitespace(c) && configuration.shouldTrimWhiteSpace());
8798
if (c == 0) {
8899
return null;
89100
}
@@ -97,7 +108,9 @@ public Object nextContent() throws JSONException {
97108
}
98109
if (c == '<') {
99110
back();
100-
return sb.toString().trim();
111+
if (configuration.shouldTrimWhiteSpace()) {
112+
return sb.toString().trim();
113+
} else return sb.toString();
101114
}
102115
if (c == '&') {
103116
sb.append(nextEntity(c));

src/test/java/org/json/junit/XMLConfigurationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1181,4 +1181,4 @@ private void compareFileToJSONObject(String xmlStr, String expectedStr) {
11811181
assertTrue("Error: " +e.getMessage(), false);
11821182
}
11831183
}
1184-
}
1184+
}

src/test/java/org/json/junit/XMLTest.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,80 @@ public void testMaxNestingDepthWithValidFittingXML() {
13191319
"parameter of the XMLParserConfiguration used");
13201320
}
13211321
}
1322+
@Test
1323+
public void testWithWhitespaceTrimmingDisabled() {
1324+
String originalXml = "<testXml> Test Whitespace String \t </testXml>";
1325+
1326+
JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(false));
1327+
String expectedJsonString = "{\"testXml\":\" Test Whitespace String \t \"}";
1328+
JSONObject expectedJson = new JSONObject(expectedJsonString);
1329+
Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
1330+
}
1331+
@Test
1332+
public void testNestedWithWhitespaceTrimmingDisabled() {
1333+
String originalXml =
1334+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
1335+
"<addresses>\n"+
1336+
" <address>\n"+
1337+
" <name> Sherlock Holmes </name>\n"+
1338+
" </address>\n"+
1339+
"</addresses>";
1340+
1341+
JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(false));
1342+
String expectedJsonString = "{\"addresses\":{\"address\":{\"name\":\" Sherlock Holmes \"}}}";
1343+
JSONObject expectedJson = new JSONObject(expectedJsonString);
1344+
Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
1345+
}
1346+
@Test
1347+
public void shouldTrimWhitespaceDoesNotSupportTagsEqualingCDataTagName() {
1348+
// When using withShouldTrimWhitespace = true, input containing tags with same name as cDataTagName is unsupported and should not be used in conjunction
1349+
String originalXml =
1350+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
1351+
"<addresses>\n"+
1352+
" <address>\n"+
1353+
" <content> Sherlock Holmes </content>\n"+
1354+
" </address>\n"+
1355+
"</addresses>";
1356+
1357+
JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(false).withcDataTagName("content"));
1358+
String expectedJsonString = "{\"addresses\":{\"address\":[[\"\\n \",\" Sherlock Holmes \",\"\\n \"]]}}";
1359+
JSONObject expectedJson = new JSONObject(expectedJsonString);
1360+
Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
1361+
}
1362+
@Test
1363+
public void shouldTrimWhitespaceEnabledDropsTagsEqualingCDataTagNameButValueRemains() {
1364+
String originalXml =
1365+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
1366+
"<addresses>\n"+
1367+
" <address>\n"+
1368+
" <content> Sherlock Holmes </content>\n"+
1369+
" </address>\n"+
1370+
"</addresses>";
1371+
1372+
JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(true).withcDataTagName("content"));
1373+
String expectedJsonString = "{\"addresses\":{\"address\":\"Sherlock Holmes\"}}";
1374+
JSONObject expectedJson = new JSONObject(expectedJsonString);
1375+
Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
1376+
}
1377+
@Test
1378+
public void testWithWhitespaceTrimmingEnabled() {
1379+
String originalXml = "<testXml> Test Whitespace String \t </testXml>";
1380+
1381+
JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(true));
1382+
String expectedJsonString = "{\"testXml\":\"Test Whitespace String\"}";
1383+
JSONObject expectedJson = new JSONObject(expectedJsonString);
1384+
Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
1385+
}
1386+
@Test
1387+
public void testWithWhitespaceTrimmingEnabledByDefault() {
1388+
String originalXml = "<testXml> Test Whitespace String \t </testXml>";
1389+
1390+
JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration());
1391+
String expectedJsonString = "{\"testXml\":\"Test Whitespace String\"}";
1392+
JSONObject expectedJson = new JSONObject(expectedJsonString);
1393+
Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
1394+
}
1395+
13221396
}
13231397

13241398

0 commit comments

Comments
 (0)