| package com.google.polo.json; |
| |
| /* |
| Copyright (c) 2002 JSON.org |
| |
| Permission is hereby granted, free of charge, to any person obtaining a copy |
| of this software and associated documentation files (the "Software"), to deal |
| in the Software without restriction, including without limitation the rights |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| copies of the Software, and to permit persons to whom the Software is |
| furnished to do so, subject to the following conditions: |
| |
| The above copyright notice and this permission notice shall be included in all |
| copies or substantial portions of the Software. |
| |
| The Software shall be used for Good, not Evil. |
| |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| SOFTWARE. |
| */ |
| |
| import java.util.Iterator; |
| |
| |
| /** |
| * This provides static methods to convert an XML text into a JSONObject, |
| * and to covert a JSONObject into an XML text. |
| * @author JSON.org |
| * @version 2008-10-14 |
| */ |
| public class XML { |
| |
| /** The Character '&'. */ |
| public static final Character AMP = new Character('&'); |
| |
| /** The Character '''. */ |
| public static final Character APOS = new Character('\''); |
| |
| /** The Character '!'. */ |
| public static final Character BANG = new Character('!'); |
| |
| /** The Character '='. */ |
| public static final Character EQ = new Character('='); |
| |
| /** The Character '>'. */ |
| public static final Character GT = new Character('>'); |
| |
| /** The Character '<'. */ |
| public static final Character LT = new Character('<'); |
| |
| /** The Character '?'. */ |
| public static final Character QUEST = new Character('?'); |
| |
| /** The Character '"'. */ |
| public static final Character QUOT = new Character('"'); |
| |
| /** The Character '/'. */ |
| public static final Character SLASH = new Character('/'); |
| |
| /** |
| * Replace special characters with XML escapes: |
| * <pre> |
| * & <small>(ampersand)</small> is replaced by &amp; |
| * < <small>(less than)</small> is replaced by &lt; |
| * > <small>(greater than)</small> is replaced by &gt; |
| * " <small>(double quote)</small> is replaced by &quot; |
| * </pre> |
| * @param string The string to be escaped. |
| * @return The escaped string. |
| */ |
| public static String escape(String string) { |
| StringBuffer sb = new StringBuffer(); |
| for (int i = 0, len = string.length(); i < len; i++) { |
| char c = string.charAt(i); |
| switch (c) { |
| case '&': |
| sb.append("&"); |
| break; |
| case '<': |
| sb.append("<"); |
| break; |
| case '>': |
| sb.append(">"); |
| break; |
| case '"': |
| sb.append("""); |
| break; |
| default: |
| sb.append(c); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Throw an exception if the string contains whitespace. |
| * Whitespace is not allowed in tagNames and attributes. |
| * @param string |
| * @throws JSONException |
| */ |
| public static void noSpace(String string) throws JSONException { |
| int i, length = string.length(); |
| if (length == 0) { |
| throw new JSONException("Empty string."); |
| } |
| for (i = 0; i < length; i += 1) { |
| if (Character.isWhitespace(string.charAt(i))) { |
| throw new JSONException("'" + string + |
| "' contains a space character."); |
| } |
| } |
| } |
| |
| /** |
| * Scan the content following the named tag, attaching it to the context. |
| * @param x The XMLTokener containing the source string. |
| * @param context The JSONObject that will include the new material. |
| * @param name The tag name. |
| * @return true if the close tag is processed. |
| * @throws JSONException |
| */ |
| private static boolean parse(XMLTokener x, JSONObject context, |
| String name) throws JSONException { |
| char c; |
| int i; |
| String n; |
| JSONObject o = null; |
| String s; |
| Object t; |
| |
| // Test for and skip past these forms: |
| // <!-- ... --> |
| // <! ... > |
| // <![ ... ]]> |
| // <? ... ?> |
| // Report errors for these forms: |
| // <> |
| // <= |
| // << |
| |
| t = x.nextToken(); |
| |
| // <! |
| |
| if (t == BANG) { |
| c = x.next(); |
| if (c == '-') { |
| if (x.next() == '-') { |
| x.skipPast("-->"); |
| return false; |
| } |
| x.back(); |
| } else if (c == '[') { |
| t = x.nextToken(); |
| if (t.equals("CDATA")) { |
| if (x.next() == '[') { |
| s = x.nextCDATA(); |
| if (s.length() > 0) { |
| context.accumulate("content", s); |
| } |
| return false; |
| } |
| } |
| throw x.syntaxError("Expected 'CDATA['"); |
| } |
| i = 1; |
| do { |
| t = x.nextMeta(); |
| if (t == null) { |
| throw x.syntaxError("Missing '>' after '<!'."); |
| } else if (t == LT) { |
| i += 1; |
| } else if (t == GT) { |
| i -= 1; |
| } |
| } while (i > 0); |
| return false; |
| } else if (t == QUEST) { |
| |
| // <? |
| |
| x.skipPast("?>"); |
| return false; |
| } else if (t == SLASH) { |
| |
| // Close tag </ |
| |
| t = x.nextToken(); |
| if (name == null) { |
| throw x.syntaxError("Mismatched close tag" + t); |
| } |
| if (!t.equals(name)) { |
| throw x.syntaxError("Mismatched " + name + " and " + t); |
| } |
| if (x.nextToken() != GT) { |
| throw x.syntaxError("Misshaped close tag"); |
| } |
| return true; |
| |
| } else if (t instanceof Character) { |
| throw x.syntaxError("Misshaped tag"); |
| |
| // Open tag < |
| |
| } else { |
| n = (String)t; |
| t = null; |
| o = new JSONObject(); |
| for (;;) { |
| if (t == null) { |
| t = x.nextToken(); |
| } |
| |
| // attribute = value |
| |
| if (t instanceof String) { |
| s = (String)t; |
| t = x.nextToken(); |
| if (t == EQ) { |
| t = x.nextToken(); |
| if (!(t instanceof String)) { |
| throw x.syntaxError("Missing value"); |
| } |
| o.accumulate(s, JSONObject.stringToValue((String)t)); |
| t = null; |
| } else { |
| o.accumulate(s, ""); |
| } |
| |
| // Empty tag <.../> |
| |
| } else if (t == SLASH) { |
| if (x.nextToken() != GT) { |
| throw x.syntaxError("Misshaped tag"); |
| } |
| context.accumulate(n, o); |
| return false; |
| |
| // Content, between <...> and </...> |
| |
| } else if (t == GT) { |
| for (;;) { |
| t = x.nextContent(); |
| if (t == null) { |
| if (n != null) { |
| throw x.syntaxError("Unclosed tag " + n); |
| } |
| return false; |
| } else if (t instanceof String) { |
| s = (String)t; |
| if (s.length() > 0) { |
| o.accumulate("content", JSONObject.stringToValue(s)); |
| } |
| |
| // Nested element |
| |
| } else if (t == LT) { |
| if (parse(x, o, n)) { |
| if (o.length() == 0) { |
| context.accumulate(n, ""); |
| } else if (o.length() == 1 && |
| o.opt("content") != null) { |
| context.accumulate(n, o.opt("content")); |
| } else { |
| context.accumulate(n, o); |
| } |
| return false; |
| } |
| } |
| } |
| } else { |
| throw x.syntaxError("Misshaped tag"); |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Convert a well-formed (but not necessarily valid) XML string into a |
| * JSONObject. Some information may be lost in this transformation |
| * because JSON is a data format and XML is a document format. XML uses |
| * elements, attributes, and content text, while JSON uses unordered |
| * collections of name/value pairs and arrays of values. JSON does not |
| * does not like to distinguish between elements and attributes. |
| * Sequences of similar elements are represented as JSONArrays. Content |
| * text may be placed in a "content" member. Comments, prologs, DTDs, and |
| * <code><[ [ ]]></code> are ignored. |
| * @param string The source string. |
| * @return A JSONObject containing the structured data from the XML string. |
| * @throws JSONException |
| */ |
| public static JSONObject toJSONObject(String string) throws JSONException { |
| JSONObject o = new JSONObject(); |
| XMLTokener x = new XMLTokener(string); |
| while (x.more() && x.skipPast("<")) { |
| parse(x, o, null); |
| } |
| return o; |
| } |
| |
| |
| /** |
| * Convert a JSONObject into a well-formed, element-normal XML string. |
| * @param o A JSONObject. |
| * @return A string. |
| * @throws JSONException |
| */ |
| public static String toString(Object o) throws JSONException { |
| return toString(o, null); |
| } |
| |
| |
| /** |
| * Convert a JSONObject into a well-formed, element-normal XML string. |
| * @param o A JSONObject. |
| * @param tagName The optional name of the enclosing tag. |
| * @return A string. |
| * @throws JSONException |
| */ |
| public static String toString(Object o, String tagName) |
| throws JSONException { |
| StringBuffer b = new StringBuffer(); |
| int i; |
| JSONArray ja; |
| JSONObject jo; |
| String k; |
| Iterator keys; |
| int len; |
| String s; |
| Object v; |
| if (o instanceof JSONObject) { |
| |
| // Emit <tagName> |
| |
| if (tagName != null) { |
| b.append('<'); |
| b.append(tagName); |
| b.append('>'); |
| } |
| |
| // Loop thru the keys. |
| |
| jo = (JSONObject)o; |
| keys = jo.keys(); |
| while (keys.hasNext()) { |
| k = keys.next().toString(); |
| v = jo.opt(k); |
| if (v == null) { |
| v = ""; |
| } |
| if (v instanceof String) { |
| s = (String)v; |
| } else { |
| s = null; |
| } |
| |
| // Emit content in body |
| |
| if (k.equals("content")) { |
| if (v instanceof JSONArray) { |
| ja = (JSONArray)v; |
| len = ja.length(); |
| for (i = 0; i < len; i += 1) { |
| if (i > 0) { |
| b.append('\n'); |
| } |
| b.append(escape(ja.get(i).toString())); |
| } |
| } else { |
| b.append(escape(v.toString())); |
| } |
| |
| // Emit an array of similar keys |
| |
| } else if (v instanceof JSONArray) { |
| ja = (JSONArray)v; |
| len = ja.length(); |
| for (i = 0; i < len; i += 1) { |
| v = ja.get(i); |
| if (v instanceof JSONArray) { |
| b.append('<'); |
| b.append(k); |
| b.append('>'); |
| b.append(toString(v)); |
| b.append("</"); |
| b.append(k); |
| b.append('>'); |
| } else { |
| b.append(toString(v, k)); |
| } |
| } |
| } else if (v.equals("")) { |
| b.append('<'); |
| b.append(k); |
| b.append("/>"); |
| |
| // Emit a new tag <k> |
| |
| } else { |
| b.append(toString(v, k)); |
| } |
| } |
| if (tagName != null) { |
| |
| // Emit the </tagname> close tag |
| |
| b.append("</"); |
| b.append(tagName); |
| b.append('>'); |
| } |
| return b.toString(); |
| |
| // XML does not have good support for arrays. If an array appears in a place |
| // where XML is lacking, synthesize an <array> element. |
| |
| } else if (o instanceof JSONArray) { |
| ja = (JSONArray)o; |
| len = ja.length(); |
| for (i = 0; i < len; ++i) { |
| v = ja.opt(i); |
| b.append(toString(v, (tagName == null) ? "array" : tagName)); |
| } |
| return b.toString(); |
| } else { |
| s = (o == null) ? "null" : escape(o.toString()); |
| return (tagName == null) ? "\"" + s + "\"" : |
| (s.length() == 0) ? "<" + tagName + "/>" : |
| "<" + tagName + ">" + s + "</" + tagName + ">"; |
| } |
| } |
| } |