001package org.json; 002 003/* 004Copyright (c) 2015 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 JSONObject, and to 031 * covert a JSONObject into an XML text. 032 * 033 * @author JSON.org 034 * @version 2016-01-30 035 */ 036@SuppressWarnings("boxing") 037public class XML { 038 039 /** The Character '&'. */ 040 public static final Character AMP = '&'; 041 042 /** The Character '''. */ 043 public static final Character APOS = '\''; 044 045 /** The Character '!'. */ 046 public static final Character BANG = '!'; 047 048 /** The Character '='. */ 049 public static final Character EQ = '='; 050 051 /** The Character '>'. */ 052 public static final Character GT = '>'; 053 054 /** The Character '<'. */ 055 public static final Character LT = '<'; 056 057 /** The Character '?'. */ 058 public static final Character QUEST = '?'; 059 060 /** The Character '"'. */ 061 public static final Character QUOT = '"'; 062 063 /** The Character '/'. */ 064 public static final Character SLASH = '/'; 065 066 /** 067 * Replace special characters with XML escapes: 068 * 069 * <pre> 070 * & <small>(ampersand)</small> is replaced by &amp; 071 * < <small>(less than)</small> is replaced by &lt; 072 * > <small>(greater than)</small> is replaced by &gt; 073 * " <small>(double quote)</small> is replaced by &quot; 074 * </pre> 075 * 076 * @param string 077 * The string to be escaped. 078 * @return The escaped string. 079 */ 080 public static String escape(String string) { 081 StringBuilder sb = new StringBuilder(string.length()); 082 for (int i = 0, length = string.length(); i < length; i++) { 083 char c = string.charAt(i); 084 switch (c) { 085 case '&': 086 sb.append("&"); 087 break; 088 case '<': 089 sb.append("<"); 090 break; 091 case '>': 092 sb.append(">"); 093 break; 094 case '"': 095 sb.append("""); 096 break; 097 case '\'': 098 sb.append("'"); 099 break; 100 default: 101 sb.append(c); 102 } 103 } 104 return sb.toString(); 105 } 106 107 /** 108 * Throw an exception if the string contains whitespace. Whitespace is not 109 * allowed in tagNames and attributes. 110 * 111 * @param string 112 * A string. 113 * @throws JSONException 114 */ 115 public static void noSpace(String string) throws JSONException { 116 int i, length = string.length(); 117 if (length == 0) { 118 throw new JSONException("Empty string."); 119 } 120 for (i = 0; i < length; i += 1) { 121 if (Character.isWhitespace(string.charAt(i))) { 122 throw new JSONException("'" + string 123 + "' contains a space character."); 124 } 125 } 126 } 127 128 /** 129 * Scan the content following the named tag, attaching it to the context. 130 * 131 * @param x 132 * The XMLTokener containing the source string. 133 * @param context 134 * The JSONObject that will include the new material. 135 * @param name 136 * The tag name. 137 * @return true if the close tag is processed. 138 * @throws JSONException 139 */ 140 private static boolean parse(XMLTokener x, JSONObject context, String name) 141 throws JSONException { 142 char c; 143 int i; 144 JSONObject jsonobject = null; 145 String string; 146 String tagName; 147 Object token; 148 149 // Test for and skip past these forms: 150 // <!-- ... --> 151 // <! ... > 152 // <![ ... ]]> 153 // <? ... ?> 154 // Report errors for these forms: 155 // <> 156 // <= 157 // << 158 159 token = x.nextToken(); 160 161 // <! 162 163 if (token == BANG) { 164 c = x.next(); 165 if (c == '-') { 166 if (x.next() == '-') { 167 x.skipPast("-->"); 168 return false; 169 } 170 x.back(); 171 } else if (c == '[') { 172 token = x.nextToken(); 173 if ("CDATA".equals(token)) { 174 if (x.next() == '[') { 175 string = x.nextCDATA(); 176 if (string.length() > 0) { 177 context.accumulate("content", string); 178 } 179 return false; 180 } 181 } 182 throw x.syntaxError("Expected 'CDATA['"); 183 } 184 i = 1; 185 do { 186 token = x.nextMeta(); 187 if (token == null) { 188 throw x.syntaxError("Missing '>' after '<!'."); 189 } else if (token == LT) { 190 i += 1; 191 } else if (token == GT) { 192 i -= 1; 193 } 194 } while (i > 0); 195 return false; 196 } else if (token == QUEST) { 197 198 // <? 199 x.skipPast("?>"); 200 return false; 201 } else if (token == SLASH) { 202 203 // Close tag </ 204 205 token = x.nextToken(); 206 if (name == null) { 207 throw x.syntaxError("Mismatched close tag " + token); 208 } 209 if (!token.equals(name)) { 210 throw x.syntaxError("Mismatched " + name + " and " + token); 211 } 212 if (x.nextToken() != GT) { 213 throw x.syntaxError("Misshaped close tag"); 214 } 215 return true; 216 217 } else if (token instanceof Character) { 218 throw x.syntaxError("Misshaped tag"); 219 220 // Open tag < 221 222 } else { 223 tagName = (String) token; 224 token = null; 225 jsonobject = new JSONObject(); 226 for (;;) { 227 if (token == null) { 228 token = x.nextToken(); 229 } 230 231 // attribute = value 232 if (token instanceof String) { 233 string = (String) token; 234 token = x.nextToken(); 235 if (token == EQ) { 236 token = x.nextToken(); 237 if (!(token instanceof String)) { 238 throw x.syntaxError("Missing value"); 239 } 240 jsonobject.accumulate(string, 241 JSONObject.stringToValue((String) token)); 242 token = null; 243 } else { 244 jsonobject.accumulate(string, ""); 245 } 246 247 248 } else if (token == SLASH) { 249 // Empty tag <.../> 250 if (x.nextToken() != GT) { 251 throw x.syntaxError("Misshaped tag"); 252 } 253 if (jsonobject.length() > 0) { 254 context.accumulate(tagName, jsonobject); 255 } else { 256 context.accumulate(tagName, ""); 257 } 258 return false; 259 260 } else if (token == GT) { 261 // Content, between <...> and </...> 262 for (;;) { 263 token = x.nextContent(); 264 if (token == null) { 265 if (tagName != null) { 266 throw x.syntaxError("Unclosed tag " + tagName); 267 } 268 return false; 269 } else if (token instanceof String) { 270 string = (String) token; 271 if (string.length() > 0) { 272 jsonobject.accumulate("content", 273 JSONObject.stringToValue(string)); 274 } 275 276 } else if (token == LT) { 277 // Nested element 278 if (parse(x, jsonobject, tagName)) { 279 if (jsonobject.length() == 0) { 280 context.accumulate(tagName, ""); 281 } else if (jsonobject.length() == 1 282 && jsonobject.opt("content") != null) { 283 context.accumulate(tagName, 284 jsonobject.opt("content")); 285 } else { 286 context.accumulate(tagName, jsonobject); 287 } 288 return false; 289 } 290 } 291 } 292 } else { 293 throw x.syntaxError("Misshaped tag"); 294 } 295 } 296 } 297 } 298 299 /** 300 * This method has been deprecated in favor of the 301 * {@link JSONObject.stringToValue(String)} method. Use it instead. 302 * 303 * @deprecated Use {@link JSONObject#stringToValue(String)} instead. 304 * @param string 305 * @return JSON value of this string or the string 306 */ 307 public static Object stringToValue(String string) { 308 return JSONObject.stringToValue(string); 309 } 310 311 /** 312 * Convert a well-formed (but not necessarily valid) XML string into a 313 * JSONObject. Some information may be lost in this transformation because 314 * JSON is a data format and XML is a document format. XML uses elements, 315 * attributes, and content text, while JSON uses unordered collections of 316 * name/value pairs and arrays of values. JSON does not does not like to 317 * distinguish between elements and attributes. Sequences of similar 318 * elements are represented as JSONArrays. Content text may be placed in a 319 * "content" member. Comments, prologs, DTDs, and <code><[ [ ]]></code> 320 * are ignored. 321 * 322 * @param string 323 * The source string. 324 * @return A JSONObject containing the structured data from the XML string. 325 * @throws JSONException 326 */ 327 public static JSONObject toJSONObject(String string) throws JSONException { 328 JSONObject jo = new JSONObject(); 329 XMLTokener x = new XMLTokener(string); 330 while (x.more() && x.skipPast("<")) { 331 parse(x, jo, null); 332 } 333 return jo; 334 } 335 336 /** 337 * Convert a JSONObject into a well-formed, element-normal XML string. 338 * 339 * @param object 340 * A JSONObject. 341 * @return A string. 342 * @throws JSONException 343 */ 344 public static String toString(Object object) throws JSONException { 345 return toString(object, null); 346 } 347 348 /** 349 * Convert a JSONObject into a well-formed, element-normal XML string. 350 * 351 * @param object 352 * A JSONObject. 353 * @param tagName 354 * The optional name of the enclosing tag. 355 * @return A string. 356 * @throws JSONException 357 */ 358 public static String toString(Object object, String tagName) 359 throws JSONException { 360 StringBuilder sb = new StringBuilder(); 361 JSONArray ja; 362 JSONObject jo; 363 String key; 364 Iterator<String> keys; 365 String string; 366 Object value; 367 368 if (object instanceof JSONObject) { 369 370 // Emit <tagName> 371 if (tagName != null) { 372 sb.append('<'); 373 sb.append(tagName); 374 sb.append('>'); 375 } 376 377 // Loop thru the keys. 378 jo = (JSONObject) object; 379 keys = jo.keys(); 380 while (keys.hasNext()) { 381 key = keys.next(); 382 value = jo.opt(key); 383 if (value == null) { 384 value = ""; 385 } else if (value.getClass().isArray()) { 386 value = new JSONArray(value); 387 } 388 string = value instanceof String ? (String) value : null; 389 390 // Emit content in body 391 if ("content".equals(key)) { 392 if (value instanceof JSONArray) { 393 ja = (JSONArray) value; 394 int i = 0; 395 for (Object val : ja) { 396 if (i > 0) { 397 sb.append('\n'); 398 } 399 sb.append(escape(val.toString())); 400 i++; 401 } 402 } else { 403 sb.append(escape(value.toString())); 404 } 405 406 // Emit an array of similar keys 407 408 } else if (value instanceof JSONArray) { 409 ja = (JSONArray) value; 410 for (Object val : ja) { 411 if (val instanceof JSONArray) { 412 sb.append('<'); 413 sb.append(key); 414 sb.append('>'); 415 sb.append(toString(val)); 416 sb.append("</"); 417 sb.append(key); 418 sb.append('>'); 419 } else { 420 sb.append(toString(val, key)); 421 } 422 } 423 } else if ("".equals(value)) { 424 sb.append('<'); 425 sb.append(key); 426 sb.append("/>"); 427 428 // Emit a new tag <k> 429 430 } else { 431 sb.append(toString(value, key)); 432 } 433 } 434 if (tagName != null) { 435 436 // Emit the </tagname> close tag 437 sb.append("</"); 438 sb.append(tagName); 439 sb.append('>'); 440 } 441 return sb.toString(); 442 443 } 444 445 if (object != null) { 446 if (object.getClass().isArray()) { 447 object = new JSONArray(object); 448 } 449 450 if (object instanceof JSONArray) { 451 ja = (JSONArray) object; 452 for (Object val : ja) { 453 // XML does not have good support for arrays. If an array 454 // appears in a place where XML is lacking, synthesize an 455 // <array> element. 456 sb.append(toString(val, tagName == null ? "array" : tagName)); 457 } 458 return sb.toString(); 459 } 460 } 461 462 string = (object == null) ? "null" : escape(object.toString()); 463 return (tagName == null) ? "\"" + string + "\"" 464 : (string.length() == 0) ? "<" + tagName + "/>" : "<" + tagName 465 + ">" + string + "</" + tagName + ">"; 466 467 } 468}