You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by mg...@apache.org on 2012/05/18 13:39:32 UTC

[3/7] Update JSON2 classes

http://git-wip-us.apache.org/repos/asf/wicket/blob/0c01d793/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONML.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONML.java b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONML.java
index f58f8e4..3fcdcea 100755
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONML.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONML.java
@@ -1,576 +1,465 @@
 package org.apache.wicket.ajax.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 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.
- */
+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 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 JSONArray or JSONObject, and to covert
- * a JSONArray or JSONObject into an XML text using the JsonML transform.
- * 
+ * 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 2011-10-05
+ * @version 2011-11-24
  */
-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;
+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)
-		{
-			if (!x.more())
-			{
-				throw x.syntaxError("Bad XML");
-			}
-			token = x.nextContent();
-			if (token == XML.LT)
-			{
-				token = x.nextToken();
-				if (token instanceof Character)
-				{
-					if (token == XML.SLASH)
-					{
+//      <!-- ... -->
+//      <![  ... ]]>
+//      <!   ...   >
+//      <?   ...  ?>
+
+        while (true) {
+            if (!x.more()) {
+                throw x.syntaxError("Bad XML");
+            }
+            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)
-					{
+                        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)
-					{
+                        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");
-					}
+                        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;
-						}
+                } 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, XML.stringToValue((String)token));
-							token = null;
-						}
-						else
-						{
-							newjo.accumulate(attribute, "");
-						}
-					}
-					if (arrayForm && newjo.length() > 0)
-					{
-						newja.put(newjo);
-					}
+                        attribute = (String)token;
+                        if (!arrayForm && ("tagName".equals(attribute) || "childNode".equals(attribute))) {
+                            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, XML.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;
-							}
-						}
+                    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 ? XML.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>&lt;[ [ ]]></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>&lt;[ [ ]]></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>&lt;[ [ ]]></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>&lt;[ [ ]]></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
-	{
-		int i;
-		JSONObject jo;
-		String key;
-		Iterator keys;
-		int length;
-		Object object;
-		StringBuffer sb = new StringBuffer();
-		String tagName;
-		String value;
+                    } 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
+                        ? XML.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>&lt;[ [ ]]></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>&lt;[ [ ]]></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>&lt;[ [ ]]></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>&lt;[ [ ]]></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 {
+        int             i;
+        JSONObject   jo;
+        String       key;
+        Iterator     keys;
+        int             length;
+        Object         object;
+        StringBuffer sb = new StringBuffer();
+        String       tagName;
+        String       value;
 
 // Emit <tagName
 
-		tagName = ja.getString(0);
-		XML.noSpace(tagName);
-		tagName = XML.escape(tagName);
-		sb.append('<');
-		sb.append(tagName);
-
-		object = ja.opt(1);
-		if (object instanceof JSONObject)
-		{
-			i = 2;
-			jo = (JSONObject)object;
-
-// Emit the attributes
-
-			keys = jo.keys();
-			while (keys.hasNext())
-			{
-				key = keys.next().toString();
-				XML.noSpace(key);
-				value = jo.optString(key);
-				if (value != null)
-				{
-					sb.append(' ');
-					sb.append(XML.escape(key));
-					sb.append('=');
-					sb.append('"');
-					sb.append(XML.escape(value));
-					sb.append('"');
-				}
-			}
-		}
-		else
-		{
-			i = 1;
-		}
-
-// Emit content in body
-
-		length = ja.length();
-		if (i >= length)
-		{
-			sb.append('/');
-			sb.append('>');
-		}
-		else
-		{
-			sb.append('>');
-			do
-			{
-				object = ja.get(i);
-				i += 1;
-				if (object != null)
-				{
-					if (object instanceof String)
-					{
-						sb.append(XML.escape(object.toString()));
-					}
-					else if (object instanceof JSONObject)
-					{
-						sb.append(toString((JSONObject)object));
-					}
-					else if (object instanceof JSONArray)
-					{
-						sb.append(toString((JSONArray)object));
-					}
-				}
-			}
-			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();
-		int i;
-		JSONArray ja;
-		String key;
-		Iterator keys;
-		int length;
-		Object object;
-		String tagName;
-		String value;
-
-// Emit <tagName
+        tagName = ja.getString(0);
+        XML.noSpace(tagName);
+        tagName = XML.escape(tagName);
+        sb.append('<');
+        sb.append(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);
+        object = ja.opt(1);
+        if (object instanceof JSONObject) {
+            i = 2;
+            jo = (JSONObject)object;
 
 // Emit the attributes
 
-		keys = jo.keys();
-		while (keys.hasNext())
-		{
-			key = keys.next().toString();
-			if (!key.equals("tagName") && !key.equals("childNodes"))
-			{
-				XML.noSpace(key);
-				value = jo.optString(key);
-				if (value != null)
-				{
-					sb.append(' ');
-					sb.append(XML.escape(key));
-					sb.append('=');
-					sb.append('"');
-					sb.append(XML.escape(value));
-					sb.append('"');
-				}
-			}
-		}
-
-// Emit content in body
-
-		ja = jo.optJSONArray("childNodes");
-		if (ja == null)
-		{
-			sb.append('/');
-			sb.append('>');
-		}
-		else
-		{
-			sb.append('>');
-			length = ja.length();
-			for (i = 0; i < length; i += 1)
-			{
-				object = ja.get(i);
-				if (object != null)
-				{
-					if (object instanceof String)
-					{
-						sb.append(XML.escape(object.toString()));
-					}
-					else if (object instanceof JSONObject)
-					{
-						sb.append(toString((JSONObject)object));
-					}
-					else if (object instanceof JSONArray)
-					{
-						sb.append(toString((JSONArray)object));
-					}
-					else
-					{
-						sb.append(object.toString());
-					}
-				}
-			}
-			sb.append('<');
-			sb.append('/');
-			sb.append(tagName);
-			sb.append('>');
-		}
-		return sb.toString();
-	}
-}
\ No newline at end of file
+            keys = jo.keys();
+            while (keys.hasNext()) {
+                key = keys.next().toString();
+                XML.noSpace(key);
+                value = jo.optString(key);
+                if (value != null) {
+                    sb.append(' ');
+                    sb.append(XML.escape(key));
+                    sb.append('=');
+                    sb.append('"');
+                    sb.append(XML.escape(value));
+                    sb.append('"');
+                }
+            }
+        } else {
+            i = 1;
+        }
+
+//Emit content in body
+
+        length = ja.length();
+        if (i >= length) {
+            sb.append('/');
+            sb.append('>');
+        } else {
+            sb.append('>');
+            do {
+                object = ja.get(i);
+                i += 1;
+                if (object != null) {
+                    if (object instanceof String) {
+                        sb.append(XML.escape(object.toString()));
+                    } else if (object instanceof JSONObject) {
+                        sb.append(toString((JSONObject)object));
+                    } else if (object instanceof JSONArray) {
+                        sb.append(toString((JSONArray)object));
+                    }
+                }
+            } 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();
+        int          i;
+        JSONArray    ja;
+        String       key;
+        Iterator     keys;
+        int          length;
+        Object         object;
+        String       tagName;
+        String       value;
+
+//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()) {
+            key = keys.next().toString();
+            if (!"tagName".equals(key) && !"childNodes".equals(key)) {
+                XML.noSpace(key);
+                value = jo.optString(key);
+                if (value != null) {
+                    sb.append(' ');
+                    sb.append(XML.escape(key));
+                    sb.append('=');
+                    sb.append('"');
+                    sb.append(XML.escape(value));
+                    sb.append('"');
+                }
+            }
+        }
+
+//Emit content in body
+
+        ja = jo.optJSONArray("childNodes");
+        if (ja == null) {
+            sb.append('/');
+            sb.append('>');
+        } else {
+            sb.append('>');
+            length = ja.length();
+            for (i = 0; i < length; i += 1) {
+                object = ja.get(i);
+                if (object != null) {
+                    if (object instanceof String) {
+                        sb.append(XML.escape(object.toString()));
+                    } else if (object instanceof JSONObject) {
+                        sb.append(toString((JSONObject)object));
+                    } else if (object instanceof JSONArray) {
+                        sb.append(toString((JSONArray)object));
+                    } else {
+                        sb.append(object.toString());
+                    }
+                }
+            }
+            sb.append('<');
+            sb.append('/');
+            sb.append(tagName);
+            sb.append('>');
+        }
+        return sb.toString();
+    }
+}