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