1 package com.google.polo.json; 2 3 /* 4 Copyright (c) 2008 JSON.org 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy 7 of this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights 9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 copies of the Software, and to permit persons to whom the Software is 11 furnished to do so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 The Software shall be used for Good, not Evil. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 SOFTWARE. 25 */ 26 27 import java.util.Iterator; 28 29 30 /** 31 * This provides static methods to convert an XML text into a JSONArray or 32 * JSONObject, and to covert a JSONArray or JSONObject into an XML text using 33 * the JsonML transform. 34 * @author JSON.org 35 * @version 2008-11-20 36 */ 37 public class JSONML { 38 39 /** 40 * Parse XML values and store them in a JSONArray. 41 * @param x The XMLTokener containing the source string. 42 * @param arrayForm true if array form, false if object form. 43 * @param ja The JSONArray that is containing the current tag or null 44 * if we are at the outermost level. 45 * @return A JSONArray if the value is the outermost tag, otherwise null. 46 * @throws JSONException 47 */ parse(XMLTokener x, boolean arrayForm, JSONArray ja)48 private static Object parse(XMLTokener x, boolean arrayForm, 49 JSONArray ja) throws JSONException { 50 String attribute; 51 char c; 52 String closeTag = null; 53 int i; 54 JSONArray newja = null; 55 JSONObject newjo = null; 56 Object token; 57 String tagName = null; 58 59 // Test for and skip past these forms: 60 // <!-- ... --> 61 // <![ ... ]]> 62 // <! ... > 63 // <? ... ?> 64 65 while (true) { 66 token = x.nextContent(); 67 if (token == XML.LT) { 68 token = x.nextToken(); 69 if (token instanceof Character) { 70 if (token == XML.SLASH) { 71 72 // Close tag </ 73 74 token = x.nextToken(); 75 if (!(token instanceof String)) { 76 throw new JSONException( 77 "Expected a closing name instead of '" + 78 token + "'."); 79 } 80 if (x.nextToken() != XML.GT) { 81 throw x.syntaxError("Misshaped close tag"); 82 } 83 return token; 84 } else if (token == XML.BANG) { 85 86 // <! 87 88 c = x.next(); 89 if (c == '-') { 90 if (x.next() == '-') { 91 x.skipPast("-->"); 92 } 93 x.back(); 94 } else if (c == '[') { 95 token = x.nextToken(); 96 if (token.equals("CDATA") && x.next() == '[') { 97 if (ja != null) { 98 ja.put(x.nextCDATA()); 99 } 100 } else { 101 throw x.syntaxError("Expected 'CDATA['"); 102 } 103 } else { 104 i = 1; 105 do { 106 token = x.nextMeta(); 107 if (token == null) { 108 throw x.syntaxError("Missing '>' after '<!'."); 109 } else if (token == XML.LT) { 110 i += 1; 111 } else if (token == XML.GT) { 112 i -= 1; 113 } 114 } while (i > 0); 115 } 116 } else if (token == XML.QUEST) { 117 118 // <? 119 120 x.skipPast("?>"); 121 } else { 122 throw x.syntaxError("Misshaped tag"); 123 } 124 125 // Open tag < 126 127 } else { 128 if (!(token instanceof String)) { 129 throw x.syntaxError("Bad tagName '" + token + "'."); 130 } 131 tagName = (String)token; 132 newja = new JSONArray(); 133 newjo = new JSONObject(); 134 if (arrayForm) { 135 newja.put(tagName); 136 if (ja != null) { 137 ja.put(newja); 138 } 139 } else { 140 newjo.put("tagName", tagName); 141 if (ja != null) { 142 ja.put(newjo); 143 } 144 } 145 token = null; 146 for (;;) { 147 if (token == null) { 148 token = x.nextToken(); 149 } 150 if (token == null) { 151 throw x.syntaxError("Misshaped tag"); 152 } 153 if (!(token instanceof String)) { 154 break; 155 } 156 157 // attribute = value 158 159 attribute = (String)token; 160 if (!arrayForm && (attribute == "tagName" || attribute == "childNode")) { 161 throw x.syntaxError("Reserved attribute."); 162 } 163 token = x.nextToken(); 164 if (token == XML.EQ) { 165 token = x.nextToken(); 166 if (!(token instanceof String)) { 167 throw x.syntaxError("Missing value"); 168 } 169 newjo.accumulate(attribute, JSONObject.stringToValue((String)token)); 170 token = null; 171 } else { 172 newjo.accumulate(attribute, ""); 173 } 174 } 175 if (arrayForm && newjo.length() > 0) { 176 newja.put(newjo); 177 } 178 179 // Empty tag <.../> 180 181 if (token == XML.SLASH) { 182 if (x.nextToken() != XML.GT) { 183 throw x.syntaxError("Misshaped tag"); 184 } 185 if (ja == null) { 186 if (arrayForm) { 187 return newja; 188 } else { 189 return newjo; 190 } 191 } 192 193 // Content, between <...> and </...> 194 195 } else { 196 if (token != XML.GT) { 197 throw x.syntaxError("Misshaped tag"); 198 } 199 closeTag = (String)parse(x, arrayForm, newja); 200 if (closeTag != null) { 201 if (!closeTag.equals(tagName)) { 202 throw x.syntaxError("Mismatched '" + tagName + 203 "' and '" + closeTag + "'"); 204 } 205 tagName = null; 206 if (!arrayForm && newja.length() > 0) { 207 newjo.put("childNodes", newja); 208 } 209 if (ja == null) { 210 if (arrayForm) { 211 return newja; 212 } else { 213 return newjo; 214 } 215 } 216 } 217 } 218 } 219 } else { 220 if (ja != null) { 221 ja.put(token instanceof String ? 222 JSONObject.stringToValue((String)token) : token); 223 } 224 } 225 } 226 } 227 228 229 /** 230 * Convert a well-formed (but not necessarily valid) XML string into a 231 * JSONArray using the JsonML transform. Each XML tag is represented as 232 * a JSONArray in which the first element is the tag name. If the tag has 233 * attributes, then the second element will be JSONObject containing the 234 * name/value pairs. If the tag contains children, then strings and 235 * JSONArrays will represent the child tags. 236 * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored. 237 * @param string The source string. 238 * @return A JSONArray containing the structured data from the XML string. 239 * @throws JSONException 240 */ toJSONArray(String string)241 public static JSONArray toJSONArray(String string) throws JSONException { 242 return toJSONArray(new XMLTokener(string)); 243 } 244 245 246 /** 247 * Convert a well-formed (but not necessarily valid) XML string into a 248 * JSONArray using the JsonML transform. Each XML tag is represented as 249 * a JSONArray in which the first element is the tag name. If the tag has 250 * attributes, then the second element will be JSONObject containing the 251 * name/value pairs. If the tag contains children, then strings and 252 * JSONArrays will represent the child content and tags. 253 * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored. 254 * @param x An XMLTokener. 255 * @return A JSONArray containing the structured data from the XML string. 256 * @throws JSONException 257 */ toJSONArray(XMLTokener x)258 public static JSONArray toJSONArray(XMLTokener x) throws JSONException { 259 return (JSONArray)parse(x, true, null); 260 } 261 262 263 264 /** 265 * Convert a well-formed (but not necessarily valid) XML string into a 266 * JSONObject using the JsonML transform. Each XML tag is represented as 267 * a JSONObject with a "tagName" property. If the tag has attributes, then 268 * the attributes will be in the JSONObject as properties. If the tag 269 * contains children, the object will have a "childNodes" property which 270 * will be an array of strings and JsonML JSONObjects. 271 272 * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored. 273 * @param x An XMLTokener of the XML source text. 274 * @return A JSONObject containing the structured data from the XML string. 275 * @throws JSONException 276 */ toJSONObject(XMLTokener x)277 public static JSONObject toJSONObject(XMLTokener x) throws JSONException { 278 return (JSONObject)parse(x, false, null); 279 } 280 /** 281 * Convert a well-formed (but not necessarily valid) XML string into a 282 * JSONObject using the JsonML transform. Each XML tag is represented as 283 * a JSONObject with a "tagName" property. If the tag has attributes, then 284 * the attributes will be in the JSONObject as properties. If the tag 285 * contains children, the object will have a "childNodes" property which 286 * will be an array of strings and JsonML JSONObjects. 287 288 * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored. 289 * @param string The XML source text. 290 * @return A JSONObject containing the structured data from the XML string. 291 * @throws JSONException 292 */ toJSONObject(String string)293 public static JSONObject toJSONObject(String string) throws JSONException { 294 return toJSONObject(new XMLTokener(string)); 295 } 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 * @throws JSONException 303 */ toString(JSONArray ja)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 * @throws JSONException 385 */ toString(JSONObject jo)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 }