Skip to content

Commit 09dddb8

Browse files
authored
Merge pull request stleary#412 from johnjaylward/XmlConfig
Initial implementation of XMLParserConfig object for flexible XML Parsing
2 parents fea0aca + ca9df04 commit 09dddb8

File tree

2 files changed

+171
-18
lines changed

2 files changed

+171
-18
lines changed

XML.java

Lines changed: 85 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ of this software and associated documentation files (the "Software"), to deal
3737
*/
3838
@SuppressWarnings("boxing")
3939
public class XML {
40+
4041
/** The Character '&'. */
4142
public static final Character AMP = '&';
4243

@@ -241,7 +242,7 @@ public static void noSpace(String string) throws JSONException {
241242
* @return true if the close tag is processed.
242243
* @throws JSONException
243244
*/
244-
private static boolean parse(XMLTokener x, JSONObject context, String name, boolean keepStrings)
245+
private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config)
245246
throws JSONException {
246247
char c;
247248
int i;
@@ -278,7 +279,7 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, bool
278279
if (x.next() == '[') {
279280
string = x.nextCDATA();
280281
if (string.length() > 0) {
281-
context.accumulate("content", string);
282+
context.accumulate(config.cDataTagName, string);
282283
}
283284
return false;
284285
}
@@ -341,7 +342,7 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, bool
341342
throw x.syntaxError("Missing value");
342343
}
343344
jsonobject.accumulate(string,
344-
keepStrings ? ((String)token) : stringToValue((String) token));
345+
config.keepStrings ? ((String)token) : stringToValue((String) token));
345346
token = null;
346347
} else {
347348
jsonobject.accumulate(string, "");
@@ -372,19 +373,19 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, bool
372373
} else if (token instanceof String) {
373374
string = (String) token;
374375
if (string.length() > 0) {
375-
jsonobject.accumulate("content",
376-
keepStrings ? string : stringToValue(string));
376+
jsonobject.accumulate(config.cDataTagName,
377+
config.keepStrings ? string : stringToValue(string));
377378
}
378379

379380
} else if (token == LT) {
380381
// Nested element
381-
if (parse(x, jsonobject, tagName,keepStrings)) {
382+
if (parse(x, jsonobject, tagName, config)) {
382383
if (jsonobject.length() == 0) {
383384
context.accumulate(tagName, "");
384385
} else if (jsonobject.length() == 1
385-
&& jsonobject.opt("content") != null) {
386+
&& jsonobject.opt(config.cDataTagName) != null) {
386387
context.accumulate(tagName,
387-
jsonobject.opt("content"));
388+
jsonobject.opt(config.cDataTagName));
388389
} else {
389390
context.accumulate(tagName, jsonobject);
390391
}
@@ -469,7 +470,7 @@ public static Object stringToValue(String string) {
469470
* @throws JSONException Thrown if there is an errors while parsing the string
470471
*/
471472
public static JSONObject toJSONObject(String string) throws JSONException {
472-
return toJSONObject(string, false);
473+
return toJSONObject(string, XMLParserConfiguration.ORIGINAL);
473474
}
474475

475476
/**
@@ -488,7 +489,7 @@ public static JSONObject toJSONObject(String string) throws JSONException {
488489
* @throws JSONException Thrown if there is an errors while parsing the string
489490
*/
490491
public static JSONObject toJSONObject(Reader reader) throws JSONException {
491-
return toJSONObject(reader, false);
492+
return toJSONObject(reader, XMLParserConfiguration.ORIGINAL);
492493
}
493494

494495
/**
@@ -512,12 +513,38 @@ public static JSONObject toJSONObject(Reader reader) throws JSONException {
512513
* @throws JSONException Thrown if there is an errors while parsing the string
513514
*/
514515
public static JSONObject toJSONObject(Reader reader, boolean keepStrings) throws JSONException {
516+
if(keepStrings) {
517+
return toJSONObject(reader, XMLParserConfiguration.KEEP_STRINGS);
518+
}
519+
return toJSONObject(reader, XMLParserConfiguration.ORIGINAL);
520+
}
521+
522+
/**
523+
* Convert a well-formed (but not necessarily valid) XML into a
524+
* JSONObject. Some information may be lost in this transformation because
525+
* JSON is a data format and XML is a document format. XML uses elements,
526+
* attributes, and content text, while JSON uses unordered collections of
527+
* name/value pairs and arrays of values. JSON does not does not like to
528+
* distinguish between elements and attributes. Sequences of similar
529+
* elements are represented as JSONArrays. Content text may be placed in a
530+
* "content" member. Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code>
531+
* are ignored.
532+
*
533+
* All values are converted as strings, for 1, 01, 29.0 will not be coerced to
534+
* numbers but will instead be the exact value as seen in the XML document.
535+
*
536+
* @param reader The XML source reader.
537+
* @param config Configuration options for the parser
538+
* @return A JSONObject containing the structured data from the XML string.
539+
* @throws JSONException Thrown if there is an errors while parsing the string
540+
*/
541+
public static JSONObject toJSONObject(Reader reader, XMLParserConfiguration config) throws JSONException {
515542
JSONObject jo = new JSONObject();
516543
XMLTokener x = new XMLTokener(reader);
517544
while (x.more()) {
518545
x.skipPast("<");
519546
if(x.more()) {
520-
parse(x, jo, null, keepStrings);
547+
parse(x, jo, null, config);
521548
}
522549
}
523550
return jo;
@@ -548,6 +575,30 @@ public static JSONObject toJSONObject(String string, boolean keepStrings) throws
548575
return toJSONObject(new StringReader(string), keepStrings);
549576
}
550577

578+
/**
579+
* Convert a well-formed (but not necessarily valid) XML string into a
580+
* JSONObject. Some information may be lost in this transformation because
581+
* JSON is a data format and XML is a document format. XML uses elements,
582+
* attributes, and content text, while JSON uses unordered collections of
583+
* name/value pairs and arrays of values. JSON does not does not like to
584+
* distinguish between elements and attributes. Sequences of similar
585+
* elements are represented as JSONArrays. Content text may be placed in a
586+
* "content" member. Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code>
587+
* are ignored.
588+
*
589+
* All values are converted as strings, for 1, 01, 29.0 will not be coerced to
590+
* numbers but will instead be the exact value as seen in the XML document.
591+
*
592+
* @param string
593+
* The source string.
594+
* @param config Configuration options for the parser.
595+
* @return A JSONObject containing the structured data from the XML string.
596+
* @throws JSONException Thrown if there is an errors while parsing the string
597+
*/
598+
public static JSONObject toJSONObject(String string, XMLParserConfiguration config) throws JSONException {
599+
return toJSONObject(new StringReader(string), config);
600+
}
601+
551602
/**
552603
* Convert a JSONObject into a well-formed, element-normal XML string.
553604
*
@@ -557,7 +608,21 @@ public static JSONObject toJSONObject(String string, boolean keepStrings) throws
557608
* @throws JSONException Thrown if there is an error parsing the string
558609
*/
559610
public static String toString(Object object) throws JSONException {
560-
return toString(object, null);
611+
return toString(object, null, XMLParserConfiguration.ORIGINAL);
612+
}
613+
614+
/**
615+
* Convert a JSONObject into a well-formed, element-normal XML string.
616+
*
617+
* @param object
618+
* A JSONObject.
619+
* @param tagName
620+
* The optional name of the enclosing tag.
621+
* @return A string.
622+
* @throws JSONException Thrown if there is an error parsing the string
623+
*/
624+
public static String toString(final Object object, final String tagName) {
625+
return toString(object, tagName, XMLParserConfiguration.ORIGINAL);
561626
}
562627

563628
/**
@@ -567,10 +632,12 @@ public static String toString(Object object) throws JSONException {
567632
* A JSONObject.
568633
* @param tagName
569634
* The optional name of the enclosing tag.
635+
* @param config
636+
* Configuration that can control output to XML.
570637
* @return A string.
571638
* @throws JSONException Thrown if there is an error parsing the string
572639
*/
573-
public static String toString(final Object object, final String tagName)
640+
public static String toString(final Object object, final String tagName, final XMLParserConfiguration config)
574641
throws JSONException {
575642
StringBuilder sb = new StringBuilder();
576643
JSONArray ja;
@@ -598,7 +665,7 @@ public static String toString(final Object object, final String tagName)
598665
}
599666

600667
// Emit content in body
601-
if ("content".equals(key)) {
668+
if (key.equals(config.cDataTagName)) {
602669
if (value instanceof JSONArray) {
603670
ja = (JSONArray) value;
604671
int jaLength = ja.length();
@@ -626,12 +693,12 @@ public static String toString(final Object object, final String tagName)
626693
sb.append('<');
627694
sb.append(key);
628695
sb.append('>');
629-
sb.append(toString(val));
696+
sb.append(toString(val, null, config));
630697
sb.append("</");
631698
sb.append(key);
632699
sb.append('>');
633700
} else {
634-
sb.append(toString(val, key));
701+
sb.append(toString(val, key, config));
635702
}
636703
}
637704
} else if ("".equals(value)) {
@@ -642,7 +709,7 @@ public static String toString(final Object object, final String tagName)
642709
// Emit a new tag <k>
643710

644711
} else {
645-
sb.append(toString(value, key));
712+
sb.append(toString(value, key, config));
646713
}
647714
}
648715
if (tagName != null) {
@@ -669,7 +736,7 @@ public static String toString(final Object object, final String tagName)
669736
// XML does not have good support for arrays. If an array
670737
// appears in a place where XML is lacking, synthesize an
671738
// <array> element.
672-
sb.append(toString(val, tagName == null ? "array" : tagName));
739+
sb.append(toString(val, tagName == null ? "array" : tagName, config));
673740
}
674741
return sb.toString();
675742
}

XMLParserConfiguration.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package org.json;
2+
/*
3+
Copyright (c) 2002 JSON.org
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
The Software shall be used for Good, not Evil.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
SOFTWARE.
24+
*/
25+
26+
/**
27+
* Configuration object for the XML parser.
28+
* @author AylwardJ
29+
*
30+
*/
31+
public class XMLParserConfiguration {
32+
/** Original Configuration of the XML Parser. */
33+
public static final XMLParserConfiguration ORIGINAL = new XMLParserConfiguration();
34+
/** Original configuration of the XML Parser except that values are kept as strings. */
35+
public static final XMLParserConfiguration KEEP_STRINGS = new XMLParserConfiguration(true);
36+
/**
37+
* When parsing the XML into JSON, specifies if values should be kept as strings (true), or if
38+
* they should try to be guessed into JSON values (numeric, boolean, string)
39+
*/
40+
public final boolean keepStrings;
41+
/**
42+
* The name of the key in a JSON Object that indicates a CDATA section. Historically this has
43+
* been the value "content" but can be changed. Use <code>null</code> to indicate no CDATA
44+
* processing.
45+
*/
46+
public final String cDataTagName;
47+
48+
/**
49+
* Default parser configuration. Does not keep strings, and the CDATA Tag Name is "content".
50+
*/
51+
public XMLParserConfiguration () {
52+
this(false, "content");
53+
}
54+
55+
/**
56+
* Configure the parser string processing and use the default CDATA Tag Name as "content".
57+
* @param keepStrings <code>true</code> to parse all values as string.
58+
* <code>false</code> to try and convert XML string values into a JSON value.
59+
*/
60+
public XMLParserConfiguration (final boolean keepStrings) {
61+
this(keepStrings, "content");
62+
}
63+
64+
/**
65+
* Configure the parser string processing to try and convert XML values to JSON values and
66+
* use the passed CDATA Tag Name the processing value. Pass <code>null</code> to
67+
* disable CDATA processing
68+
* @param cDataTagName<code>null</code> to disable CDATA processing. Any other value
69+
* to use that value as the JSONObject key name to process as CDATA.
70+
*/
71+
public XMLParserConfiguration (final String cDataTagName) {
72+
this(false, cDataTagName);
73+
}
74+
75+
/**
76+
* Configure the parser to use custom settings.
77+
* @param keepStrings <code>true</code> to parse all values as string.
78+
* <code>false</code> to try and convert XML string values into a JSON value.
79+
* @param cDataTagName<code>null</code> to disable CDATA processing. Any other value
80+
* to use that value as the JSONObject key name to process as CDATA.
81+
*/
82+
public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName) {
83+
this.keepStrings = keepStrings;
84+
this.cDataTagName = cDataTagName;
85+
}
86+
}

0 commit comments

Comments
 (0)