Skip to content

Commit 3080b8b

Browse files
authored
Merge pull request #253 from johnjaylward/OptionalTypeConversion
Optional type conversion for XML reading
2 parents 2c228ec + 09d37e5 commit 3080b8b

File tree

3 files changed

+158
-34
lines changed

3 files changed

+158
-34
lines changed

JSONML.java

Lines changed: 104 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,21 @@ of this software and associated documentation files (the "Software"), to deal
3636
* @version 2016-01-30
3737
*/
3838
public class JSONML {
39-
4039
/**
4140
* Parse XML values and store them in a JSONArray.
4241
* @param x The XMLTokener containing the source string.
4342
* @param arrayForm true if array form, false if object form.
4443
* @param ja The JSONArray that is containing the current tag or null
4544
* if we are at the outermost level.
45+
* @param keepStrings Don't type-convert text nodes and attibute values
4646
* @return A JSONArray if the value is the outermost tag, otherwise null.
4747
* @throws JSONException
4848
*/
4949
private static Object parse(
5050
XMLTokener x,
5151
boolean arrayForm,
52-
JSONArray ja
52+
JSONArray ja,
53+
boolean keepStrings
5354
) throws JSONException {
5455
String attribute;
5556
char c;
@@ -174,7 +175,7 @@ private static Object parse(
174175
if (!(token instanceof String)) {
175176
throw x.syntaxError("Missing value");
176177
}
177-
newjo.accumulate(attribute, JSONObject.stringToValue((String)token));
178+
newjo.accumulate(attribute, keepStrings ? token :JSONObject.stringToValue((String)token));
178179
token = null;
179180
} else {
180181
newjo.accumulate(attribute, "");
@@ -193,9 +194,8 @@ private static Object parse(
193194
if (ja == null) {
194195
if (arrayForm) {
195196
return newja;
196-
} else {
197-
return newjo;
198197
}
198+
return newjo;
199199
}
200200

201201
// Content, between <...> and </...>
@@ -204,7 +204,7 @@ private static Object parse(
204204
if (token != XML.GT) {
205205
throw x.syntaxError("Misshaped tag");
206206
}
207-
closeTag = (String)parse(x, arrayForm, newja);
207+
closeTag = (String)parse(x, arrayForm, newja, keepStrings);
208208
if (closeTag != null) {
209209
if (!closeTag.equals(tagName)) {
210210
throw x.syntaxError("Mismatched '" + tagName +
@@ -217,17 +217,16 @@ private static Object parse(
217217
if (ja == null) {
218218
if (arrayForm) {
219219
return newja;
220-
} else {
221-
return newjo;
222220
}
221+
return newjo;
223222
}
224223
}
225224
}
226225
}
227226
} else {
228227
if (ja != null) {
229228
ja.put(token instanceof String
230-
? JSONObject.stringToValue((String)token)
229+
? keepStrings ? token :JSONObject.stringToValue((String)token)
231230
: token);
232231
}
233232
}
@@ -245,10 +244,32 @@ private static Object parse(
245244
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
246245
* @param string The source string.
247246
* @return A JSONArray containing the structured data from the XML string.
248-
* @throws JSONException
247+
* @throws JSONException Thrown on error converting to a JSONArray
249248
*/
250249
public static JSONArray toJSONArray(String string) throws JSONException {
251-
return toJSONArray(new XMLTokener(string));
250+
return (JSONArray)parse(new XMLTokener(string), true, null, false);
251+
}
252+
253+
254+
/**
255+
* Convert a well-formed (but not necessarily valid) XML string into a
256+
* JSONArray using the JsonML transform. Each XML tag is represented as
257+
* a JSONArray in which the first element is the tag name. If the tag has
258+
* attributes, then the second element will be JSONObject containing the
259+
* name/value pairs. If the tag contains children, then strings and
260+
* JSONArrays will represent the child tags.
261+
* As opposed to toJSONArray this method does not attempt to convert
262+
* any text node or attribute value to any type
263+
* but just leaves it as a string.
264+
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
265+
* @param string The source string.
266+
* @param keepStrings If true, then values will not be coerced into boolean
267+
* or numeric values and will instead be left as strings
268+
* @return A JSONArray containing the structured data from the XML string.
269+
* @throws JSONException Thrown on error converting to a JSONArray
270+
*/
271+
public static JSONArray toJSONArray(String string, boolean keepStrings) throws JSONException {
272+
return (JSONArray)parse(new XMLTokener(string), true, null, keepStrings);
252273
}
253274

254275

@@ -259,16 +280,76 @@ public static JSONArray toJSONArray(String string) throws JSONException {
259280
* attributes, then the second element will be JSONObject containing the
260281
* name/value pairs. If the tag contains children, then strings and
261282
* JSONArrays will represent the child content and tags.
283+
* As opposed to toJSONArray this method does not attempt to convert
284+
* any text node or attribute value to any type
285+
* but just leaves it as a string.
262286
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
263287
* @param x An XMLTokener.
288+
* @param keepStrings If true, then values will not be coerced into boolean
289+
* or numeric values and will instead be left as strings
264290
* @return A JSONArray containing the structured data from the XML string.
265-
* @throws JSONException
291+
* @throws JSONException Thrown on error converting to a JSONArray
292+
*/
293+
public static JSONArray toJSONArray(XMLTokener x, boolean keepStrings) throws JSONException {
294+
return (JSONArray)parse(x, true, null, keepStrings);
295+
}
296+
297+
298+
/**
299+
* Convert a well-formed (but not necessarily valid) XML string into a
300+
* JSONArray using the JsonML transform. Each XML tag is represented as
301+
* a JSONArray in which the first element is the tag name. If the tag has
302+
* attributes, then the second element will be JSONObject containing the
303+
* name/value pairs. If the tag contains children, then strings and
304+
* JSONArrays will represent the child content and tags.
305+
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
306+
* @param x An XMLTokener.
307+
* @return A JSONArray containing the structured data from the XML string.
308+
* @throws JSONException Thrown on error converting to a JSONArray
266309
*/
267310
public static JSONArray toJSONArray(XMLTokener x) throws JSONException {
268-
return (JSONArray)parse(x, true, null);
311+
return (JSONArray)parse(x, true, null, false);
269312
}
270313

271314

315+
/**
316+
* Convert a well-formed (but not necessarily valid) XML string into a
317+
* JSONObject using the JsonML transform. Each XML tag is represented as
318+
* a JSONObject with a "tagName" property. If the tag has attributes, then
319+
* the attributes will be in the JSONObject as properties. If the tag
320+
* contains children, the object will have a "childNodes" property which
321+
* will be an array of strings and JsonML JSONObjects.
322+
323+
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
324+
* @param string The XML source text.
325+
* @return A JSONObject containing the structured data from the XML string.
326+
* @throws JSONException Thrown on error converting to a JSONObject
327+
*/
328+
public static JSONObject toJSONObject(String string) throws JSONException {
329+
return (JSONObject)parse(new XMLTokener(string), false, null, false);
330+
}
331+
332+
333+
/**
334+
* Convert a well-formed (but not necessarily valid) XML string into a
335+
* JSONObject using the JsonML transform. Each XML tag is represented as
336+
* a JSONObject with a "tagName" property. If the tag has attributes, then
337+
* the attributes will be in the JSONObject as properties. If the tag
338+
* contains children, the object will have a "childNodes" property which
339+
* will be an array of strings and JsonML JSONObjects.
340+
341+
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
342+
* @param string The XML source text.
343+
* @param keepStrings If true, then values will not be coerced into boolean
344+
* or numeric values and will instead be left as strings
345+
* @return A JSONObject containing the structured data from the XML string.
346+
* @throws JSONException Thrown on error converting to a JSONObject
347+
*/
348+
public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException {
349+
return (JSONObject)parse(new XMLTokener(string), false, null, keepStrings);
350+
}
351+
352+
272353
/**
273354
* Convert a well-formed (but not necessarily valid) XML string into a
274355
* JSONObject using the JsonML transform. Each XML tag is represented as
@@ -280,10 +361,10 @@ public static JSONArray toJSONArray(XMLTokener x) throws JSONException {
280361
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
281362
* @param x An XMLTokener of the XML source text.
282363
* @return A JSONObject containing the structured data from the XML string.
283-
* @throws JSONException
364+
* @throws JSONException Thrown on error converting to a JSONObject
284365
*/
285366
public static JSONObject toJSONObject(XMLTokener x) throws JSONException {
286-
return (JSONObject)parse(x, false, null);
367+
return (JSONObject)parse(x, false, null, false);
287368
}
288369

289370

@@ -296,20 +377,22 @@ public static JSONObject toJSONObject(XMLTokener x) throws JSONException {
296377
* will be an array of strings and JsonML JSONObjects.
297378
298379
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
299-
* @param string The XML source text.
380+
* @param x An XMLTokener of the XML source text.
381+
* @param keepStrings If true, then values will not be coerced into boolean
382+
* or numeric values and will instead be left as strings
300383
* @return A JSONObject containing the structured data from the XML string.
301-
* @throws JSONException
384+
* @throws JSONException Thrown on error converting to a JSONObject
302385
*/
303-
public static JSONObject toJSONObject(String string) throws JSONException {
304-
return toJSONObject(new XMLTokener(string));
386+
public static JSONObject toJSONObject(XMLTokener x, boolean keepStrings) throws JSONException {
387+
return (JSONObject)parse(x, false, null, keepStrings);
305388
}
306389

307390

308391
/**
309392
* Reverse the JSONML transformation, making an XML text from a JSONArray.
310393
* @param ja A JSONArray.
311394
* @return An XML string.
312-
* @throws JSONException
395+
* @throws JSONException Thrown on error converting to a string
313396
*/
314397
public static String toString(JSONArray ja) throws JSONException {
315398
int i;
@@ -393,7 +476,7 @@ public static String toString(JSONArray ja) throws JSONException {
393476
* The other properties are attributes with string values.
394477
* @param jo A JSONObject.
395478
* @return An XML string.
396-
* @throws JSONException
479+
* @throws JSONException Thrown on error converting to a string
397480
*/
398481
public static String toString(JSONObject jo) throws JSONException {
399482
StringBuilder sb = new StringBuilder();

JSONTokener.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public JSONTokener(Reader reader) {
7171
* Construct a JSONTokener from an InputStream.
7272
* @param inputStream The source.
7373
*/
74-
public JSONTokener(InputStream inputStream) throws JSONException {
74+
public JSONTokener(InputStream inputStream) {
7575
this(new InputStreamReader(inputStream));
7676
}
7777

@@ -90,6 +90,8 @@ public JSONTokener(String s) {
9090
* Back up one character. This provides a sort of lookahead capability,
9191
* so that you can test for a digit or letter before attempting to parse
9292
* the next number or identifier.
93+
* @throws JSONException Thrown if trying to step back more than 1 step
94+
* or if already at the start of the string
9395
*/
9496
public void back() throws JSONException {
9597
if (this.usePrevious || this.index <= 0) {
@@ -121,6 +123,9 @@ public static int dehexchar(char c) {
121123
return -1;
122124
}
123125

126+
/**
127+
* @return true if at the end of the file and we didn't step back
128+
*/
124129
public boolean end() {
125130
return this.eof && !this.usePrevious;
126131
}
@@ -130,6 +135,8 @@ public boolean end() {
130135
* Determine if the source string still contains characters that next()
131136
* can consume.
132137
* @return true if not yet at the end of the source.
138+
* @throws JSONException thrown if there is an error stepping forward
139+
* or backward while checking for more data.
133140
*/
134141
public boolean more() throws JSONException {
135142
this.next();
@@ -145,6 +152,7 @@ public boolean more() throws JSONException {
145152
* Get the next character in the source string.
146153
*
147154
* @return The next character, or 0 if past the end of the source string.
155+
* @throws JSONException Thrown if there is an error reading the source string.
148156
*/
149157
public char next() throws JSONException {
150158
int c;
@@ -225,7 +233,7 @@ public String next(int n) throws JSONException {
225233

226234
/**
227235
* Get the next char in the string, skipping whitespace.
228-
* @throws JSONException
236+
* @throws JSONException Thrown if there is an error reading the source string.
229237
* @return A character, or 0 if there are no more characters.
230238
*/
231239
public char nextClean() throws JSONException {
@@ -309,6 +317,8 @@ public String nextString(char quote) throws JSONException {
309317
* end of line, whichever comes first.
310318
* @param delimiter A delimiter character.
311319
* @return A string.
320+
* @throws JSONException Thrown if there is an error while searching
321+
* for the delimiter
312322
*/
313323
public String nextTo(char delimiter) throws JSONException {
314324
StringBuilder sb = new StringBuilder();
@@ -330,6 +340,8 @@ public String nextTo(char delimiter) throws JSONException {
330340
* characters or the end of line, whichever comes first.
331341
* @param delimiters A set of delimiter characters.
332342
* @return A string, trimmed.
343+
* @throws JSONException Thrown if there is an error while searching
344+
* for the delimiter
333345
*/
334346
public String nextTo(String delimiters) throws JSONException {
335347
char c;
@@ -401,6 +413,8 @@ public Object nextValue() throws JSONException {
401413
* @param to A character to skip to.
402414
* @return The requested character, or zero if the requested character
403415
* is not found.
416+
* @throws JSONException Thrown if there is an error while searching
417+
* for the to character
404418
*/
405419
public char skipTo(char to) throws JSONException {
406420
char c;
@@ -453,6 +467,7 @@ public JSONException syntaxError(String message, Throwable causedBy) {
453467
*
454468
* @return " at {index} [character {character} line {line}]"
455469
*/
470+
@Override
456471
public String toString() {
457472
return " at " + this.index + " [character " + this.character + " line " +
458473
this.line + "]";

0 commit comments

Comments
 (0)