1 | package org.json;
|
---|
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 | * @author JSON.org
|
---|
35 | * @version 2010-12-23
|
---|
36 | */
|
---|
37 | public 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(XMLTokener x, boolean arrayForm,
|
---|
49 | JSONArray ja) throws JSONException {
|
---|
50 | String attribute;
|
---|
51 | char c;
|
---|
52 | String closeTag = null;
|
---|
53 | int i;
|
---|
54 | JSONArray newja = null;
|
---|
55 | JSONObject newjo = null;
|
---|
56 | Object token;
|
---|
57 | String tagName = null;
|
---|
58 |
|
---|
59 | // Test for and skip past these forms:
|
---|
60 | // <!-- ... -->
|
---|
61 | // <![ ... ]]>
|
---|
62 | // <! ... >
|
---|
63 | // <? ... ?>
|
---|
64 |
|
---|
65 | while (true) {
|
---|
66 | token = x.nextContent();
|
---|
67 | if (token == XML.LT) {
|
---|
68 | token = x.nextToken();
|
---|
69 | if (token instanceof Character) {
|
---|
70 | if (token == XML.SLASH) {
|
---|
71 |
|
---|
72 | // Close tag </
|
---|
73 |
|
---|
74 | token = x.nextToken();
|
---|
75 | if (!(token instanceof String)) {
|
---|
76 | throw new JSONException(
|
---|
77 | "Expected a closing name instead of '" +
|
---|
78 | token + "'.");
|
---|
79 | }
|
---|
80 | if (x.nextToken() != XML.GT) {
|
---|
81 | throw x.syntaxError("Misshaped close tag");
|
---|
82 | }
|
---|
83 | return token;
|
---|
84 | } else if (token == XML.BANG) {
|
---|
85 |
|
---|
86 | // <!
|
---|
87 |
|
---|
88 | c = x.next();
|
---|
89 | if (c == '-') {
|
---|
90 | if (x.next() == '-') {
|
---|
91 | x.skipPast("-->");
|
---|
92 | }
|
---|
93 | x.back();
|
---|
94 | } else if (c == '[') {
|
---|
95 | token = x.nextToken();
|
---|
96 | if (token.equals("CDATA") && x.next() == '[') {
|
---|
97 | if (ja != null) {
|
---|
98 | ja.put(x.nextCDATA());
|
---|
99 | }
|
---|
100 | } else {
|
---|
101 | throw x.syntaxError("Expected 'CDATA['");
|
---|
102 | }
|
---|
103 | } else {
|
---|
104 | i = 1;
|
---|
105 | do {
|
---|
106 | token = x.nextMeta();
|
---|
107 | if (token == null) {
|
---|
108 | throw x.syntaxError("Missing '>' after '<!'.");
|
---|
109 | } else if (token == XML.LT) {
|
---|
110 | i += 1;
|
---|
111 | } else if (token == XML.GT) {
|
---|
112 | i -= 1;
|
---|
113 | }
|
---|
114 | } while (i > 0);
|
---|
115 | }
|
---|
116 | } else if (token == XML.QUEST) {
|
---|
117 |
|
---|
118 | // <?
|
---|
119 |
|
---|
120 | x.skipPast("?>");
|
---|
121 | } else {
|
---|
122 | throw x.syntaxError("Misshaped tag");
|
---|
123 | }
|
---|
124 |
|
---|
125 | // Open tag <
|
---|
126 |
|
---|
127 | } else {
|
---|
128 | if (!(token instanceof String)) {
|
---|
129 | throw x.syntaxError("Bad tagName '" + token + "'.");
|
---|
130 | }
|
---|
131 | tagName = (String)token;
|
---|
132 | newja = new JSONArray();
|
---|
133 | newjo = new JSONObject();
|
---|
134 | if (arrayForm) {
|
---|
135 | newja.put(tagName);
|
---|
136 | if (ja != null) {
|
---|
137 | ja.put(newja);
|
---|
138 | }
|
---|
139 | } else {
|
---|
140 | newjo.put("tagName", tagName);
|
---|
141 | if (ja != null) {
|
---|
142 | ja.put(newjo);
|
---|
143 | }
|
---|
144 | }
|
---|
145 | token = null;
|
---|
146 | for (;;) {
|
---|
147 | if (token == null) {
|
---|
148 | token = x.nextToken();
|
---|
149 | }
|
---|
150 | if (token == null) {
|
---|
151 | throw x.syntaxError("Misshaped tag");
|
---|
152 | }
|
---|
153 | if (!(token instanceof String)) {
|
---|
154 | break;
|
---|
155 | }
|
---|
156 |
|
---|
157 | // attribute = value
|
---|
158 |
|
---|
159 | attribute = (String)token;
|
---|
160 | if (!arrayForm && (attribute == "tagName" || attribute == "childNode")) {
|
---|
161 | throw x.syntaxError("Reserved attribute.");
|
---|
162 | }
|
---|
163 | token = x.nextToken();
|
---|
164 | if (token == XML.EQ) {
|
---|
165 | token = x.nextToken();
|
---|
166 | if (!(token instanceof String)) {
|
---|
167 | throw x.syntaxError("Missing value");
|
---|
168 | }
|
---|
169 | newjo.accumulate(attribute, XML.stringToValue((String)token));
|
---|
170 | token = null;
|
---|
171 | } else {
|
---|
172 | newjo.accumulate(attribute, "");
|
---|
173 | }
|
---|
174 | }
|
---|
175 | if (arrayForm && newjo.length() > 0) {
|
---|
176 | newja.put(newjo);
|
---|
177 | }
|
---|
178 |
|
---|
179 | // Empty tag <.../>
|
---|
180 |
|
---|
181 | if (token == XML.SLASH) {
|
---|
182 | if (x.nextToken() != XML.GT) {
|
---|
183 | throw x.syntaxError("Misshaped tag");
|
---|
184 | }
|
---|
185 | if (ja == null) {
|
---|
186 | if (arrayForm) {
|
---|
187 | return newja;
|
---|
188 | } else {
|
---|
189 | return newjo;
|
---|
190 | }
|
---|
191 | }
|
---|
192 |
|
---|
193 | // Content, between <...> and </...>
|
---|
194 |
|
---|
195 | } else {
|
---|
196 | if (token != XML.GT) {
|
---|
197 | throw x.syntaxError("Misshaped tag");
|
---|
198 | }
|
---|
199 | closeTag = (String)parse(x, arrayForm, newja);
|
---|
200 | if (closeTag != null) {
|
---|
201 | if (!closeTag.equals(tagName)) {
|
---|
202 | throw x.syntaxError("Mismatched '" + tagName +
|
---|
203 | "' and '" + closeTag + "'");
|
---|
204 | }
|
---|
205 | tagName = null;
|
---|
206 | if (!arrayForm && newja.length() > 0) {
|
---|
207 | newjo.put("childNodes", newja);
|
---|
208 | }
|
---|
209 | if (ja == null) {
|
---|
210 | if (arrayForm) {
|
---|
211 | return newja;
|
---|
212 | } else {
|
---|
213 | return newjo;
|
---|
214 | }
|
---|
215 | }
|
---|
216 | }
|
---|
217 | }
|
---|
218 | }
|
---|
219 | } else {
|
---|
220 | if (ja != null) {
|
---|
221 | ja.put(token instanceof String ?
|
---|
222 | XML.stringToValue((String)token) : token);
|
---|
223 | }
|
---|
224 | }
|
---|
225 | }
|
---|
226 | }
|
---|
227 |
|
---|
228 |
|
---|
229 | /**
|
---|
230 | * Convert a well-formed (but not necessarily valid) XML string into a
|
---|
231 | * JSONArray using the JsonML transform. Each XML tag is represented as
|
---|
232 | * a JSONArray in which the first element is the tag name. If the tag has
|
---|
233 | * attributes, then the second element will be JSONObject containing the
|
---|
234 | * name/value pairs. If the tag contains children, then strings and
|
---|
235 | * JSONArrays will represent the child tags.
|
---|
236 | * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored.
|
---|
237 | * @param string The source string.
|
---|
238 | * @return A JSONArray containing the structured data from the XML string.
|
---|
239 | * @throws JSONException
|
---|
240 | */
|
---|
241 | public static JSONArray toJSONArray(String string) throws JSONException {
|
---|
242 | return toJSONArray(new XMLTokener(string));
|
---|
243 | }
|
---|
244 |
|
---|
245 |
|
---|
246 | /**
|
---|
247 | * Convert a well-formed (but not necessarily valid) XML string into a
|
---|
248 | * JSONArray using the JsonML transform. Each XML tag is represented as
|
---|
249 | * a JSONArray in which the first element is the tag name. If the tag has
|
---|
250 | * attributes, then the second element will be JSONObject containing the
|
---|
251 | * name/value pairs. If the tag contains children, then strings and
|
---|
252 | * JSONArrays will represent the child content and tags.
|
---|
253 | * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored.
|
---|
254 | * @param x An XMLTokener.
|
---|
255 | * @return A JSONArray containing the structured data from the XML string.
|
---|
256 | * @throws JSONException
|
---|
257 | */
|
---|
258 | public static JSONArray toJSONArray(XMLTokener x) throws JSONException {
|
---|
259 | return (JSONArray)parse(x, true, null);
|
---|
260 | }
|
---|
261 |
|
---|
262 |
|
---|
263 | /**
|
---|
264 | * Convert a well-formed (but not necessarily valid) XML string into a
|
---|
265 | * JSONObject using the JsonML transform. Each XML tag is represented as
|
---|
266 | * a JSONObject with a "tagName" property. If the tag has attributes, then
|
---|
267 | * the attributes will be in the JSONObject as properties. If the tag
|
---|
268 | * contains children, the object will have a "childNodes" property which
|
---|
269 | * will be an array of strings and JsonML JSONObjects.
|
---|
270 |
|
---|
271 | * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored.
|
---|
272 | * @param x An XMLTokener of the XML source text.
|
---|
273 | * @return A JSONObject containing the structured data from the XML string.
|
---|
274 | * @throws JSONException
|
---|
275 | */
|
---|
276 | public static JSONObject toJSONObject(XMLTokener x) throws JSONException {
|
---|
277 | return (JSONObject)parse(x, false, null);
|
---|
278 | }
|
---|
279 |
|
---|
280 |
|
---|
281 | /**
|
---|
282 | * Convert a well-formed (but not necessarily valid) XML string into a
|
---|
283 | * JSONObject using the JsonML transform. Each XML tag is represented as
|
---|
284 | * a JSONObject with a "tagName" property. If the tag has attributes, then
|
---|
285 | * the attributes will be in the JSONObject as properties. If the tag
|
---|
286 | * contains children, the object will have a "childNodes" property which
|
---|
287 | * will be an array of strings and JsonML JSONObjects.
|
---|
288 |
|
---|
289 | * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored.
|
---|
290 | * @param string The XML source text.
|
---|
291 | * @return A JSONObject containing the structured data from the XML string.
|
---|
292 | * @throws JSONException
|
---|
293 | */
|
---|
294 | public static JSONObject toJSONObject(String string) throws JSONException {
|
---|
295 | return toJSONObject(new XMLTokener(string));
|
---|
296 | }
|
---|
297 |
|
---|
298 |
|
---|
299 | /**
|
---|
300 | * Reverse the JSONML transformation, making an XML text from a JSONArray.
|
---|
301 | * @param ja A JSONArray.
|
---|
302 | * @return An XML string.
|
---|
303 | * @throws JSONException
|
---|
304 | */
|
---|
305 | public static String toString(JSONArray ja) throws JSONException {
|
---|
306 | int i;
|
---|
307 | JSONObject jo;
|
---|
308 | String key;
|
---|
309 | Iterator keys;
|
---|
310 | int length;
|
---|
311 | Object object;
|
---|
312 | StringBuffer sb = new StringBuffer();
|
---|
313 | String tagName;
|
---|
314 | String value;
|
---|
315 |
|
---|
316 | // Emit <tagName
|
---|
317 |
|
---|
318 | tagName = ja.getString(0);
|
---|
319 | XML.noSpace(tagName);
|
---|
320 | tagName = XML.escape(tagName);
|
---|
321 | sb.append('<');
|
---|
322 | sb.append(tagName);
|
---|
323 |
|
---|
324 | object = ja.opt(1);
|
---|
325 | if (object instanceof JSONObject) {
|
---|
326 | i = 2;
|
---|
327 | jo = (JSONObject)object;
|
---|
328 |
|
---|
329 | // Emit the attributes
|
---|
330 |
|
---|
331 | keys = jo.keys();
|
---|
332 | while (keys.hasNext()) {
|
---|
333 | key = keys.next().toString();
|
---|
334 | XML.noSpace(key);
|
---|
335 | value = jo.optString(key);
|
---|
336 | if (value != null) {
|
---|
337 | sb.append(' ');
|
---|
338 | sb.append(XML.escape(key));
|
---|
339 | sb.append('=');
|
---|
340 | sb.append('"');
|
---|
341 | sb.append(XML.escape(value));
|
---|
342 | sb.append('"');
|
---|
343 | }
|
---|
344 | }
|
---|
345 | } else {
|
---|
346 | i = 1;
|
---|
347 | }
|
---|
348 |
|
---|
349 | //Emit content in body
|
---|
350 |
|
---|
351 | length = ja.length();
|
---|
352 | if (i >= length) {
|
---|
353 | sb.append('/');
|
---|
354 | sb.append('>');
|
---|
355 | } else {
|
---|
356 | sb.append('>');
|
---|
357 | do {
|
---|
358 | object = ja.get(i);
|
---|
359 | i += 1;
|
---|
360 | if (object != null) {
|
---|
361 | if (object instanceof String) {
|
---|
362 | sb.append(XML.escape(object.toString()));
|
---|
363 | } else if (object instanceof JSONObject) {
|
---|
364 | sb.append(toString((JSONObject)object));
|
---|
365 | } else if (object instanceof JSONArray) {
|
---|
366 | sb.append(toString((JSONArray)object));
|
---|
367 | }
|
---|
368 | }
|
---|
369 | } while (i < length);
|
---|
370 | sb.append('<');
|
---|
371 | sb.append('/');
|
---|
372 | sb.append(tagName);
|
---|
373 | sb.append('>');
|
---|
374 | }
|
---|
375 | return sb.toString();
|
---|
376 | }
|
---|
377 |
|
---|
378 | /**
|
---|
379 | * Reverse the JSONML transformation, making an XML text from a JSONObject.
|
---|
380 | * The JSONObject must contain a "tagName" property. If it has children,
|
---|
381 | * then it must have a "childNodes" property containing an array of objects.
|
---|
382 | * The other properties are attributes with string values.
|
---|
383 | * @param jo A JSONObject.
|
---|
384 | * @return An XML string.
|
---|
385 | * @throws JSONException
|
---|
386 | */
|
---|
387 | public static String toString(JSONObject jo) throws JSONException {
|
---|
388 | StringBuffer sb = new StringBuffer();
|
---|
389 | int i;
|
---|
390 | JSONArray ja;
|
---|
391 | String key;
|
---|
392 | Iterator keys;
|
---|
393 | int length;
|
---|
394 | Object object;
|
---|
395 | String tagName;
|
---|
396 | String value;
|
---|
397 |
|
---|
398 | //Emit <tagName
|
---|
399 |
|
---|
400 | tagName = jo.optString("tagName");
|
---|
401 | if (tagName == null) {
|
---|
402 | return XML.escape(jo.toString());
|
---|
403 | }
|
---|
404 | XML.noSpace(tagName);
|
---|
405 | tagName = XML.escape(tagName);
|
---|
406 | sb.append('<');
|
---|
407 | sb.append(tagName);
|
---|
408 |
|
---|
409 | //Emit the attributes
|
---|
410 |
|
---|
411 | keys = jo.keys();
|
---|
412 | while (keys.hasNext()) {
|
---|
413 | key = keys.next().toString();
|
---|
414 | if (!key.equals("tagName") && !key.equals("childNodes")) {
|
---|
415 | XML.noSpace(key);
|
---|
416 | value = jo.optString(key);
|
---|
417 | if (value != null) {
|
---|
418 | sb.append(' ');
|
---|
419 | sb.append(XML.escape(key));
|
---|
420 | sb.append('=');
|
---|
421 | sb.append('"');
|
---|
422 | sb.append(XML.escape(value));
|
---|
423 | sb.append('"');
|
---|
424 | }
|
---|
425 | }
|
---|
426 | }
|
---|
427 |
|
---|
428 | //Emit content in body
|
---|
429 |
|
---|
430 | ja = jo.optJSONArray("childNodes");
|
---|
431 | if (ja == null) {
|
---|
432 | sb.append('/');
|
---|
433 | sb.append('>');
|
---|
434 | } else {
|
---|
435 | sb.append('>');
|
---|
436 | length = ja.length();
|
---|
437 | for (i = 0; i < length; i += 1) {
|
---|
438 | object = ja.get(i);
|
---|
439 | if (object != null) {
|
---|
440 | if (object instanceof String) {
|
---|
441 | sb.append(XML.escape(object.toString()));
|
---|
442 | } else if (object instanceof JSONObject) {
|
---|
443 | sb.append(toString((JSONObject)object));
|
---|
444 | } else if (object instanceof JSONArray) {
|
---|
445 | sb.append(toString((JSONArray)object));
|
---|
446 | }
|
---|
447 | }
|
---|
448 | }
|
---|
449 | sb.append('<');
|
---|
450 | sb.append('/');
|
---|
451 | sb.append(tagName);
|
---|
452 | sb.append('>');
|
---|
453 | }
|
---|
454 | return sb.toString();
|
---|
455 | }
|
---|
456 | } |
---|