001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 * 027 * This file is based on: 028 * org.json.XML 029 * from the JSON in Java implementation. 030 * 031 * Copyright (c) 2002 JSON.org 032 * 033 * Permission is hereby granted, free of charge, to any person obtaining a copy 034 * of this software and associated documentation files (the "Software"), to deal 035 * in the Software without restriction, including without limitation the rights 036 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 037 * copies of the Software, and to permit persons to whom the Software is 038 * furnished to do so, subject to the following conditions: 039 * 040 * The above copyright notice and this permission notice shall be included in all 041 * copies or substantial portions of the Software. 042 * 043 * The Software shall be used for Good, not Evil. 044 * 045 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 046 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 047 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 048 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 049 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 050 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 051 * SOFTWARE. 052 */ 053 054package org.opencms.json; 055 056import java.util.Iterator; 057 058/** 059 * This provides static methods to convert an XML text into a JSONObject, 060 * and to covert a JSONObject into an XML text.<p> 061 * 062 */ 063public final class XML { 064 065 /** The Character '&'. */ 066 public static final Character AMP = new Character('&'); 067 068 /** The Character '''. */ 069 public static final Character APOS = new Character('\''); 070 071 /** The Character '!'. */ 072 public static final Character BANG = new Character('!'); 073 074 /** The Character '='. */ 075 public static final Character EQ = new Character('='); 076 077 /** The Character '>'. */ 078 public static final Character GT = new Character('>'); 079 080 /** The Character '<'. */ 081 public static final Character LT = new Character('<'); 082 083 /** The Character '?'. */ 084 public static final Character QUEST = new Character('?'); 085 086 /** The Character '"'. */ 087 public static final Character QUOT = new Character('"'); 088 089 /** The Character '/'. */ 090 public static final Character SLASH = new Character('/'); 091 092 /** 093 * Hidden constructor.<p> 094 */ 095 private XML() { 096 097 // hide constructor 098 } 099 100 /** 101 * Replace special characters with XML escapes: 102 * <pre> 103 * & <small>(ampersand)</small> is replaced by &amp; 104 * < <small>(less than)</small> is replaced by &lt; 105 * > <small>(greater than)</small> is replaced by &gt; 106 * " <small>(double quote)</small> is replaced by &quot; 107 * </pre>.<p> 108 * 109 * @param string the string to be escaped 110 * @return the escaped string 111 */ 112 public static String escape(String string) { 113 114 StringBuffer sb = new StringBuffer(); 115 for (int i = 0, len = string.length(); i < len; i++) { 116 char c = string.charAt(i); 117 switch (c) { 118 case '&': 119 sb.append("&"); 120 break; 121 case '<': 122 sb.append("<"); 123 break; 124 case '>': 125 sb.append(">"); 126 break; 127 case '"': 128 sb.append("""); 129 break; 130 default: 131 sb.append(c); 132 } 133 } 134 return sb.toString(); 135 } 136 137 /** 138 * Convert a well-formed (but not necessarily valid) XML string into a 139 * JSONObject.<p> 140 * 141 * Some information may be lost in this transformation 142 * because JSON is a data format and XML is a document format. XML uses 143 * elements, attributes, and content text, while JSON uses unordered 144 * collections of name/value pairs and arrays of values. JSON does not 145 * does not like to distinguish between elements and attributes.<p> 146 * 147 * Sequences of similar elements are represented as JSONArrays. Content 148 * text may be placed in a "content" member. Comments, prologs, DTDs, and 149 * <code><[ [ ]]></code> are ignored.<p> 150 * 151 * @param string the source string 152 * @return a JSONObject containing the structured data from the XML string 153 * @throws JSONException if something goes wrong 154 */ 155 public static JSONObject toJSONObject(String string) throws JSONException { 156 157 JSONObject o = new JSONObject(); 158 XMLTokener x = new XMLTokener(string); 159 while (x.more() && x.skipPast("<")) { 160 parse(x, o, null); 161 } 162 return o; 163 } 164 165 /** 166 * Convert a JSONObject into a well-formed, element-normal XML string.<p> 167 * 168 * @param o a JSONObject 169 * @return a string 170 * @throws JSONException if something goes wrong 171 */ 172 public static String toString(Object o) throws JSONException { 173 174 return toString(o, null); 175 } 176 177 /** 178 * Convert a JSONObject into a well-formed, element-normal XML string.<p> 179 * 180 * @param o a JSONObject 181 * @param tagName the optional name of the enclosing tag 182 * @return a string 183 * @throws JSONException if something goes wrong 184 */ 185 public static String toString(Object o, String tagName) throws JSONException { 186 187 StringBuffer b = new StringBuffer(); 188 int i; 189 JSONArray ja; 190 JSONObject jo; 191 String k; 192 Iterator<String> keys; 193 int len; 194 String s; 195 Object v; 196 if (o instanceof JSONObject) { 197 198 // Emit <tagName> 199 200 if (tagName != null) { 201 b.append('<'); 202 b.append(tagName); 203 b.append('>'); 204 } 205 206 // Loop thru the keys. 207 208 jo = (JSONObject)o; 209 keys = jo.keys(); 210 while (keys.hasNext()) { 211 k = keys.next(); 212 v = jo.get(k); 213 if (v instanceof String) { 214 s = (String)v; 215 } else { 216 s = null; 217 } 218 219 // Emit content in body 220 221 if (k.equals("content")) { 222 if (v instanceof JSONArray) { 223 ja = (JSONArray)v; 224 len = ja.length(); 225 for (i = 0; i < len; i += 1) { 226 if (i > 0) { 227 b.append('\n'); 228 } 229 b.append(escape(ja.get(i).toString())); 230 } 231 } else { 232 b.append(escape(v.toString())); 233 } 234 235 // Emit an array of similar keys 236 237 } else if (v instanceof JSONArray) { 238 ja = (JSONArray)v; 239 len = ja.length(); 240 for (i = 0; i < len; i += 1) { 241 b.append(toString(ja.get(i), k)); 242 } 243 } else if (v.equals("")) { 244 b.append('<'); 245 b.append(k); 246 b.append("/>"); 247 248 // Emit a new tag <k> 249 250 } else { 251 b.append(toString(v, k)); 252 } 253 } 254 if (tagName != null) { 255 256 // Emit the </tagname> close tag 257 258 b.append("</"); 259 b.append(tagName); 260 b.append('>'); 261 } 262 return b.toString(); 263 264 // XML does not have good support for arrays. If an array appears in a place 265 // where XML is lacking, synthesize an <array> element. 266 267 } else if (o instanceof JSONArray) { 268 ja = (JSONArray)o; 269 len = ja.length(); 270 for (i = 0; i < len; ++i) { 271 b.append(toString(ja.opt(i), (tagName == null) ? "array" : tagName)); 272 } 273 return b.toString(); 274 } else { 275 s = (o == null) ? "null" : escape(o.toString()); 276 return (tagName == null) ? "\"" + s + "\"" : (s.length() == 0) ? "<" + tagName + "/>" : "<" 277 + tagName 278 + ">" 279 + s 280 + "</" 281 + tagName 282 + ">"; 283 } 284 } 285 286 /** 287 * Scan the content following the named tag, attaching it to the context.<p> 288 * 289 * @param x the XMLTokener containing the source string 290 * @param context the JSONObject that will include the new material 291 * @param name the tag name 292 * @return true if the close tag is processed 293 * @throws JSONException if something goes wrong 294 */ 295 private static boolean parse(XMLTokener x, JSONObject context, String name) throws JSONException { 296 297 char c; 298 int i; 299 String n; 300 JSONObject o = null; 301 String s; 302 Object t; 303 304 // Test for and skip past these forms: 305 // <!-- ... --> 306 // <! ... > 307 // <![ ... ]]> 308 // <? ... ?> 309 // Report errors for these forms: 310 // <> 311 // <= 312 // << 313 314 t = x.nextToken(); 315 316 // <! 317 318 if (t == BANG) { 319 c = x.next(); 320 if (c == '-') { 321 if (x.next() == '-') { 322 x.skipPast("-->"); 323 return false; 324 } 325 x.back(); 326 } else if (c == '[') { 327 t = x.nextToken(); 328 if (t.equals("CDATA")) { 329 if (x.next() == '[') { 330 s = x.nextCDATA(); 331 if (s.length() > 0) { 332 context.accumulate("content", s); 333 } 334 return false; 335 } 336 } 337 throw x.syntaxError("Expected 'CDATA['"); 338 } 339 i = 1; 340 do { 341 t = x.nextMeta(); 342 if (t == null) { 343 throw x.syntaxError("Missing '>' after '<!'."); 344 } else if (t == LT) { 345 i += 1; 346 } else if (t == GT) { 347 i -= 1; 348 } 349 } while (i > 0); 350 return false; 351 } else if (t == QUEST) { 352 353 // <? 354 355 x.skipPast("?>"); 356 return false; 357 } else if (t == SLASH) { 358 359 // Close tag </ 360 361 t = x.nextToken(); 362 if (name == null) { 363 throw x.syntaxError("Mismatched close tag" + t); 364 } 365 if (!t.equals(name)) { 366 throw x.syntaxError("Mismatched " + name + " and " + t); 367 } 368 if (x.nextToken() != GT) { 369 throw x.syntaxError("Misshaped close tag"); 370 } 371 return true; 372 373 } else if (t instanceof Character) { 374 throw x.syntaxError("Misshaped tag"); 375 376 // Open tag < 377 378 } else { 379 n = (String)t; 380 t = null; 381 o = new JSONObject(); 382 for (;;) { 383 if (t == null) { 384 t = x.nextToken(); 385 } 386 387 // attribute = value 388 389 if (t instanceof String) { 390 s = (String)t; 391 t = x.nextToken(); 392 if (t == EQ) { 393 t = x.nextToken(); 394 if (!(t instanceof String)) { 395 throw x.syntaxError("Missing value"); 396 } 397 o.accumulate(s, t); 398 t = null; 399 } else { 400 o.accumulate(s, ""); 401 } 402 403 // Empty tag <.../> 404 405 } else if (t == SLASH) { 406 if (x.nextToken() != GT) { 407 throw x.syntaxError("Misshaped tag"); 408 } 409 context.accumulate(n, o); 410 return false; 411 412 // Content, between <...> and </...> 413 414 } else if (t == GT) { 415 for (;;) { 416 t = x.nextContent(); 417 if (t == null) { 418 if (n != null) { 419 throw x.syntaxError("Unclosed tag " + n); 420 } 421 return false; 422 } else if (t instanceof String) { 423 s = (String)t; 424 if (s.length() > 0) { 425 o.accumulate("content", s); 426 } 427 428 // Nested element 429 430 } else if (t == LT) { 431 if (parse(x, o, n)) { 432 if (o.length() == 0) { 433 context.accumulate(n, ""); 434 } else if ((o.length() == 1) && (o.opt("content") != null)) { 435 context.accumulate(n, o.opt("content")); 436 } else { 437 context.accumulate(n, o); 438 } 439 return false; 440 } 441 } 442 } 443 } else { 444 throw x.syntaxError("Misshaped tag"); 445 } 446 } 447 } 448 } 449}