package org.json; /******************************************************************************* * Copyright (c) 2008 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 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 JSONArray or * JSONObject, and to covert a JSONArray or JSONObject into an XML text using * the JsonML transform. * @author JSON.org * @version 2008-11-20 */ public class JSONML { /** * Parse XML values and store them in a JSONArray. * @param x The XMLTokener containing the source string. * @param arrayForm true if array form, false if object form. * @param ja The JSONArray that is containing the current tag or null * if we are at the outermost level. * @return A JSONArray if the value is the outermost tag, otherwise null. * @throws JSONException */ private static Object parse(XMLTokener x, boolean arrayForm, JSONArray ja) throws JSONException { String attribute; char c; String closeTag = null; int i; JSONArray newja = null; JSONObject newjo = null; Object token; String tagName = null; // Test for and skip past these forms: // <!-- ... --> // <![ ... ]]> // <! ... > // <? ... ?> while (true) { token = x.nextContent(); if (token == XML.LT) { token = x.nextToken(); if (token instanceof Character) { if (token == XML.SLASH) { // Close tag </ token = x.nextToken(); if (!(token instanceof String)) { throw new JSONException( "Expected a closing name instead of '" + token + "'."); } if (x.nextToken() != XML.GT) { throw x.syntaxError("Misshaped close tag"); } return token; } else if (token == XML.BANG) { // <! c = x.next(); if (c == '-') { if (x.next() == '-') { x.skipPast("-->"); } x.back(); } else if (c == '[') { token = x.nextToken(); if (token.equals("CDATA") && x.next() == '[') { if (ja != null) { ja.put(x.nextCDATA()); } } else { throw x.syntaxError("Expected 'CDATA['"); } } else { i = 1; do { token = x.nextMeta(); if (token == null) { throw x.syntaxError("Missing '>' after '<!'."); } else if (token == XML.LT) { i += 1; } else if (token == XML.GT) { i -= 1; } } while (i > 0); } } else if (token == XML.QUEST) { // <? x.skipPast("?>"); } else { throw x.syntaxError("Misshaped tag"); } // Open tag < } else { if (!(token instanceof String)) { throw x.syntaxError("Bad tagName '" + token + "'."); } tagName = (String)token; newja = new JSONArray(); newjo = new JSONObject(); if (arrayForm) { newja.put(tagName); if (ja != null) { ja.put(newja); } } else { newjo.put("tagName", tagName); if (ja != null) { ja.put(newjo); } } token = null; for (;;) { if (token == null) { token = x.nextToken(); } if (token == null) { throw x.syntaxError("Misshaped tag"); } if (!(token instanceof String)) { break; } // attribute = value attribute = (String)token; if (!arrayForm && (attribute == "tagName" || attribute == "childNode")) { throw x.syntaxError("Reserved attribute."); } token = x.nextToken(); if (token == XML.EQ) { token = x.nextToken(); if (!(token instanceof String)) { throw x.syntaxError("Missing value"); } newjo.accumulate(attribute, JSONObject.stringToValue((String)token)); token = null; } else { newjo.accumulate(attribute, ""); } } if (arrayForm && newjo.length() > 0) { newja.put(newjo); } // Empty tag <.../> if (token == XML.SLASH) { if (x.nextToken() != XML.GT) { throw x.syntaxError("Misshaped tag"); } if (ja == null) { if (arrayForm) { return newja; } else { return newjo; } } // Content, between <...> and </...> } else { if (token != XML.GT) { throw x.syntaxError("Misshaped tag"); } closeTag = (String)parse(x, arrayForm, newja); if (closeTag != null) { if (!closeTag.equals(tagName)) { throw x.syntaxError("Mismatched '" + tagName + "' and '" + closeTag + "'"); } tagName = null; if (!arrayForm && newja.length() > 0) { newjo.put("childNodes", newja); } if (ja == null) { if (arrayForm) { return newja; } else { return newjo; } } } } } } else { if (ja != null) { ja.put(token instanceof String ? JSONObject.stringToValue((String)token) : token); } } } } /** * Convert a well-formed (but not necessarily valid) XML string into a * JSONArray using the JsonML transform. Each XML tag is represented as * a JSONArray in which the first element is the tag name. If the tag has * attributes, then the second element will be JSONObject containing the * name/value pairs. If the tag contains children, then strings and * JSONArrays will represent the child tags. * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored. * @param string The source string. * @return A JSONArray containing the structured data from the XML string. * @throws JSONException */ public static JSONArray toJSONArray(String string) throws JSONException { return toJSONArray(new XMLTokener(string)); } /** * Convert a well-formed (but not necessarily valid) XML string into a * JSONArray using the JsonML transform. Each XML tag is represented as * a JSONArray in which the first element is the tag name. If the tag has * attributes, then the second element will be JSONObject containing the * name/value pairs. If the tag contains children, then strings and * JSONArrays will represent the child content and tags. * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored. * @param x An XMLTokener. * @return A JSONArray containing the structured data from the XML string. * @throws JSONException */ public static JSONArray toJSONArray(XMLTokener x) throws JSONException { return (JSONArray)parse(x, true, null); } /** * Convert a well-formed (but not necessarily valid) XML string into a * JSONObject using the JsonML transform. Each XML tag is represented as * a JSONObject with a "tagName" property. If the tag has attributes, then * the attributes will be in the JSONObject as properties. If the tag * contains children, the object will have a "childNodes" property which * will be an array of strings and JsonML JSONObjects. * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored. * @param x An XMLTokener of the XML source text. * @return A JSONObject containing the structured data from the XML string. * @throws JSONException */ public static JSONObject toJSONObject(XMLTokener x) throws JSONException { return (JSONObject)parse(x, false, null); } /** * Convert a well-formed (but not necessarily valid) XML string into a * JSONObject using the JsonML transform. Each XML tag is represented as * a JSONObject with a "tagName" property. If the tag has attributes, then * the attributes will be in the JSONObject as properties. If the tag * contains children, the object will have a "childNodes" property which * will be an array of strings and JsonML JSONObjects. * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored. * @param string The XML source text. * @return A JSONObject containing the structured data from the XML string. * @throws JSONException */ public static JSONObject toJSONObject(String string) throws JSONException { return toJSONObject(new XMLTokener(string)); } /** * Reverse the JSONML transformation, making an XML text from a JSONArray. * @param ja A JSONArray. * @return An XML string. * @throws JSONException */ public static String toString(JSONArray ja) throws JSONException { Object e; int i; JSONObject jo; String k; Iterator keys; int length; StringBuffer sb = new StringBuffer(); String tagName; String v; // Emit <tagName tagName = ja.getString(0); XML.noSpace(tagName); tagName = XML.escape(tagName); sb.append('<'); sb.append(tagName); e = ja.opt(1); if (e instanceof JSONObject) { i = 2; jo = (JSONObject)e; // Emit the attributes keys = jo.keys(); while (keys.hasNext()) { k = keys.next().toString(); XML.noSpace(k); v = jo.optString(k); if (v != null) { sb.append(' '); sb.append(XML.escape(k)); sb.append('='); sb.append('"'); sb.append(XML.escape(v)); sb.append('"'); } } } else { i = 1; } //Emit content in body length = ja.length(); if (i >= length) { sb.append('/'); sb.append('>'); } else { sb.append('>'); do { e = ja.get(i); i += 1; if (e != null) { if (e instanceof String) { sb.append(XML.escape(e.toString())); } else if (e instanceof JSONObject) { sb.append(toString((JSONObject)e)); } else if (e instanceof JSONArray) { sb.append(toString((JSONArray)e)); } } } while (i < length); sb.append('<'); sb.append('/'); sb.append(tagName); sb.append('>'); } return sb.toString(); } /** * Reverse the JSONML transformation, making an XML text from a JSONObject. * The JSONObject must contain a "tagName" property. If it has children, * then it must have a "childNodes" property containing an array of objects. * The other properties are attributes with string values. * @param jo A JSONObject. * @return An XML string. * @throws JSONException */ public static String toString(JSONObject jo) throws JSONException { StringBuffer sb = new StringBuffer(); Object e; int i; JSONArray ja; String k; Iterator keys; int len; String tagName; String v; //Emit <tagName tagName = jo.optString("tagName"); if (tagName == null) { return XML.escape(jo.toString()); } XML.noSpace(tagName); tagName = XML.escape(tagName); sb.append('<'); sb.append(tagName); //Emit the attributes keys = jo.keys(); while (keys.hasNext()) { k = keys.next().toString(); if (!k.equals("tagName") && !k.equals("childNodes")) { XML.noSpace(k); v = jo.optString(k); if (v != null) { sb.append(' '); sb.append(XML.escape(k)); sb.append('='); sb.append('"'); sb.append(XML.escape(v)); sb.append('"'); } } } //Emit content in body ja = jo.optJSONArray("childNodes"); if (ja == null) { sb.append('/'); sb.append('>'); } else { sb.append('>'); len = ja.length(); for (i = 0; i < len; i += 1) { e = ja.get(i); if (e != null) { if (e instanceof String) { sb.append(XML.escape(e.toString())); } else if (e instanceof JSONObject) { sb.append(toString((JSONObject)e)); } else if (e instanceof JSONArray) { sb.append(toString((JSONArray)e)); } } } sb.append('<'); sb.append('/'); sb.append(tagName); sb.append('>'); } return sb.toString(); } }