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 * This provides static methods to convert an XML text into a JSONArray or 031 * JSONObject, and to covert a JSONArray or JSONObject into an XML text using 032 * the JsonML transform. 033 * @author JSON.org 034@version $Id$ 035@since Ptolemy II 10.0 036 * @version 2010-02-12 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 * @exception JSONException 048 */ 049 private static Object parse(XMLTokener x, boolean arrayForm, JSONArray ja) 050 throws JSONException { 051 String attribute; 052 char c; 053 String closeTag = null; 054 int i; 055 JSONArray newja = null; 056 JSONObject newjo = null; 057 Object token; 058 String tagName = null; 059 060 // Test for and skip past these forms: 061 // <!-- ... --> 062 // <![ ... ]]> 063 // <! ... > 064 // <? ... ?> 065 066 while (true) { 067 token = x.nextContent(); 068 if (token == XML.LT) { 069 token = x.nextToken(); 070 if (token instanceof Character) { 071 if (token == XML.SLASH) { 072 073 // Close tag </ 074 075 token = x.nextToken(); 076 if (!(token instanceof String)) { 077 throw new JSONException( 078 "Expected a closing name instead of '" 079 + token + "'."); 080 } 081 if (x.nextToken() != XML.GT) { 082 throw x.syntaxError("Misshaped close tag"); 083 } 084 return token; 085 } else if (token == XML.BANG) { 086 087 // <! 088 089 c = x.next(); 090 if (c == '-') { 091 if (x.next() == '-') { 092 x.skipPast("-->"); 093 } 094 x.back(); 095 } else if (c == '[') { 096 token = x.nextToken(); 097 if (token.equals("CDATA") && x.next() == '[') { 098 if (ja != null) { 099 ja.put(x.nextCDATA()); 100 } 101 } else { 102 throw x.syntaxError("Expected 'CDATA['"); 103 } 104 } else { 105 i = 1; 106 do { 107 token = x.nextMeta(); 108 if (token == null) { 109 throw x.syntaxError( 110 "Missing '>' after '<!'."); 111 } else if (token == XML.LT) { 112 i += 1; 113 } else if (token == XML.GT) { 114 i -= 1; 115 } 116 } while (i > 0); 117 } 118 } else if (token == XML.QUEST) { 119 120 // <? 121 122 x.skipPast("?>"); 123 } else { 124 throw x.syntaxError("Misshaped tag"); 125 } 126 127 // Open tag < 128 129 } else { 130 if (!(token instanceof String)) { 131 throw x.syntaxError("Bad tagName '" + token + "'."); 132 } 133 tagName = (String) token; 134 newja = new JSONArray(); 135 newjo = new JSONObject(); 136 if (arrayForm) { 137 newja.put(tagName); 138 if (ja != null) { 139 ja.put(newja); 140 } 141 } else { 142 newjo.put("tagName", tagName); 143 if (ja != null) { 144 ja.put(newjo); 145 } 146 } 147 token = null; 148 for (;;) { 149 if (token == null) { 150 token = x.nextToken(); 151 } 152 if (token == null) { 153 throw x.syntaxError("Misshaped tag"); 154 } 155 if (!(token instanceof String)) { 156 break; 157 } 158 159 // attribute = value 160 161 attribute = (String) token; 162 if (!arrayForm && (attribute == "tagName" 163 || attribute == "childNode")) { 164 throw x.syntaxError("Reserved attribute."); 165 } 166 token = x.nextToken(); 167 if (token == XML.EQ) { 168 token = x.nextToken(); 169 if (!(token instanceof String)) { 170 throw x.syntaxError("Missing value"); 171 } 172 newjo.accumulate(attribute, 173 JSONObject.stringToValue((String) token)); 174 token = null; 175 } else { 176 newjo.accumulate(attribute, ""); 177 } 178 } 179 if (arrayForm && newjo.length() > 0) { 180 newja.put(newjo); 181 } 182 183 // Empty tag <.../> 184 185 if (token == XML.SLASH) { 186 if (x.nextToken() != XML.GT) { 187 throw x.syntaxError("Misshaped tag"); 188 } 189 if (ja == null) { 190 if (arrayForm) { 191 return newja; 192 } else { 193 return newjo; 194 } 195 } 196 197 // Content, between <...> and </...> 198 199 } else { 200 if (token != XML.GT) { 201 throw x.syntaxError("Misshaped tag"); 202 } 203 closeTag = (String) parse(x, arrayForm, newja); 204 if (closeTag != null) { 205 if (!closeTag.equals(tagName)) { 206 throw x.syntaxError("Mismatched '" + tagName 207 + "' and '" + closeTag + "'"); 208 } 209 tagName = null; 210 if (!arrayForm && newja.length() > 0) { 211 newjo.put("childNodes", newja); 212 } 213 if (ja == null) { 214 if (arrayForm) { 215 return newja; 216 } else { 217 return newjo; 218 } 219 } 220 } 221 } 222 } 223 } else { 224 if (ja != null) { 225 ja.put(token instanceof String 226 ? JSONObject.stringToValue((String) token) 227 : token); 228 } 229 } 230 } 231 } 232 233 /** 234 * Convert a well-formed (but not necessarily valid) XML string into a 235 * JSONArray using the JsonML transform. Each XML tag is represented as 236 * a JSONArray in which the first element is the tag name. If the tag has 237 * attributes, then the second element will be JSONObject containing the 238 * name/value pairs. If the tag contains children, then strings and 239 * JSONArrays will represent the child tags. 240 * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored. 241 * @param string The source string. 242 * @return A JSONArray containing the structured data from the XML string. 243 * @exception JSONException 244 */ 245 public static JSONArray toJSONArray(String string) throws JSONException { 246 return toJSONArray(new XMLTokener(string)); 247 } 248 249 /** 250 * Convert a well-formed (but not necessarily valid) XML string into a 251 * JSONArray using the JsonML transform. Each XML tag is represented as 252 * a JSONArray in which the first element is the tag name. If the tag has 253 * attributes, then the second element will be JSONObject containing the 254 * name/value pairs. If the tag contains children, then strings and 255 * JSONArrays will represent the child content and tags. 256 * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored. 257 * @param x An XMLTokener. 258 * @return A JSONArray containing the structured data from the XML string. 259 * @exception JSONException 260 */ 261 public static JSONArray toJSONArray(XMLTokener x) throws JSONException { 262 return (JSONArray) parse(x, true, null); 263 } 264 265 /** 266 * Convert a well-formed (but not necessarily valid) XML string into a 267 * JSONObject using the JsonML transform. Each XML tag is represented as 268 * a JSONObject with a "tagName" property. If the tag has attributes, then 269 * the attributes will be in the JSONObject as properties. If the tag 270 * contains children, the object will have a "childNodes" property which 271 * will be an array of strings and JsonML JSONObjects. 272 273 * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored. 274 * @param x An XMLTokener of the XML source text. 275 * @return A JSONObject containing the structured data from the XML string. 276 * @exception JSONException 277 */ 278 public static JSONObject toJSONObject(XMLTokener x) throws JSONException { 279 return (JSONObject) parse(x, false, null); 280 } 281 282 /** 283 * Convert a well-formed (but not necessarily valid) XML string into a 284 * JSONObject using the JsonML transform. Each XML tag is represented as 285 * a JSONObject with a "tagName" property. If the tag has attributes, then 286 * the attributes will be in the JSONObject as properties. If the tag 287 * contains children, the object will have a "childNodes" property which 288 * will be an array of strings and JsonML JSONObjects. 289 * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored. 290 * @param string The XML source text. 291 * @return A JSONObject containing the structured data from the XML string. 292 * @exception JSONException 293 */ 294 public static JSONObject toJSONObject(String string) throws JSONException { 295 return toJSONObject(new XMLTokener(string)); 296 } 297 298 /** 299 * Reverse the JSONML transformation, making an XML text from a JSONArray. 300 * @param ja A JSONArray. 301 * @return An XML string. 302 * @exception JSONException 303 */ 304 public static String toString(JSONArray ja) throws JSONException { 305 Object e; 306 int i; 307 JSONObject jo; 308 String k; 309 Iterator keys; 310 int length; 311 StringBuffer sb = new StringBuffer(); 312 String tagName; 313 String v; 314 315 // Emit <tagName 316 317 tagName = ja.getString(0); 318 XML.noSpace(tagName); 319 tagName = XML.escape(tagName); 320 sb.append('<'); 321 sb.append(tagName); 322 323 e = ja.opt(1); 324 if (e instanceof JSONObject) { 325 i = 2; 326 jo = (JSONObject) e; 327 328 // Emit the attributes 329 330 keys = jo.keys(); 331 while (keys.hasNext()) { 332 k = keys.next().toString(); 333 XML.noSpace(k); 334 v = jo.optString(k); 335 if (v != null) { 336 sb.append(' '); 337 sb.append(XML.escape(k)); 338 sb.append('='); 339 sb.append('"'); 340 sb.append(XML.escape(v)); 341 sb.append('"'); 342 } 343 } 344 } else { 345 i = 1; 346 } 347 348 //Emit content in body 349 350 length = ja.length(); 351 if (i >= length) { 352 sb.append('/'); 353 sb.append('>'); 354 } else { 355 sb.append('>'); 356 do { 357 e = ja.get(i); 358 i += 1; 359 if (e != null) { 360 if (e instanceof String) { 361 sb.append(XML.escape(e.toString())); 362 } else if (e instanceof JSONObject) { 363 sb.append(toString((JSONObject) e)); 364 } else if (e instanceof JSONArray) { 365 sb.append(toString((JSONArray) e)); 366 } 367 } 368 } while (i < length); 369 sb.append('<'); 370 sb.append('/'); 371 sb.append(tagName); 372 sb.append('>'); 373 } 374 return sb.toString(); 375 } 376 377 /** 378 * Reverse the JSONML transformation, making an XML text from a JSONObject. 379 * The JSONObject must contain a "tagName" property. If it has children, 380 * then it must have a "childNodes" property containing an array of objects. 381 * The other properties are attributes with string values. 382 * @param jo A JSONObject. 383 * @return An XML string. 384 * @exception JSONException 385 */ 386 public static String toString(JSONObject jo) throws JSONException { 387 StringBuffer sb = new StringBuffer(); 388 Object e; 389 int i; 390 JSONArray ja; 391 String k; 392 Iterator keys; 393 int len; 394 String tagName; 395 String v; 396 397 //Emit <tagName 398 399 tagName = jo.optString("tagName"); 400 if (tagName == null) { 401 return XML.escape(jo.toString()); 402 } 403 XML.noSpace(tagName); 404 tagName = XML.escape(tagName); 405 sb.append('<'); 406 sb.append(tagName); 407 408 //Emit the attributes 409 410 keys = jo.keys(); 411 while (keys.hasNext()) { 412 k = keys.next().toString(); 413 if (!k.equals("tagName") && !k.equals("childNodes")) { 414 XML.noSpace(k); 415 v = jo.optString(k); 416 if (v != null) { 417 sb.append(' '); 418 sb.append(XML.escape(k)); 419 sb.append('='); 420 sb.append('"'); 421 sb.append(XML.escape(v)); 422 sb.append('"'); 423 } 424 } 425 } 426 427 //Emit content in body 428 429 ja = jo.optJSONArray("childNodes"); 430 if (ja == null) { 431 sb.append('/'); 432 sb.append('>'); 433 } else { 434 sb.append('>'); 435 len = ja.length(); 436 for (i = 0; i < len; i += 1) { 437 e = ja.get(i); 438 if (e != null) { 439 if (e instanceof String) { 440 sb.append(XML.escape(e.toString())); 441 } else if (e instanceof JSONObject) { 442 sb.append(toString((JSONObject) e)); 443 } else if (e instanceof JSONArray) { 444 sb.append(toString((JSONArray) e)); 445 } 446 } 447 } 448 sb.append('<'); 449 sb.append('/'); 450 sb.append(tagName); 451 sb.append('>'); 452 } 453 return sb.toString(); 454 } 455}