blob: 325bee18b46d4a70c620d8105b9f9fe9bb216c18 [file] [log] [blame]
Jerome Poichet7c997852014-05-20 10:50:05 -07001package com.google.polo.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 2008-11-20
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(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, JSONObject.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 JSONObject.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>&lt;[ [ ]]></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>&lt;[ [ ]]></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 /**
265 * Convert a well-formed (but not necessarily valid) XML string into a
266 * JSONObject using the JsonML transform. Each XML tag is represented as
267 * a JSONObject with a "tagName" property. If the tag has attributes, then
268 * the attributes will be in the JSONObject as properties. If the tag
269 * contains children, the object will have a "childNodes" property which
270 * will be an array of strings and JsonML JSONObjects.
271
272 * Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
273 * @param x An XMLTokener of the XML source text.
274 * @return A JSONObject containing the structured data from the XML string.
275 * @throws JSONException
276 */
277 public static JSONObject toJSONObject(XMLTokener x) throws JSONException {
278 return (JSONObject)parse(x, false, null);
279 }
280 /**
281 * Convert a well-formed (but not necessarily valid) XML string into a
282 * JSONObject using the JsonML transform. Each XML tag is represented as
283 * a JSONObject with a "tagName" property. If the tag has attributes, then
284 * the attributes will be in the JSONObject as properties. If the tag
285 * contains children, the object will have a "childNodes" property which
286 * will be an array of strings and JsonML JSONObjects.
287
288 * Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
289 * @param string The XML source text.
290 * @return A JSONObject containing the structured data from the XML string.
291 * @throws JSONException
292 */
293 public static JSONObject toJSONObject(String string) throws JSONException {
294 return toJSONObject(new XMLTokener(string));
295 }
296
297
298 /**
299 * Reverse the JSONML transformation, making an XML text from a JSONArray.
300 * @param ja A JSONArray.
301 * @return An XML string.
302 * @throws JSONException
303 */
304 public static String toString(JSONArray ja) throws JSONException {
305 Object e;
306 int i;
307 JSONObject jo;
308 String k;
309 Iterator keys;
310 int length;
311 StringBuffer sb = new StringBuffer();
312 String tagName;
313 String v;
314
315// Emit <tagName
316
317 tagName = ja.getString(0);
318 XML.noSpace(tagName);
319 tagName = XML.escape(tagName);
320 sb.append('<');
321 sb.append(tagName);
322
323 e = ja.opt(1);
324 if (e instanceof JSONObject) {
325 i = 2;
326 jo = (JSONObject)e;
327
328// Emit the attributes
329
330 keys = jo.keys();
331 while (keys.hasNext()) {
332 k = keys.next().toString();
333 XML.noSpace(k);
334 v = jo.optString(k);
335 if (v != null) {
336 sb.append(' ');
337 sb.append(XML.escape(k));
338 sb.append('=');
339 sb.append('"');
340 sb.append(XML.escape(v));
341 sb.append('"');
342 }
343 }
344 } else {
345 i = 1;
346 }
347
348//Emit content in body
349
350 length = ja.length();
351 if (i >= length) {
352 sb.append('/');
353 sb.append('>');
354 } else {
355 sb.append('>');
356 do {
357 e = ja.get(i);
358 i += 1;
359 if (e != null) {
360 if (e instanceof String) {
361 sb.append(XML.escape(e.toString()));
362 } else if (e instanceof JSONObject) {
363 sb.append(toString((JSONObject)e));
364 } else if (e instanceof JSONArray) {
365 sb.append(toString((JSONArray)e));
366 }
367 }
368 } while (i < length);
369 sb.append('<');
370 sb.append('/');
371 sb.append(tagName);
372 sb.append('>');
373 }
374 return sb.toString();
375 }
376
377 /**
378 * Reverse the JSONML transformation, making an XML text from a JSONObject.
379 * The JSONObject must contain a "tagName" property. If it has children,
380 * then it must have a "childNodes" property containing an array of objects.
381 * The other properties are attributes with string values.
382 * @param jo A JSONObject.
383 * @return An XML string.
384 * @throws JSONException
385 */
386 public static String toString(JSONObject jo) throws JSONException {
387 StringBuffer sb = new StringBuffer();
388 Object e;
389 int i;
390 JSONArray ja;
391 String k;
392 Iterator keys;
393 int len;
394 String tagName;
395 String v;
396
397//Emit <tagName
398
399 tagName = jo.optString("tagName");
400 if (tagName == null) {
401 return XML.escape(jo.toString());
402 }
403 XML.noSpace(tagName);
404 tagName = XML.escape(tagName);
405 sb.append('<');
406 sb.append(tagName);
407
408//Emit the attributes
409
410 keys = jo.keys();
411 while (keys.hasNext()) {
412 k = keys.next().toString();
413 if (!k.equals("tagName") && !k.equals("childNodes")) {
414 XML.noSpace(k);
415 v = jo.optString(k);
416 if (v != null) {
417 sb.append(' ');
418 sb.append(XML.escape(k));
419 sb.append('=');
420 sb.append('"');
421 sb.append(XML.escape(v));
422 sb.append('"');
423 }
424 }
425 }
426
427//Emit content in body
428
429 ja = jo.optJSONArray("childNodes");
430 if (ja == null) {
431 sb.append('/');
432 sb.append('>');
433 } else {
434 sb.append('>');
435 len = ja.length();
436 for (i = 0; i < len; i += 1) {
437 e = ja.get(i);
438 if (e != null) {
439 if (e instanceof String) {
440 sb.append(XML.escape(e.toString()));
441 } else if (e instanceof JSONObject) {
442 sb.append(toString((JSONObject)e));
443 } else if (e instanceof JSONArray) {
444 sb.append(toString((JSONArray)e));
445 }
446 }
447 }
448 sb.append('<');
449 sb.append('/');
450 sb.append(tagName);
451 sb.append('>');
452 }
453 return sb.toString();
454 }
455}