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