View Javadoc

1   package atg.taglib.json.util;
2   
3   /*
4   Copyright (c) 2002 JSON.org
5   
6   Permission is hereby granted, free of charge, to any person obtaining a copy
7   of this software and associated documentation files (the "Software"), to deal
8   in the Software without restriction, including without limitation the rights
9   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  copies of the Software, and to permit persons to whom the Software is
11  furnished to do so, subject to the following conditions:
12  
13  The above copyright notice and this permission notice shall be included in all
14  copies or substantial portions of the Software.
15  
16  The Software shall be used for Good, not Evil.
17  
18  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24  SOFTWARE.
25  */
26  
27  import java.util.Iterator;
28  
29  /**
30   * This provides static methods to convert an XML text into a JSONObject,
31   * and to covert a JSONObject into an XML text.
32   * @author JSON.org
33   * @version 2
34   */
35  public class XML {
36  
37      /** The Character '&'. */
38      public static final Character AMP   = new Character('&');
39  
40      /** The Character '''. */
41      public static final Character APOS  = new Character('\'');
42  
43      /** The Character '!'. */
44      public static final Character BANG  = new Character('!');
45  
46      /** The Character '='. */
47      public static final Character EQ    = new Character('=');
48  
49      /** The Character '>'. */
50      public static final Character GT    = new Character('>');
51  
52      /** The Character '<'. */
53      public static final Character LT    = new Character('<');
54  
55      /** The Character '?'. */
56      public static final Character QUEST = new Character('?');
57  
58      /** The Character '"'. */
59      public static final Character QUOT  = new Character('"');
60  
61      /** The Character '/'. */
62      public static final Character SLASH = new Character('/');
63  
64      /**
65       * Replace special characters with XML escapes:
66       * <pre>
67       * &amp; <small>(ampersand)</small> is replaced by &amp;amp;
68       * &lt; <small>(less than)</small> is replaced by &amp;lt;
69       * &gt; <small>(greater than)</small> is replaced by &amp;gt;
70       * &quot; <small>(double quote)</small> is replaced by &amp;quot;
71       * </pre>
72       * @param string The string to be escaped.
73       * @return The escaped string.
74       */
75      public static String escape(String string) {
76          StringBuffer sb = new StringBuffer();
77          for (int i = 0, len = string.length(); i < len; i++) {
78              char c = string.charAt(i);
79              switch (c) {
80              case '&':
81                  sb.append("&amp;");
82                  break;
83              case '<':
84                  sb.append("&lt;");
85                  break;
86              case '>':
87                  sb.append("&gt;");
88                  break;
89              case '"':
90                  sb.append("&quot;");
91                  break;
92              default:
93                  sb.append(c);
94              }
95          }
96          return sb.toString();
97      }
98  
99      /**
100      * Scan the content following the named tag, attaching it to the context.
101      * @param x       The XMLTokener containing the source string.
102      * @param context The JSONObject that will include the new material.
103      * @param name    The tag name.
104      * @return true if the close tag is processed.
105      * @throws JSONException
106      */
107     private static boolean parse(XMLTokener x, JSONObject context,
108                                  String name) throws JSONException {
109         char       c;
110         int        i;
111         String     n;
112         JSONObject o = null;
113         String     s;
114         Object     t;
115 
116 // Test for and skip past these forms:
117 //      <!-- ... -->
118 //      <!   ...   >
119 //      <![  ... ]]>
120 //      <?   ...  ?>
121 // Report errors for these forms:
122 //      <>
123 //      <=
124 //      <<
125 
126         t = x.nextToken();
127 
128 // <!
129 
130         if (t == BANG) {
131             c = x.next();
132             if (c == '-') {
133                 if (x.next() == '-') {
134                     x.skipPast("-->");
135                     return false;
136                 }
137                 x.back();
138             } else if (c == '[') {
139                 t = x.nextToken();
140                 if (t.equals("CDATA")) {
141                     if (x.next() == '[') {
142                         s = x.nextCDATA();
143                         if (s.length() > 0) {
144                             context.accumulate("content", s);
145                         }
146                         return false;
147                     }
148                 }
149                 throw x.syntaxError("Expected 'CDATA['");
150             }
151             i = 1;
152             do {
153                 t = x.nextMeta();
154                 if (t == null) {
155                     throw x.syntaxError("Missing '>' after '<!'.");
156                 } else if (t == LT) {
157                     i += 1;
158                 } else if (t == GT) {
159                     i -= 1;
160                 }
161             } while (i > 0);
162             return false;
163         } else if (t == QUEST) {
164 
165 // <?
166 
167             x.skipPast("?>");
168             return false;
169         } else if (t == SLASH) {
170 
171 // Close tag </
172 
173             if (name == null || !x.nextToken().equals(name)) {
174                 throw x.syntaxError("Mismatched close tag");
175             }
176             if (x.nextToken() != GT) {
177                 throw x.syntaxError("Misshaped close tag");
178             }
179             return true;
180 
181         } else if (t instanceof Character) {
182             throw x.syntaxError("Misshaped tag");
183 
184 // Open tag <
185 
186         } else {
187             n = (String)t;
188             t = null;
189             o = new JSONObject();
190             for (;;) {
191                 if (t == null) {
192                     t = x.nextToken();
193                 }
194 
195 // attribute = value
196 
197                 if (t instanceof String) {
198                     s = (String)t;
199                     t = x.nextToken();
200                     if (t == EQ) {
201                         t = x.nextToken();
202                         if (!(t instanceof String)) {
203                             throw x.syntaxError("Missing value");
204                         }
205                         o.accumulate(s, t);
206                         t = null;
207                     } else {
208                         o.accumulate(s, "");
209                     }
210 
211 // Empty tag <.../>
212 
213                 } else if (t == SLASH) {
214                     if (x.nextToken() != GT) {
215                         throw x.syntaxError("Misshaped tag");
216                     }
217                     context.accumulate(n, o);
218                     return false;
219 
220 // Content, between <...> and </...>
221 
222                 } else if (t == GT) {
223                     for (;;) {
224                         t = x.nextContent();
225                         if (t == null) {
226                             if (name != null) {
227                                 throw x.syntaxError("Unclosed tag " + name);
228                             }
229                             return false;
230                         } else if (t instanceof String) {
231                             s = (String)t;
232                             if (s.length() > 0) {
233                                 o.accumulate("content", s);
234                             }
235 
236 // Nested element
237 
238                         } else if (t == LT) {
239                             if (parse(x, o, n)) {
240                                 if (o.length() == 0) {
241                                     context.accumulate(n, "");
242                                 } else if (o.length() == 1 &&
243                                        o.opt("content") != null) {
244                                     context.accumulate(n, o.opt("content"));
245                                 } else {
246                                     context.accumulate(n, o);
247                                 }
248                                 return false;
249                             }
250                         }
251                     }
252                 } else {
253                     throw x.syntaxError("Misshaped tag");
254                 }
255             }
256         }
257     }
258 
259 
260     /**
261      * Convert a well-formed (but not necessarily valid) XML string into a
262      * JSONObject. Some information may be lost in this transformation
263      * because JSON is a data format and XML is a document format. XML uses
264      * elements, attributes, and content text, while JSON uses unordered
265      * collections of name/value pairs and arrays of values. JSON does not
266      * does not like to distinguish between elements and attributes.
267      * Sequences of similar elements are represented as JSONArrays. Content
268      * text may be placed in a "content" member. Comments, prologs, DTDs, and
269      * <code>&lt;[ [ ]]></code> are ignored.
270      * @param string The source string.
271      * @return A JSONObject containing the structured data from the XML string.
272      * @throws JSONException
273      */
274     public static JSONObject toJSONObject(String string) throws JSONException {
275         JSONObject o = new JSONObject();
276         XMLTokener x = new XMLTokener(string);
277         while (x.more()) {
278             x.skipPast("<");
279             parse(x, o, null);
280         }
281         return o;
282     }
283 
284 
285     /**
286      * Convert a JSONObject into a well-formed, element-normal XML string.
287      * @param o A JSONObject.
288      * @return  A string.
289      * @throws  JSONException
290      */
291     public static String toString(Object o) throws JSONException {
292         return toString(o, null);
293     }
294 
295 
296     /**
297      * Convert a JSONObject into a well-formed, element-normal XML string.
298      * @param o A JSONObject.
299      * @param tagName The optional name of the enclosing tag.
300      * @return A string.
301      * @throws JSONException
302      */
303     public static String toString(Object o, String tagName)
304             throws JSONException {
305         StringBuffer b = new StringBuffer();
306         int          i;
307         JSONArray    ja;
308         JSONObject   jo;
309         String       k;
310         Iterator     keys;
311         int          len;
312         String       s;
313         Object       v;
314         if (o instanceof JSONObject) {
315 
316 // Emit <tagName>
317 
318             if (tagName != null) {
319                 b.append('<');
320                 b.append(tagName);
321                 b.append('>');
322             }
323 
324 // Loop thru the keys.
325 
326             jo = (JSONObject)o;
327             keys = jo.keys();
328             while (keys.hasNext()) {
329                 k = keys.next().toString();
330                 v = jo.get(k);
331                 if (v instanceof String) {
332                     s = (String)v;
333                 } else {
334                     s = null;
335                 }
336 
337 // Emit content in body
338 
339                 if (k.equals("content")) {
340                     if (v instanceof JSONArray) {
341                         ja = (JSONArray)v;
342                         len = ja.length();
343                         for (i = 0; i < len; i += 1) {
344                             if (i > 0) {
345                                 b.append('\n');
346                             }
347                             b.append(escape(ja.get(i).toString()));
348                         }
349                     } else {
350                         b.append(escape(v.toString()));
351                     }
352 
353 // Emit an array of similar keys
354 
355                 } else if (v instanceof JSONArray) {
356                     ja = (JSONArray)v;
357                     len = ja.length();
358                     for (i = 0; i < len; i += 1) {
359                         b.append(toString(ja.get(i), k));
360                     }
361                 } else if (v.equals("")) {
362                     b.append('<');
363                     b.append(k);
364                     b.append("/>");
365 
366 // Emit a new tag <k>
367 
368                 } else {
369                     b.append(toString(v, k));
370                 }
371             }
372             if (tagName != null) {
373 
374 // Emit the </tagname> close tag
375 
376                 b.append("</");
377                 b.append(tagName);
378                 b.append('>');
379             }
380             return b.toString();
381 
382 // XML does not have good support for arrays. If an array appears in a place
383 // where XML is lacking, synthesize an <array> element.
384 
385         } else if (o instanceof JSONArray) {
386             ja = (JSONArray)o;
387             len = ja.length();
388             for (i = 0; i < len; ++i) {
389                 b.append(toString(
390                     ja.opt(i), (tagName == null) ? "array" : tagName));
391             }
392             return b.toString();
393         } else {
394             s = (o == null) ? "null" : escape(o.toString());
395             return (tagName == null) ? "\"" + s + "\"" :
396                 (s.length() == 0) ? "<" + tagName + "/>" :
397                 "<" + tagName + ">" + s + "</" + tagName + ">";
398         }
399     }
400 }