Skip to content

Commit d5b2785

Browse files
author
meiskalt7
committed
add configuration for xsi:nil="true" conversion to null
1 parent 12bbe8c commit d5b2785

File tree

2 files changed

+72
-34
lines changed

2 files changed

+72
-34
lines changed

XML.java

+45-28
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ of this software and associated documentation files (the "Software"), to deal
3131
/**
3232
* This provides static methods to convert an XML text into a JSONObject, and to
3333
* covert a JSONObject into an XML text.
34-
*
34+
*
3535
* @author JSON.org
3636
* @version 2016-08-10
3737
*/
@@ -64,15 +64,20 @@ public class XML {
6464

6565
/** The Character '/'. */
6666
public static final Character SLASH = '/';
67-
67+
68+
/**
69+
* Null attrubute name
70+
*/
71+
public static final String NULL_ATTR = "xsi:nil";
72+
6873
/**
6974
* Creates an iterator for navigating Code Points in a string instead of
7075
* characters. Once Java7 support is dropped, this can be replaced with
7176
* <code>
7277
* string.codePoints()
7378
* </code>
7479
* which is available in Java8 and above.
75-
*
80+
*
7681
* @see <a href=
7782
* "http://stackoverflow.com/a/21791059/6030888">http://stackoverflow.com/a/21791059/6030888</a>
7883
*/
@@ -107,15 +112,15 @@ public void remove() {
107112

108113
/**
109114
* Replace special characters with XML escapes:
110-
*
115+
*
111116
* <pre>
112117
* &amp; <small>(ampersand)</small> is replaced by &amp;amp;
113118
* &lt; <small>(less than)</small> is replaced by &amp;lt;
114119
* &gt; <small>(greater than)</small> is replaced by &amp;gt;
115120
* &quot; <small>(double quote)</small> is replaced by &amp;quot;
116121
* &apos; <small>(single quote / apostrophe)</small> is replaced by &amp;apos;
117122
* </pre>
118-
*
123+
*
119124
* @param string
120125
* The string to be escaped.
121126
* @return The escaped string.
@@ -151,17 +156,17 @@ public static String escape(String string) {
151156
}
152157
return sb.toString();
153158
}
154-
159+
155160
/**
156161
* @param cp code point to test
157162
* @return true if the code point is not valid for an XML
158163
*/
159164
private static boolean mustEscape(int cp) {
160165
/* Valid range from https://www.w3.org/TR/REC-xml/#charsets
161-
*
162-
* #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
163-
*
164-
* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.
166+
*
167+
* #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
168+
*
169+
* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.
165170
*/
166171
// isISOControl is true when (cp >= 0 && cp <= 0x1F) || (cp >= 0x7F && cp <= 0x9F)
167172
// all ISO control characters are out of range except tabs and new lines
@@ -180,7 +185,7 @@ private static boolean mustEscape(int cp) {
180185

181186
/**
182187
* Removes XML escapes from the string.
183-
*
188+
*
184189
* @param string
185190
* string to remove escapes from
186191
* @return string with converted entities
@@ -212,7 +217,7 @@ public static String unescape(String string) {
212217
/**
213218
* Throw an exception if the string contains whitespace. Whitespace is not
214219
* allowed in tagNames and attributes.
215-
*
220+
*
216221
* @param string
217222
* A string.
218223
* @throws JSONException Thrown if the string contains whitespace or is empty.
@@ -232,7 +237,7 @@ public static void noSpace(String string) throws JSONException {
232237

233238
/**
234239
* Scan the content following the named tag, attaching it to the context.
235-
*
240+
*
236241
* @param x
237242
* The XMLTokener containing the source string.
238243
* @param context
@@ -328,6 +333,7 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
328333
tagName = (String) token;
329334
token = null;
330335
jsonobject = new JSONObject();
336+
boolean nilAttributeFound = false;
331337
for (;;) {
332338
if (token == null) {
333339
token = x.nextToken();
@@ -341,8 +347,17 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
341347
if (!(token instanceof String)) {
342348
throw x.syntaxError("Missing value");
343349
}
344-
jsonobject.accumulate(string,
345-
config.keepStrings ? ((String)token) : stringToValue((String) token));
350+
351+
if (config.convertNilAttributeToNull
352+
&& NULL_ATTR.equals(string)
353+
&& Boolean.parseBoolean((String) token)) {
354+
nilAttributeFound = true;
355+
} else if (!nilAttributeFound) {
356+
jsonobject.accumulate(string,
357+
config.keepStrings
358+
? ((String) token)
359+
: stringToValue((String) token));
360+
}
346361
token = null;
347362
} else {
348363
jsonobject.accumulate(string, "");
@@ -354,7 +369,9 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
354369
if (x.nextToken() != GT) {
355370
throw x.syntaxError("Misshaped tag");
356371
}
357-
if (jsonobject.length() > 0) {
372+
if (nilAttributeFound) {
373+
context.accumulate(tagName, JSONObject.NULL);
374+
} else if (jsonobject.length() > 0) {
358375
context.accumulate(tagName, jsonobject);
359376
} else {
360377
context.accumulate(tagName, "");
@@ -399,10 +416,10 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
399416
}
400417
}
401418
}
402-
419+
403420
/**
404421
* This method is the same as {@link JSONObject#stringToValue(String)}.
405-
*
422+
*
406423
* @param string String to convert
407424
* @return JSON value of this string or the string
408425
*/
@@ -463,7 +480,7 @@ public static Object stringToValue(String string) {
463480
* elements are represented as JSONArrays. Content text may be placed in a
464481
* "content" member. Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code>
465482
* are ignored.
466-
*
483+
*
467484
* @param string
468485
* The source string.
469486
* @return A JSONObject containing the structured data from the XML string.
@@ -518,7 +535,7 @@ public static JSONObject toJSONObject(Reader reader, boolean keepStrings) throws
518535
}
519536
return toJSONObject(reader, XMLParserConfiguration.ORIGINAL);
520537
}
521-
538+
522539
/**
523540
* Convert a well-formed (but not necessarily valid) XML into a
524541
* JSONObject. Some information may be lost in this transformation because
@@ -560,10 +577,10 @@ public static JSONObject toJSONObject(Reader reader, XMLParserConfiguration conf
560577
* elements are represented as JSONArrays. Content text may be placed in a
561578
* "content" member. Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code>
562579
* are ignored.
563-
*
580+
*
564581
* All values are converted as strings, for 1, 01, 29.0 will not be coerced to
565582
* numbers but will instead be the exact value as seen in the XML document.
566-
*
583+
*
567584
* @param string
568585
* The source string.
569586
* @param keepStrings If true, then values will not be coerced into boolean
@@ -585,10 +602,10 @@ public static JSONObject toJSONObject(String string, boolean keepStrings) throws
585602
* elements are represented as JSONArrays. Content text may be placed in a
586603
* "content" member. Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code>
587604
* are ignored.
588-
*
605+
*
589606
* All values are converted as strings, for 1, 01, 29.0 will not be coerced to
590607
* numbers but will instead be the exact value as seen in the XML document.
591-
*
608+
*
592609
* @param string
593610
* The source string.
594611
* @param config Configuration options for the parser.
@@ -601,7 +618,7 @@ public static JSONObject toJSONObject(String string, XMLParserConfiguration conf
601618

602619
/**
603620
* Convert a JSONObject into a well-formed, element-normal XML string.
604-
*
621+
*
605622
* @param object
606623
* A JSONObject.
607624
* @return A string.
@@ -610,10 +627,10 @@ public static JSONObject toJSONObject(String string, XMLParserConfiguration conf
610627
public static String toString(Object object) throws JSONException {
611628
return toString(object, null, XMLParserConfiguration.ORIGINAL);
612629
}
613-
630+
614631
/**
615632
* Convert a JSONObject into a well-formed, element-normal XML string.
616-
*
633+
*
617634
* @param object
618635
* A JSONObject.
619636
* @param tagName
@@ -627,7 +644,7 @@ public static String toString(final Object object, final String tagName) {
627644

628645
/**
629646
* Convert a JSONObject into a well-formed, element-normal XML string.
630-
*
647+
*
631648
* @param object
632649
* A JSONObject.
633650
* @param tagName

XMLParserConfiguration.java

+27-6
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class XMLParserConfiguration {
3232
/** Original Configuration of the XML Parser. */
3333
public static final XMLParserConfiguration ORIGINAL = new XMLParserConfiguration();
3434
/** Original configuration of the XML Parser except that values are kept as strings. */
35-
public static final XMLParserConfiguration KEEP_STRINGS = new XMLParserConfiguration(true);
35+
public static final XMLParserConfiguration KEEP_STRINGS = new XMLParserConfiguration(true);
3636
/**
3737
* When parsing the XML into JSON, specifies if values should be kept as strings (true), or if
3838
* they should try to be guessed into JSON values (numeric, boolean, string)
@@ -44,12 +44,17 @@ public class XMLParserConfiguration {
4444
* processing.
4545
*/
4646
public final String cDataTagName;
47+
/**
48+
* When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
49+
* should be kept as attribute(false), or they should be converted to null(true)
50+
*/
51+
public final boolean convertNilAttributeToNull;
4752

4853
/**
4954
* Default parser configuration. Does not keep strings, and the CDATA Tag Name is "content".
5055
*/
5156
public XMLParserConfiguration () {
52-
this(false, "content");
57+
this(false, "content", false);
5358
}
5459

5560
/**
@@ -58,7 +63,7 @@ public XMLParserConfiguration () {
5863
* <code>false</code> to try and convert XML string values into a JSON value.
5964
*/
6065
public XMLParserConfiguration (final boolean keepStrings) {
61-
this(keepStrings, "content");
66+
this(keepStrings, "content", false);
6267
}
6368

6469
/**
@@ -69,7 +74,7 @@ public XMLParserConfiguration (final boolean keepStrings) {
6974
* to use that value as the JSONObject key name to process as CDATA.
7075
*/
7176
public XMLParserConfiguration (final String cDataTagName) {
72-
this(false, cDataTagName);
77+
this(false, cDataTagName, false);
7378
}
7479

7580
/**
@@ -80,7 +85,23 @@ public XMLParserConfiguration (final String cDataTagName) {
8085
* to use that value as the JSONObject key name to process as CDATA.
8186
*/
8287
public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName) {
83-
this.keepStrings = keepStrings;
84-
this.cDataTagName = cDataTagName;
88+
this.keepStrings = keepStrings;
89+
this.cDataTagName = cDataTagName;
90+
this.convertNilAttributeToNull = false;
91+
}
92+
93+
/**
94+
* Configure the parser to use custom settings.
95+
* @param keepStrings <code>true</code> to parse all values as string.
96+
* <code>false</code> to try and convert XML string values into a JSON value.
97+
* @param cDataTagName <code>null</code> to disable CDATA processing. Any other value
98+
* to use that value as the JSONObject key name to process as CDATA.
99+
* @param convertNilAttributeToNull <code>true</code> to parse values with attribute xsi:nil="true" as null.
100+
* <code>false</code> to parse values with attribute xsi:nil="true" as {"xsi:nil":true}.
101+
*/
102+
public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, final boolean convertNilAttributeToNull) {
103+
this.keepStrings = keepStrings;
104+
this.cDataTagName = cDataTagName;
105+
this.convertNilAttributeToNull = convertNilAttributeToNull;
85106
}
86107
}

0 commit comments

Comments
 (0)