001package org.json;
002
003/*
004Copyright (c) 2008 JSON.org
005
006Permission is hereby granted, free of charge, to any person obtaining a copy
007of this software and associated documentation files (the "Software"), to deal
008in the Software without restriction, including without limitation the rights
009to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010copies of the Software, and to permit persons to whom the Software is
011furnished to do so, subject to the following conditions:
012
013The above copyright notice and this permission notice shall be included in all
014copies or substantial portions of the Software.
015
016The Software shall be used for Good, not Evil.
017
018THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
019IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
020FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
021AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
022LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
023OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
024SOFTWARE.
025 */
026
027import java.util.Iterator;
028
029/**
030 * This provides static methods to convert an XML text into a JSONArray or
031 * JSONObject, and to covert a JSONArray or JSONObject into an XML text using
032 * the JsonML transform.
033 * @author JSON.org
034@version $Id$
035@since Ptolemy II 10.0
036 * @version 2010-02-12
037 */
038public class JSONML {
039
040    /**
041     * Parse XML values and store them in a JSONArray.
042     * @param x       The XMLTokener containing the source string.
043     * @param arrayForm true if array form, false if object form.
044     * @param ja      The JSONArray that is containing the current tag or null
045     *     if we are at the outermost level.
046     * @return A JSONArray if the value is the outermost tag, otherwise null.
047     * @exception JSONException
048     */
049    private static Object parse(XMLTokener x, boolean arrayForm, JSONArray ja)
050            throws JSONException {
051        String attribute;
052        char c;
053        String closeTag = null;
054        int i;
055        JSONArray newja = null;
056        JSONObject newjo = null;
057        Object token;
058        String tagName = null;
059
060        // Test for and skip past these forms:
061        //      <!-- ... -->
062        //      <![  ... ]]>
063        //      <!   ...   >
064        //      <?   ...  ?>
065
066        while (true) {
067            token = x.nextContent();
068            if (token == XML.LT) {
069                token = x.nextToken();
070                if (token instanceof Character) {
071                    if (token == XML.SLASH) {
072
073                        // Close tag </
074
075                        token = x.nextToken();
076                        if (!(token instanceof String)) {
077                            throw new JSONException(
078                                    "Expected a closing name instead of '"
079                                            + token + "'.");
080                        }
081                        if (x.nextToken() != XML.GT) {
082                            throw x.syntaxError("Misshaped close tag");
083                        }
084                        return token;
085                    } else if (token == XML.BANG) {
086
087                        // <!
088
089                        c = x.next();
090                        if (c == '-') {
091                            if (x.next() == '-') {
092                                x.skipPast("-->");
093                            }
094                            x.back();
095                        } else if (c == '[') {
096                            token = x.nextToken();
097                            if (token.equals("CDATA") && x.next() == '[') {
098                                if (ja != null) {
099                                    ja.put(x.nextCDATA());
100                                }
101                            } else {
102                                throw x.syntaxError("Expected 'CDATA['");
103                            }
104                        } else {
105                            i = 1;
106                            do {
107                                token = x.nextMeta();
108                                if (token == null) {
109                                    throw x.syntaxError(
110                                            "Missing '>' after '<!'.");
111                                } else if (token == XML.LT) {
112                                    i += 1;
113                                } else if (token == XML.GT) {
114                                    i -= 1;
115                                }
116                            } while (i > 0);
117                        }
118                    } else if (token == XML.QUEST) {
119
120                        // <?
121
122                        x.skipPast("?>");
123                    } else {
124                        throw x.syntaxError("Misshaped tag");
125                    }
126
127                    // Open tag <
128
129                } else {
130                    if (!(token instanceof String)) {
131                        throw x.syntaxError("Bad tagName '" + token + "'.");
132                    }
133                    tagName = (String) token;
134                    newja = new JSONArray();
135                    newjo = new JSONObject();
136                    if (arrayForm) {
137                        newja.put(tagName);
138                        if (ja != null) {
139                            ja.put(newja);
140                        }
141                    } else {
142                        newjo.put("tagName", tagName);
143                        if (ja != null) {
144                            ja.put(newjo);
145                        }
146                    }
147                    token = null;
148                    for (;;) {
149                        if (token == null) {
150                            token = x.nextToken();
151                        }
152                        if (token == null) {
153                            throw x.syntaxError("Misshaped tag");
154                        }
155                        if (!(token instanceof String)) {
156                            break;
157                        }
158
159                        //                              attribute = value
160
161                        attribute = (String) token;
162                        if (!arrayForm && (attribute == "tagName"
163                                || attribute == "childNode")) {
164                            throw x.syntaxError("Reserved attribute.");
165                        }
166                        token = x.nextToken();
167                        if (token == XML.EQ) {
168                            token = x.nextToken();
169                            if (!(token instanceof String)) {
170                                throw x.syntaxError("Missing value");
171                            }
172                            newjo.accumulate(attribute,
173                                    JSONObject.stringToValue((String) token));
174                            token = null;
175                        } else {
176                            newjo.accumulate(attribute, "");
177                        }
178                    }
179                    if (arrayForm && newjo.length() > 0) {
180                        newja.put(newjo);
181                    }
182
183                    // Empty tag <.../>
184
185                    if (token == XML.SLASH) {
186                        if (x.nextToken() != XML.GT) {
187                            throw x.syntaxError("Misshaped tag");
188                        }
189                        if (ja == null) {
190                            if (arrayForm) {
191                                return newja;
192                            } else {
193                                return newjo;
194                            }
195                        }
196
197                        // Content, between <...> and </...>
198
199                    } else {
200                        if (token != XML.GT) {
201                            throw x.syntaxError("Misshaped tag");
202                        }
203                        closeTag = (String) parse(x, arrayForm, newja);
204                        if (closeTag != null) {
205                            if (!closeTag.equals(tagName)) {
206                                throw x.syntaxError("Mismatched '" + tagName
207                                        + "' and '" + closeTag + "'");
208                            }
209                            tagName = null;
210                            if (!arrayForm && newja.length() > 0) {
211                                newjo.put("childNodes", newja);
212                            }
213                            if (ja == null) {
214                                if (arrayForm) {
215                                    return newja;
216                                } else {
217                                    return newjo;
218                                }
219                            }
220                        }
221                    }
222                }
223            } else {
224                if (ja != null) {
225                    ja.put(token instanceof String
226                            ? JSONObject.stringToValue((String) token)
227                            : token);
228                }
229            }
230        }
231    }
232
233    /**
234     * Convert a well-formed (but not necessarily valid) XML string into a
235     * JSONArray using the JsonML transform. Each XML tag is represented as
236     * a JSONArray in which the first element is the tag name. If the tag has
237     * attributes, then the second element will be JSONObject containing the
238     * name/value pairs. If the tag contains children, then strings and
239     * JSONArrays will represent the child tags.
240     * Comments, prologs, DTDs, and <code>&lt;[ [ ]]&gt;</code> are ignored.
241     * @param string The source string.
242     * @return A JSONArray containing the structured data from the XML string.
243     * @exception JSONException
244     */
245    public static JSONArray toJSONArray(String string) throws JSONException {
246        return toJSONArray(new XMLTokener(string));
247    }
248
249    /**
250     * Convert a well-formed (but not necessarily valid) XML string into a
251     * JSONArray using the JsonML transform. Each XML tag is represented as
252     * a JSONArray in which the first element is the tag name. If the tag has
253     * attributes, then the second element will be JSONObject containing the
254     * name/value pairs. If the tag contains children, then strings and
255     * JSONArrays will represent the child content and tags.
256     * Comments, prologs, DTDs, and <code>&lt;[ [ ]]&gt;</code> are ignored.
257     * @param x An XMLTokener.
258     * @return A JSONArray containing the structured data from the XML string.
259     * @exception JSONException
260     */
261    public static JSONArray toJSONArray(XMLTokener x) throws JSONException {
262        return (JSONArray) parse(x, true, null);
263    }
264
265    /**
266     * Convert a well-formed (but not necessarily valid) XML string into a
267     * JSONObject using the JsonML transform. Each XML tag is represented as
268     * a JSONObject with a "tagName" property. If the tag has attributes, then
269     * the attributes will be in the JSONObject as properties. If the tag
270     * contains children, the object will have a "childNodes" property which
271     * will be an array of strings and JsonML JSONObjects.
272
273     * Comments, prologs, DTDs, and <code>&lt;[ [ ]]&gt;</code> are ignored.
274     * @param x An XMLTokener of the XML source text.
275     * @return A JSONObject containing the structured data from the XML string.
276     * @exception JSONException
277     */
278    public static JSONObject toJSONObject(XMLTokener x) throws JSONException {
279        return (JSONObject) parse(x, false, null);
280    }
281
282    /**
283     * Convert a well-formed (but not necessarily valid) XML string into a
284     * JSONObject using the JsonML transform. Each XML tag is represented as
285     * a JSONObject with a "tagName" property. If the tag has attributes, then
286     * the attributes will be in the JSONObject as properties. If the tag
287     * contains children, the object will have a "childNodes" property which
288     * will be an array of strings and JsonML JSONObjects.
289     * Comments, prologs, DTDs, and <code>&lt;[ [ ]]&gt;</code> are ignored.
290     * @param string The XML source text.
291     * @return A JSONObject containing the structured data from the XML string.
292     * @exception JSONException
293     */
294    public static JSONObject toJSONObject(String string) throws JSONException {
295        return toJSONObject(new XMLTokener(string));
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     * @exception 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     * @exception 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}