/utils/src/org/json/JSONML.java
Java | 465 lines | 298 code | 45 blank | 122 comment | 120 complexity | d7af33832138a63358357e755a6b46b2 MD5 | raw file
1package org.json;
2
3/*
4Copyright (c) 2008 JSON.org
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in all
14copies or substantial portions of the Software.
15
16The Software shall be used for Good, not Evil.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24SOFTWARE.
25*/
26
27import java.util.Iterator;
28
29
30/**
31 * This provides static methods to convert an XML text into a JSONArray or
32 * JSONObject, and to covert a JSONArray or JSONObject into an XML text using
33 * the JsonML transform.
34 * @author JSON.org
35 * @version 2011-11-24
36 */
37public class JSONML {
38
39 /**
40 * Parse XML values and store them in a JSONArray.
41 * @param x The XMLTokener containing the source string.
42 * @param arrayForm true if array form, false if object form.
43 * @param ja The JSONArray that is containing the current tag or null
44 * if we are at the outermost level.
45 * @return A JSONArray if the value is the outermost tag, otherwise null.
46 * @throws JSONException
47 */
48 private static Object parse(
49 XMLTokener x,
50 boolean arrayForm,
51 JSONArray ja
52 ) throws JSONException {
53 String attribute;
54 char c;
55 String closeTag = null;
56 int i;
57 JSONArray newja = null;
58 JSONObject newjo = null;
59 Object token;
60 String tagName = null;
61
62// Test for and skip past these forms:
63// <!-- ... -->
64// <![ ... ]]>
65// <! ... >
66// <? ... ?>
67
68 while (true) {
69 if (!x.more()) {
70 throw x.syntaxError("Bad XML");
71 }
72 token = x.nextContent();
73 if (token == XML.LT) {
74 token = x.nextToken();
75 if (token instanceof Character) {
76 if (token == XML.SLASH) {
77
78// Close tag </
79
80 token = x.nextToken();
81 if (!(token instanceof String)) {
82 throw new JSONException(
83 "Expected a closing name instead of '" +
84 token + "'.");
85 }
86 if (x.nextToken() != XML.GT) {
87 throw x.syntaxError("Misshaped close tag");
88 }
89 return token;
90 } else if (token == XML.BANG) {
91
92// <!
93
94 c = x.next();
95 if (c == '-') {
96 if (x.next() == '-') {
97 x.skipPast("-->");
98 }
99 x.back();
100 } else if (c == '[') {
101 token = x.nextToken();
102 if (token.equals("CDATA") && x.next() == '[') {
103 if (ja != null) {
104 ja.put(x.nextCDATA());
105 }
106 } else {
107 throw x.syntaxError("Expected 'CDATA['");
108 }
109 } else {
110 i = 1;
111 do {
112 token = x.nextMeta();
113 if (token == null) {
114 throw x.syntaxError("Missing '>' after '<!'.");
115 } else if (token == XML.LT) {
116 i += 1;
117 } else if (token == XML.GT) {
118 i -= 1;
119 }
120 } while (i > 0);
121 }
122 } else if (token == XML.QUEST) {
123
124// <?
125
126 x.skipPast("?>");
127 } else {
128 throw x.syntaxError("Misshaped tag");
129 }
130
131// Open tag <
132
133 } else {
134 if (!(token instanceof String)) {
135 throw x.syntaxError("Bad tagName '" + token + "'.");
136 }
137 tagName = (String)token;
138 newja = new JSONArray();
139 newjo = new JSONObject();
140 if (arrayForm) {
141 newja.put(tagName);
142 if (ja != null) {
143 ja.put(newja);
144 }
145 } else {
146 newjo.put("tagName", tagName);
147 if (ja != null) {
148 ja.put(newjo);
149 }
150 }
151 token = null;
152 for (;;) {
153 if (token == null) {
154 token = x.nextToken();
155 }
156 if (token == null) {
157 throw x.syntaxError("Misshaped tag");
158 }
159 if (!(token instanceof String)) {
160 break;
161 }
162
163// attribute = value
164
165 attribute = (String)token;
166 if (!arrayForm && ("tagName".equals(attribute) || "childNode".equals(attribute))) {
167 throw x.syntaxError("Reserved attribute.");
168 }
169 token = x.nextToken();
170 if (token == XML.EQ) {
171 token = x.nextToken();
172 if (!(token instanceof String)) {
173 throw x.syntaxError("Missing value");
174 }
175 newjo.accumulate(attribute, XML.stringToValue((String)token));
176 token = null;
177 } else {
178 newjo.accumulate(attribute, "");
179 }
180 }
181 if (arrayForm && newjo.length() > 0) {
182 newja.put(newjo);
183 }
184
185// Empty tag <.../>
186
187 if (token == XML.SLASH) {
188 if (x.nextToken() != XML.GT) {
189 throw x.syntaxError("Misshaped tag");
190 }
191 if (ja == null) {
192 if (arrayForm) {
193 return newja;
194 } else {
195 return newjo;
196 }
197 }
198
199// Content, between <...> and </...>
200
201 } else {
202 if (token != XML.GT) {
203 throw x.syntaxError("Misshaped tag");
204 }
205 closeTag = (String)parse(x, arrayForm, newja);
206 if (closeTag != null) {
207 if (!closeTag.equals(tagName)) {
208 throw x.syntaxError("Mismatched '" + tagName +
209 "' and '" + closeTag + "'");
210 }
211 tagName = null;
212 if (!arrayForm && newja.length() > 0) {
213 newjo.put("childNodes", newja);
214 }
215 if (ja == null) {
216 if (arrayForm) {
217 return newja;
218 } else {
219 return newjo;
220 }
221 }
222 }
223 }
224 }
225 } else {
226 if (ja != null) {
227 ja.put(token instanceof String
228 ? XML.stringToValue((String)token)
229 : token);
230 }
231 }
232 }
233 }
234
235
236 /**
237 * Convert a well-formed (but not necessarily valid) XML string into a
238 * JSONArray using the JsonML transform. Each XML tag is represented as
239 * a JSONArray in which the first element is the tag name. If the tag has
240 * attributes, then the second element will be JSONObject containing the
241 * name/value pairs. If the tag contains children, then strings and
242 * JSONArrays will represent the child tags.
243 * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored.
244 * @param string The source string.
245 * @return A JSONArray containing the structured data from the XML string.
246 * @throws JSONException
247 */
248 public static JSONArray toJSONArray(String string) throws JSONException {
249 return toJSONArray(new XMLTokener(string));
250 }
251
252
253 /**
254 * Convert a well-formed (but not necessarily valid) XML string into a
255 * JSONArray using the JsonML transform. Each XML tag is represented as
256 * a JSONArray in which the first element is the tag name. If the tag has
257 * attributes, then the second element will be JSONObject containing the
258 * name/value pairs. If the tag contains children, then strings and
259 * JSONArrays will represent the child content and tags.
260 * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored.
261 * @param x An XMLTokener.
262 * @return A JSONArray containing the structured data from the XML string.
263 * @throws JSONException
264 */
265 public static JSONArray toJSONArray(XMLTokener x) throws JSONException {
266 return (JSONArray)parse(x, true, null);
267 }
268
269
270 /**
271 * Convert a well-formed (but not necessarily valid) XML string into a
272 * JSONObject using the JsonML transform. Each XML tag is represented as
273 * a JSONObject with a "tagName" property. If the tag has attributes, then
274 * the attributes will be in the JSONObject as properties. If the tag
275 * contains children, the object will have a "childNodes" property which
276 * will be an array of strings and JsonML JSONObjects.
277
278 * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored.
279 * @param x An XMLTokener of the XML source text.
280 * @return A JSONObject containing the structured data from the XML string.
281 * @throws JSONException
282 */
283 public static JSONObject toJSONObject(XMLTokener x) throws JSONException {
284 return (JSONObject)parse(x, false, null);
285 }
286
287
288 /**
289 * Convert a well-formed (but not necessarily valid) XML string into a
290 * JSONObject using the JsonML transform. Each XML tag is represented as
291 * a JSONObject with a "tagName" property. If the tag has attributes, then
292 * the attributes will be in the JSONObject as properties. If the tag
293 * contains children, the object will have a "childNodes" property which
294 * will be an array of strings and JsonML JSONObjects.
295
296 * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored.
297 * @param string The XML source text.
298 * @return A JSONObject containing the structured data from the XML string.
299 * @throws JSONException
300 */
301 public static JSONObject toJSONObject(String string) throws JSONException {
302 return toJSONObject(new XMLTokener(string));
303 }
304
305
306 /**
307 * Reverse the JSONML transformation, making an XML text from a JSONArray.
308 * @param ja A JSONArray.
309 * @return An XML string.
310 * @throws JSONException
311 */
312 public static String toString(JSONArray ja) throws JSONException {
313 int i;
314 JSONObject jo;
315 String key;
316 Iterator keys;
317 int length;
318 Object object;
319 StringBuffer sb = new StringBuffer();
320 String tagName;
321 String value;
322
323// Emit <tagName
324
325 tagName = ja.getString(0);
326 XML.noSpace(tagName);
327 tagName = XML.escape(tagName);
328 sb.append('<');
329 sb.append(tagName);
330
331 object = ja.opt(1);
332 if (object instanceof JSONObject) {
333 i = 2;
334 jo = (JSONObject)object;
335
336// Emit the attributes
337
338 keys = jo.keys();
339 while (keys.hasNext()) {
340 key = keys.next().toString();
341 XML.noSpace(key);
342 value = jo.optString(key);
343 if (value != null) {
344 sb.append(' ');
345 sb.append(XML.escape(key));
346 sb.append('=');
347 sb.append('"');
348 sb.append(XML.escape(value));
349 sb.append('"');
350 }
351 }
352 } else {
353 i = 1;
354 }
355
356//Emit content in body
357
358 length = ja.length();
359 if (i >= length) {
360 sb.append('/');
361 sb.append('>');
362 } else {
363 sb.append('>');
364 do {
365 object = ja.get(i);
366 i += 1;
367 if (object != null) {
368 if (object instanceof String) {
369 sb.append(XML.escape(object.toString()));
370 } else if (object instanceof JSONObject) {
371 sb.append(toString((JSONObject)object));
372 } else if (object instanceof JSONArray) {
373 sb.append(toString((JSONArray)object));
374 }
375 }
376 } while (i < length);
377 sb.append('<');
378 sb.append('/');
379 sb.append(tagName);
380 sb.append('>');
381 }
382 return sb.toString();
383 }
384
385 /**
386 * Reverse the JSONML transformation, making an XML text from a JSONObject.
387 * The JSONObject must contain a "tagName" property. If it has children,
388 * then it must have a "childNodes" property containing an array of objects.
389 * The other properties are attributes with string values.
390 * @param jo A JSONObject.
391 * @return An XML string.
392 * @throws JSONException
393 */
394 public static String toString(JSONObject jo) throws JSONException {
395 StringBuffer sb = new StringBuffer();
396 int i;
397 JSONArray ja;
398 String key;
399 Iterator keys;
400 int length;
401 Object object;
402 String tagName;
403 String value;
404
405//Emit <tagName
406
407 tagName = jo.optString("tagName");
408 if (tagName == null) {
409 return XML.escape(jo.toString());
410 }
411 XML.noSpace(tagName);
412 tagName = XML.escape(tagName);
413 sb.append('<');
414 sb.append(tagName);
415
416//Emit the attributes
417
418 keys = jo.keys();
419 while (keys.hasNext()) {
420 key = keys.next().toString();
421 if (!"tagName".equals(key) && !"childNodes".equals(key)) {
422 XML.noSpace(key);
423 value = jo.optString(key);
424 if (value != null) {
425 sb.append(' ');
426 sb.append(XML.escape(key));
427 sb.append('=');
428 sb.append('"');
429 sb.append(XML.escape(value));
430 sb.append('"');
431 }
432 }
433 }
434
435//Emit content in body
436
437 ja = jo.optJSONArray("childNodes");
438 if (ja == null) {
439 sb.append('/');
440 sb.append('>');
441 } else {
442 sb.append('>');
443 length = ja.length();
444 for (i = 0; i < length; i += 1) {
445 object = ja.get(i);
446 if (object != null) {
447 if (object instanceof String) {
448 sb.append(XML.escape(object.toString()));
449 } else if (object instanceof JSONObject) {
450 sb.append(toString((JSONObject)object));
451 } else if (object instanceof JSONArray) {
452 sb.append(toString((JSONArray)object));
453 } else {
454 sb.append(object.toString());
455 }
456 }
457 }
458 sb.append('<');
459 sb.append('/');
460 sb.append(tagName);
461 sb.append('>');
462 }
463 return sb.toString();
464 }
465}