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/**
031 * This provides static methods to convert an XML text into a JSONArray or
032 * JSONObject, and to covert a JSONArray or JSONObject into an XML text using
033 * the JsonML transform.
034 *
035 * @author JSON.org
036 * @version 2016-01-30
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     * @throws JSONException
048     */
049    private static Object parse(
050        XMLTokener x,
051        boolean    arrayForm,
052        JSONArray  ja
053    ) throws JSONException {
054        String     attribute;
055        char       c;
056        String     closeTag = null;
057        int        i;
058        JSONArray  newja = null;
059        JSONObject newjo = null;
060        Object     token;
061        String     tagName = null;
062
063// Test for and skip past these forms:
064//      <!-- ... -->
065//      <![  ... ]]>
066//      <!   ...   >
067//      <?   ...  ?>
068
069        while (true) {
070            if (!x.more()) {
071                throw x.syntaxError("Bad XML");
072            }
073            token = x.nextContent();
074            if (token == XML.LT) {
075                token = x.nextToken();
076                if (token instanceof Character) {
077                    if (token == XML.SLASH) {
078
079// Close tag </
080
081                        token = x.nextToken();
082                        if (!(token instanceof String)) {
083                            throw new JSONException(
084                                    "Expected a closing name instead of '" +
085                                    token + "'.");
086                        }
087                        if (x.nextToken() != XML.GT) {
088                            throw x.syntaxError("Misshaped close tag");
089                        }
090                        return token;
091                    } else if (token == XML.BANG) {
092
093// <!
094
095                        c = x.next();
096                        if (c == '-') {
097                            if (x.next() == '-') {
098                                x.skipPast("-->");
099                            } 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, JSONObject.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                        ? JSONObject.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>&lt;[ [ ]]></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>&lt;[ [ ]]></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>&lt;[ [ ]]></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>&lt;[ [ ]]></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<String>    keys;
319        int                 length;
320        Object              object;
321        StringBuilder        sb = new StringBuilder();
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();
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                    } else {
377                        sb.append(object.toString());
378                    }
379                }
380            } while (i < length);
381            sb.append('<');
382            sb.append('/');
383            sb.append(tagName);
384            sb.append('>');
385        }
386        return sb.toString();
387    }
388
389    /**
390     * Reverse the JSONML transformation, making an XML text from a JSONObject.
391     * The JSONObject must contain a "tagName" property. If it has children,
392     * then it must have a "childNodes" property containing an array of objects.
393     * The other properties are attributes with string values.
394     * @param jo A JSONObject.
395     * @return An XML string.
396     * @throws JSONException
397     */
398    public static String toString(JSONObject jo) throws JSONException {
399        StringBuilder sb = new StringBuilder();
400        int                 i;
401        JSONArray           ja;
402        String              key;
403        Iterator<String>    keys;
404        int                 length;
405        Object              object;
406        String              tagName;
407        String              value;
408
409//Emit <tagName
410
411        tagName = jo.optString("tagName");
412        if (tagName == null) {
413            return XML.escape(jo.toString());
414        }
415        XML.noSpace(tagName);
416        tagName = XML.escape(tagName);
417        sb.append('<');
418        sb.append(tagName);
419
420//Emit the attributes
421
422        keys = jo.keys();
423        while (keys.hasNext()) {
424            key = keys.next();
425            if (!"tagName".equals(key) && !"childNodes".equals(key)) {
426                XML.noSpace(key);
427                value = jo.optString(key);
428                if (value != null) {
429                    sb.append(' ');
430                    sb.append(XML.escape(key));
431                    sb.append('=');
432                    sb.append('"');
433                    sb.append(XML.escape(value));
434                    sb.append('"');
435                }
436            }
437        }
438
439//Emit content in body
440
441        ja = jo.optJSONArray("childNodes");
442        if (ja == null) {
443            sb.append('/');
444            sb.append('>');
445        } else {
446            sb.append('>');
447            length = ja.length();
448            for (i = 0; i < length; i += 1) {
449                object = ja.get(i);
450                if (object != null) {
451                    if (object instanceof String) {
452                        sb.append(XML.escape(object.toString()));
453                    } else if (object instanceof JSONObject) {
454                        sb.append(toString((JSONObject)object));
455                    } else if (object instanceof JSONArray) {
456                        sb.append(toString((JSONArray)object));
457                    } else {
458                        sb.append(object.toString());
459                    }
460                }
461            }
462            sb.append('<');
463            sb.append('/');
464            sb.append(tagName);
465            sb.append('>');
466        }
467        return sb.toString();
468    }
469}