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.JSONML 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 convert a JSONObject into an XML text using the JsonML transform.<p> 061 * 062 */ 063public final class JSONML { 064 065 /** 066 * Hidden constructor.<p> 067 */ 068 private JSONML() { 069 070 // hidden constructor 071 } 072 073 /** 074 * Convert a well-formed (but not necessarily valid) XML string into a 075 * JSONArray using the JsonML transform.<p> 076 * 077 * Each XML tag is represented as 078 * a JSONArray in which the first element is the tag name. If the tag has 079 * attributes, then the second element will be JSONObject containing the 080 * name/value pairs. If the tag contains children, then strings and 081 * JSONArrays will represent the child tags. 082 * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored.<p> 083 * 084 * @param string the source string 085 * @return a JSONArray containing the structured data from the XML string. 086 * @throws JSONException if something goes wrong 087 */ 088 public static JSONArray toJSONArray(String string) throws JSONException { 089 090 return toJSONArray(new XMLTokener(string)); 091 } 092 093 /** 094 * Convert a well-formed (but not necessarily valid) XML string into a 095 * JSONArray using the JsonML transform.<p> 096 * 097 * Each XML tag is represented as 098 * a JSONArray in which the first element is the tag name. If the tag has 099 * attributes, then the second element will be JSONObject containing the 100 * name/value pairs. If the tag contains children, then strings and 101 * JSONArrays will represent the child content and tags. 102 * Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored.<p> 103 * 104 * @param x an XMLTokener 105 * @return a JSONArray containing the structured data from the XML string 106 * @throws JSONException if something goes wrong 107 */ 108 public static JSONArray toJSONArray(XMLTokener x) throws JSONException { 109 110 return parse(x, null); 111 } 112 113 /** 114 * Reverse the JSONML transformation, making an XML text from a JSONArray.<p> 115 * 116 * @param ja a JSONArray 117 * @return an XML string 118 * @throws JSONException if something goes wrong 119 */ 120 public static String toString(JSONArray ja) throws JSONException { 121 122 StringBuffer b = new StringBuffer(); 123 stringify(ja, b); 124 return b.toString(); 125 } 126 127 /** 128 * Parse XML values and store them in a JSONArray.<p> 129 * 130 * @param x the XMLTokener containing the source string 131 * @param ja the JSONArray that is containing the current tag or null 132 * if we are at the outermost level 133 * @return a JSONArray if the value is the outermost tag, otherwise null 134 * @throws JSONException if something goes wrong 135 */ 136 private static JSONArray parse(XMLTokener x, JSONArray ja) throws JSONException { 137 138 char c; 139 int i; 140 String s; 141 Object t; 142 143 // Test for and skip past these forms: 144 // <!-- ... --> 145 // <! ... > 146 // <![ ... ]]> 147 // <? ... ?> 148 // Report errors for these forms: 149 // <> 150 // <= 151 // << 152 153 while (true) { 154 t = x.nextContent(); 155 if (t == XML.LT) { 156 t = x.nextToken(); 157 if (t instanceof Character) { 158 159 // <! 160 161 if (t == XML.BANG) { 162 c = x.next(); 163 if (c == '-') { 164 if (x.next() == '-') { 165 x.skipPast("-->"); 166 } 167 x.back(); 168 } else if (c == '[') { 169 t = x.nextToken(); 170 if (t.equals("CDATA") && (x.next() == '[')) { 171 x.nextCDATA(); 172 } else { 173 throw x.syntaxError("Expected 'CDATA['"); 174 } 175 } else { 176 i = 1; 177 do { 178 t = x.nextMeta(); 179 if (t == null) { 180 throw x.syntaxError("Missing '>' after '<!'."); 181 } else if (t == XML.LT) { 182 i += 1; 183 } else if (t == XML.GT) { 184 i -= 1; 185 } 186 } while (i > 0); 187 } 188 } else if (t == XML.QUEST) { 189 190 // <? 191 192 x.skipPast("?>"); 193 } else if (t == XML.SLASH) { 194 195 // Close tag </ 196 197 t = x.nextToken(); 198 if (ja == null) { 199 throw x.syntaxError("Mismatched close tag '" + t + "'"); 200 } 201 if (!t.equals(ja.get(0))) { 202 throw x.syntaxError("Mismatched '" + ja.get(0) + "' and '" + t + "'"); 203 } 204 if (x.nextToken() != XML.GT) { 205 throw x.syntaxError("Misshaped close tag"); 206 } 207 return null; 208 } else { 209 throw x.syntaxError("Misshaped tag"); 210 } 211 212 // Open tag < 213 214 } else { 215 JSONArray newja = new JSONArray(); 216 JSONObject attributes = new JSONObject(); 217 if (ja != null) { 218 ja.put(newja); 219 } 220 newja.put(t); 221 t = null; 222 for (;;) { 223 if (t == null) { 224 t = x.nextToken(); 225 } 226 if (t == null) { 227 throw x.syntaxError("Misshaped tag"); 228 } 229 if (!(t instanceof String)) { 230 break; 231 } 232 233 // attribute = value 234 235 s = (String)t; 236 t = x.nextToken(); 237 if (t == XML.EQ) { 238 t = x.nextToken(); 239 if (!(t instanceof String)) { 240 throw x.syntaxError("Missing value"); 241 } 242 attributes.accumulate(s, t); 243 t = null; 244 } else { 245 attributes.accumulate(s, ""); 246 } 247 } 248 if (attributes.length() > 0) { 249 newja.put(attributes); 250 } 251 252 // Empty tag <.../> 253 254 if (t == XML.SLASH) { 255 if (x.nextToken() != XML.GT) { 256 throw x.syntaxError("Misshaped tag"); 257 } 258 if (ja == null) { 259 return newja; 260 } 261 262 // Content, between <...> and </...> 263 264 } else if (t == XML.GT) { 265 parse(x, newja); 266 if (ja == null) { 267 return newja; 268 } 269 } else { 270 throw x.syntaxError("Misshaped tag"); 271 } 272 } 273 } else { 274 if (ja != null) { 275 ja.put(t); 276 } 277 } 278 } 279 } 280 281 /** 282 * Reverse the JSONML transformation, making an XML text from a JSONArray.<p> 283 * 284 * @param ja a JSONArray 285 * @param b a string buffer in which to build the text 286 * @throws JSONException if something goes wrong 287 */ 288 private static void stringify(JSONArray ja, StringBuffer b) throws JSONException { 289 290 int i; 291 JSONObject jo; 292 String k; 293 Iterator<String> keys; 294 int len; 295 Object o; 296 Object v; 297 298 // Emit <tagName> 299 300 b.append('<'); 301 b.append(ja.get(0)); 302 o = ja.opt(1); 303 if (o instanceof JSONObject) { 304 305 // Loop thru the attributes. 306 307 jo = (JSONObject)o; 308 keys = jo.keys(); 309 while (keys.hasNext()) { 310 k = keys.next().toString(); 311 v = jo.get(k).toString(); 312 b.append(' '); 313 b.append(k); 314 b.append("=\""); 315 b.append(XML.escape((String)v)); 316 b.append('"'); 317 } 318 i = 2; 319 } else { 320 i = 1; 321 } 322 len = ja.length(); 323 324 if (i >= len) { 325 b.append("/>"); 326 } else { 327 b.append('>'); 328 while (i < len) { 329 v = ja.get(i); 330 if (v instanceof JSONArray) { 331 stringify((JSONArray)v, b); 332 } else { 333 b.append(XML.escape(v.toString())); 334 } 335 i += 1; 336 } 337 b.append("</"); 338 b.append(ja.get(0)); 339 b.append('>'); 340 } 341 } 342}