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>&lt;[ [ ]]></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>&lt;[ [ ]]></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}