001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (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 & Co. KG, 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 = Character.valueOf('&'); 067 068 /** The Character '''. */ 069 public static final Character APOS = Character.valueOf('\''); 070 071 /** The Character '!'. */ 072 public static final Character BANG = Character.valueOf('!'); 073 074 /** The Character '='. */ 075 public static final Character EQ = Character.valueOf('='); 076 077 /** The Character '>'. */ 078 public static final Character GT = Character.valueOf('>'); 079 080 /** The Character '<'. */ 081 public static final Character LT = Character.valueOf('<'); 082 083 /** The Character '?'. */ 084 public static final Character QUEST = Character.valueOf('?'); 085 086 /** The Character '"'. */ 087 public static final Character QUOT = Character.valueOf('"'); 088 089 /** The Character '/'. */ 090 public static final Character SLASH = Character.valueOf('/'); 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) 277 ? "\"" + s + "\"" 278 : (s.length() == 0) ? "<" + tagName + "/>" : "<" + tagName + ">" + s + "</" + tagName + ">"; 279 } 280 } 281 282 /** 283 * Scan the content following the named tag, attaching it to the context.<p> 284 * 285 * @param x the XMLTokener containing the source string 286 * @param context the JSONObject that will include the new material 287 * @param name the tag name 288 * @return true if the close tag is processed 289 * @throws JSONException if something goes wrong 290 */ 291 private static boolean parse(XMLTokener x, JSONObject context, String name) throws JSONException { 292 293 char c; 294 int i; 295 String n; 296 JSONObject o = null; 297 String s; 298 Object t; 299 300 // Test for and skip past these forms: 301 // <!-- ... --> 302 // <! ... > 303 // <![ ... ]]> 304 // <? ... ?> 305 // Report errors for these forms: 306 // <> 307 // <= 308 // << 309 310 t = x.nextToken(); 311 312 // <! 313 314 if (t == BANG) { 315 c = x.next(); 316 if (c == '-') { 317 if (x.next() == '-') { 318 x.skipPast("-->"); 319 return false; 320 } 321 x.back(); 322 } else if (c == '[') { 323 t = x.nextToken(); 324 if (t.equals("CDATA")) { 325 if (x.next() == '[') { 326 s = x.nextCDATA(); 327 if (s.length() > 0) { 328 context.accumulate("content", s); 329 } 330 return false; 331 } 332 } 333 throw x.syntaxError("Expected 'CDATA['"); 334 } 335 i = 1; 336 do { 337 t = x.nextMeta(); 338 if (t == null) { 339 throw x.syntaxError("Missing '>' after '<!'."); 340 } else if (t == LT) { 341 i += 1; 342 } else if (t == GT) { 343 i -= 1; 344 } 345 } while (i > 0); 346 return false; 347 } else if (t == QUEST) { 348 349 // <? 350 351 x.skipPast("?>"); 352 return false; 353 } else if (t == SLASH) { 354 355 // Close tag </ 356 357 t = x.nextToken(); 358 if (name == null) { 359 throw x.syntaxError("Mismatched close tag" + t); 360 } 361 if (!t.equals(name)) { 362 throw x.syntaxError("Mismatched " + name + " and " + t); 363 } 364 if (x.nextToken() != GT) { 365 throw x.syntaxError("Misshaped close tag"); 366 } 367 return true; 368 369 } else if (t instanceof Character) { 370 throw x.syntaxError("Misshaped tag"); 371 372 // Open tag < 373 374 } else { 375 n = (String)t; 376 t = null; 377 o = new JSONObject(); 378 for (;;) { 379 if (t == null) { 380 t = x.nextToken(); 381 } 382 383 // attribute = value 384 385 if (t instanceof String) { 386 s = (String)t; 387 t = x.nextToken(); 388 if (t == EQ) { 389 t = x.nextToken(); 390 if (!(t instanceof String)) { 391 throw x.syntaxError("Missing value"); 392 } 393 o.accumulate(s, t); 394 t = null; 395 } else { 396 o.accumulate(s, ""); 397 } 398 399 // Empty tag <.../> 400 401 } else if (t == SLASH) { 402 if (x.nextToken() != GT) { 403 throw x.syntaxError("Misshaped tag"); 404 } 405 context.accumulate(n, o); 406 return false; 407 408 // Content, between <...> and </...> 409 410 } else if (t == GT) { 411 for (;;) { 412 t = x.nextContent(); 413 if (t == null) { 414 if (n != null) { 415 throw x.syntaxError("Unclosed tag " + n); 416 } 417 return false; 418 } else if (t instanceof String) { 419 s = (String)t; 420 if (s.length() > 0) { 421 o.accumulate("content", s); 422 } 423 424 // Nested element 425 426 } else if (t == LT) { 427 if (parse(x, o, n)) { 428 if (o.length() == 0) { 429 context.accumulate(n, ""); 430 } else if ((o.length() == 1) && (o.opt("content") != null)) { 431 context.accumulate(n, o.opt("content")); 432 } else { 433 context.accumulate(n, o); 434 } 435 return false; 436 } 437 } 438 } 439 } else { 440 throw x.syntaxError("Misshaped tag"); 441 } 442 } 443 } 444 } 445}