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