001package org.json;
002
003/*
004Copyright (c) 2002 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 JSONObject,
031 * and to covert a JSONObject into an XML text.
032 * @author JSON.org
033@version $Id$
034@since Ptolemy II 10.0
035 * @version 2010-04-08
036 */
037public class XML {
038
039    /** The Character '&'. */
040    public static final Character AMP = new Character('&');
041
042    /** The Character '''. */
043    public static final Character APOS = new Character('\'');
044
045    /** The Character '!'. */
046    public static final Character BANG = new Character('!');
047
048    /** The Character '='. */
049    public static final Character EQ = new Character('=');
050
051    /** The Character '>'. */
052    public static final Character GT = new Character('>');
053
054    /** The Character '<'. */
055    public static final Character LT = new Character('<');
056
057    /** The Character '?'. */
058    public static final Character QUEST = new Character('?');
059
060    /** The Character '"'. */
061    public static final Character QUOT = new Character('"');
062
063    /** The Character '/'. */
064    public static final Character SLASH = new Character('/');
065
066    /**
067     * Replace special characters with XML escapes:
068     * <pre>
069     * &amp; (ampersand) is replaced by &amp;amp;
070     * &lt; (less than) is replaced by &amp;lt;
071     * &gt; (greater than) is replaced by &amp;gt;
072     * &quot; (double quote) is replaced by &amp;quot;
073     * </pre>
074     * @param string The string to be escaped.
075     * @return The escaped string.
076     */
077    public static String escape(String string) {
078        StringBuffer sb = new StringBuffer();
079        for (int i = 0, len = string.length(); i < len; i++) {
080            char c = string.charAt(i);
081            switch (c) {
082            case '&':
083                sb.append("&amp;");
084                break;
085            case '<':
086                sb.append("&lt;");
087                break;
088            case '>':
089                sb.append("&gt;");
090                break;
091            case '"':
092                sb.append("&quot;");
093                break;
094            default:
095                sb.append(c);
096            }
097        }
098        return sb.toString();
099    }
100
101    /**
102     * Throw an exception if the string contains whitespace.
103     * Whitespace is not allowed in tagNames and attributes.
104     * @param string
105     * @exception JSONException
106     */
107    public static void noSpace(String string) throws JSONException {
108        int i, length = string.length();
109        if (length == 0) {
110            throw new JSONException("Empty string.");
111        }
112        for (i = 0; i < length; i += 1) {
113            if (Character.isWhitespace(string.charAt(i))) {
114                throw new JSONException(
115                        "'" + string + "' contains a space character.");
116            }
117        }
118    }
119
120    /**
121     * Scan the content following the named tag, attaching it to the context.
122     * @param x       The XMLTokener containing the source string.
123     * @param context The JSONObject that will include the new material.
124     * @param name    The tag name.
125     * @return true if the close tag is processed.
126     * @exception JSONException
127     */
128    private static boolean parse(XMLTokener x, JSONObject context, String name)
129            throws JSONException {
130        char c;
131        int i;
132        String n;
133        JSONObject o = null;
134        String s;
135        Object t;
136
137        // Test for and skip past these forms:
138        //      <!-- ... -->
139        //      <!   ...   >
140        //      <![  ... ]]>
141        //      <?   ...  ?>
142        // Report errors for these forms:
143        //      <>
144        //      <=
145        //      <<
146
147        t = x.nextToken();
148
149        // <!
150
151        if (t == BANG) {
152            c = x.next();
153            if (c == '-') {
154                if (x.next() == '-') {
155                    x.skipPast("-->");
156                    return false;
157                }
158                x.back();
159            } else if (c == '[') {
160                t = x.nextToken();
161                if (t.equals("CDATA")) {
162                    if (x.next() == '[') {
163                        s = x.nextCDATA();
164                        if (s.length() > 0) {
165                            context.accumulate("content", s);
166                        }
167                        return false;
168                    }
169                }
170                throw x.syntaxError("Expected 'CDATA['");
171            }
172            i = 1;
173            do {
174                t = x.nextMeta();
175                if (t == null) {
176                    throw x.syntaxError("Missing '>' after '<!'.");
177                } else if (t == LT) {
178                    i += 1;
179                } else if (t == GT) {
180                    i -= 1;
181                }
182            } while (i > 0);
183            return false;
184        } else if (t == QUEST) {
185
186            // <?
187
188            x.skipPast("?>");
189            return false;
190        } else if (t == SLASH) {
191
192            // Close tag </
193
194            t = x.nextToken();
195            if (name == null) {
196                throw x.syntaxError("Mismatched close tag" + t);
197            }
198            if (!t.equals(name)) {
199                throw x.syntaxError("Mismatched " + name + " and " + t);
200            }
201            if (x.nextToken() != GT) {
202                throw x.syntaxError("Misshaped close tag");
203            }
204            return true;
205
206        } else if (t instanceof Character) {
207            throw x.syntaxError("Misshaped tag");
208
209            // Open tag <
210
211        } else {
212            n = (String) t;
213            t = null;
214            o = new JSONObject();
215            for (;;) {
216                if (t == null) {
217                    t = x.nextToken();
218                }
219
220                // attribute = value
221
222                if (t instanceof String) {
223                    s = (String) t;
224                    t = x.nextToken();
225                    if (t == EQ) {
226                        t = x.nextToken();
227                        if (!(t instanceof String)) {
228                            throw x.syntaxError("Missing value");
229                        }
230                        o.accumulate(s, JSONObject.stringToValue((String) t));
231                        t = null;
232                    } else {
233                        o.accumulate(s, "");
234                    }
235
236                    // Empty tag <.../>
237
238                } else if (t == SLASH) {
239                    if (x.nextToken() != GT) {
240                        throw x.syntaxError("Misshaped tag");
241                    }
242                    if (o.length() > 0) {
243                        context.accumulate(n, o);
244                    } else {
245                        context.accumulate(n, "");
246                    }
247                    return false;
248
249                    // Content, between <...> and </...>
250
251                } else if (t == GT) {
252                    for (;;) {
253                        t = x.nextContent();
254                        if (t == null) {
255                            if (n != null) {
256                                throw x.syntaxError("Unclosed tag " + n);
257                            }
258                            return false;
259                        } else if (t instanceof String) {
260                            s = (String) t;
261                            if (s.length() > 0) {
262                                o.accumulate("content",
263                                        JSONObject.stringToValue(s));
264                            }
265
266                            // Nested element
267
268                        } else if (t == LT) {
269                            if (parse(x, o, n)) {
270                                if (o.length() == 0) {
271                                    context.accumulate(n, "");
272                                } else if (o.length() == 1
273                                        && o.opt("content") != null) {
274                                    context.accumulate(n, o.opt("content"));
275                                } else {
276                                    context.accumulate(n, o);
277                                }
278                                return false;
279                            }
280                        }
281                    }
282                } else {
283                    throw x.syntaxError("Misshaped tag");
284                }
285            }
286        }
287    }
288
289    /**
290     * Convert a well-formed (but not necessarily valid) XML string into a
291     * JSONObject. Some information may be lost in this transformation
292     * because JSON is a data format and XML is a document format. XML uses
293     * elements, attributes, and content text, while JSON uses unordered
294     * collections of name/value pairs and arrays of values. JSON does not
295     * does not like to distinguish between elements and attributes.
296     * Sequences of similar elements are represented as JSONArrays. Content
297     * text may be placed in a "content" member. Comments, prologs, DTDs, and
298     * <code>&lt;[ [ ]]&gt;</code> are ignored.
299     * @param string The source string.
300     * @return A JSONObject containing the structured data from the XML string.
301     * @exception JSONException
302     */
303    public static JSONObject toJSONObject(String string) throws JSONException {
304        JSONObject o = new JSONObject();
305        XMLTokener x = new XMLTokener(string);
306        while (x.more() && x.skipPast("<")) {
307            parse(x, o, null);
308        }
309        return o;
310    }
311
312    /**
313     * Convert a JSONObject into a well-formed, element-normal XML string.
314     * @param o A JSONObject.
315     * @return  A string.
316     * @exception  JSONException
317     */
318    public static String toString(Object o) throws JSONException {
319        return toString(o, null);
320    }
321
322    /**
323     * Convert a JSONObject into a well-formed, element-normal XML string.
324     * @param o A JSONObject.
325     * @param tagName The optional name of the enclosing tag.
326     * @return A string.
327     * @exception JSONException
328     */
329    public static String toString(Object o, String tagName)
330            throws JSONException {
331        StringBuffer b = new StringBuffer();
332        int i;
333        JSONArray ja;
334        JSONObject jo;
335        String k;
336        Iterator keys;
337        int len;
338        String s;
339        Object v;
340        if (o instanceof JSONObject) {
341
342            // Emit <tagName>
343
344            if (tagName != null) {
345                b.append('<');
346                b.append(tagName);
347                b.append('>');
348            }
349
350            // Loop thru the keys.
351
352            jo = (JSONObject) o;
353            keys = jo.keys();
354            while (keys.hasNext()) {
355                k = keys.next().toString();
356                v = jo.opt(k);
357                if (v == null) {
358                    v = "";
359                }
360                if (v instanceof String) {
361                    s = (String) v;
362                } else {
363                    s = null;
364                }
365
366                // Emit content in body
367
368                if (k.equals("content")) {
369                    if (v instanceof JSONArray) {
370                        ja = (JSONArray) v;
371                        len = ja.length();
372                        for (i = 0; i < len; i += 1) {
373                            if (i > 0) {
374                                b.append('\n');
375                            }
376                            b.append(escape(ja.get(i).toString()));
377                        }
378                    } else {
379                        b.append(escape(v.toString()));
380                    }
381
382                    // Emit an array of similar keys
383
384                } else if (v instanceof JSONArray) {
385                    ja = (JSONArray) v;
386                    len = ja.length();
387                    for (i = 0; i < len; i += 1) {
388                        v = ja.get(i);
389                        if (v instanceof JSONArray) {
390                            b.append('<');
391                            b.append(k);
392                            b.append('>');
393                            b.append(toString(v));
394                            b.append("</");
395                            b.append(k);
396                            b.append('>');
397                        } else {
398                            b.append(toString(v, k));
399                        }
400                    }
401                } else if (v.equals("")) {
402                    b.append('<');
403                    b.append(k);
404                    b.append("/>");
405
406                    // Emit a new tag <k>
407
408                } else {
409                    b.append(toString(v, k));
410                }
411            }
412            if (tagName != null) {
413
414                // Emit the </tagname> close tag
415
416                b.append("</");
417                b.append(tagName);
418                b.append('>');
419            }
420            return b.toString();
421
422            // XML does not have good support for arrays. If an array appears in a place
423            // where XML is lacking, synthesize an <array> element.
424
425        } else if (o instanceof JSONArray) {
426            ja = (JSONArray) o;
427            len = ja.length();
428            for (i = 0; i < len; ++i) {
429                v = ja.opt(i);
430                b.append(toString(v, tagName == null ? "array" : tagName));
431            }
432            return b.toString();
433        } else {
434            s = o == null ? "null" : escape(o.toString());
435            return tagName == null ? "\"" + s + "\""
436                    : s.length() == 0 ? "<" + tagName + "/>"
437                            : "<" + tagName + ">" + s + "</" + tagName + ">";
438        }
439    }
440}