JAL-3438 spotless for 2.11.2.0
[jalview.git] / src / org / json / JSONML.java
1 package org.json;
2
3 /*
4 Copyright (c) 2008 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 /**
28  * This provides static methods to convert an XML text into a JSONArray or
29  * JSONObject, and to covert a JSONArray or JSONObject into an XML text using
30  * the JsonML transform.
31  *
32  * @author JSON.org
33  * @version 2016-01-30
34  */
35 public class JSONML
36 {
37   /**
38    * Parse XML values and store them in a JSONArray.
39    * 
40    * @param x
41    *          The XMLTokener containing the source string.
42    * @param arrayForm
43    *          true if array form, false if object form.
44    * @param ja
45    *          The JSONArray that is containing the current tag or null if we are
46    *          at the outermost level.
47    * @param keepStrings
48    *          Don't type-convert text nodes and attribute values
49    * @return A JSONArray if the value is the outermost tag, otherwise null.
50    * @throws JSONException
51    */
52   private static Object parse(XMLTokener x, boolean arrayForm, JSONArray ja,
53           boolean keepStrings) throws JSONException
54   {
55     String attribute;
56     char c;
57     String closeTag = null;
58     int i;
59     JSONArray newja = null;
60     JSONObject newjo = null;
61     Object token;
62     String tagName = null;
63
64     // Test for and skip past these forms:
65     // <!-- ... -->
66     // <![ ... ]]>
67     // <! ... >
68     // <? ... ?>
69
70     while (true)
71     {
72       if (!x.more())
73       {
74         throw x.syntaxError("Bad XML");
75       }
76       token = x.nextContent();
77       if (token == XML.LT)
78       {
79         token = x.nextToken();
80         if (token instanceof Character)
81         {
82           if (token == XML.SLASH)
83           {
84
85             // Close tag </
86
87             token = x.nextToken();
88             if (!(token instanceof String))
89             {
90               throw new JSONException("Expected a closing name instead of '"
91                       + token + "'.");
92             }
93             if (x.nextToken() != XML.GT)
94             {
95               throw x.syntaxError("Misshaped close tag");
96             }
97             return token;
98           }
99           else if (token == XML.BANG)
100           {
101
102             // <!
103
104             c = x.next();
105             if (c == '-')
106             {
107               if (x.next() == '-')
108               {
109                 x.skipPast("-->");
110               }
111               else
112               {
113                 x.back();
114               }
115             }
116             else if (c == '[')
117             {
118               token = x.nextToken();
119               if (token.equals("CDATA") && x.next() == '[')
120               {
121                 if (ja != null)
122                 {
123                   ja.put(x.nextCDATA());
124                 }
125               }
126               else
127               {
128                 throw x.syntaxError("Expected 'CDATA['");
129               }
130             }
131             else
132             {
133               i = 1;
134               do
135               {
136                 token = x.nextMeta();
137                 if (token == null)
138                 {
139                   throw x.syntaxError("Missing '>' after '<!'.");
140                 }
141                 else if (token == XML.LT)
142                 {
143                   i += 1;
144                 }
145                 else if (token == XML.GT)
146                 {
147                   i -= 1;
148                 }
149               } while (i > 0);
150             }
151           }
152           else if (token == XML.QUEST)
153           {
154
155             // <?
156
157             x.skipPast("?>");
158           }
159           else
160           {
161             throw x.syntaxError("Misshaped tag");
162           }
163
164           // Open tag <
165
166         }
167         else
168         {
169           if (!(token instanceof String))
170           {
171             throw x.syntaxError("Bad tagName '" + token + "'.");
172           }
173           tagName = (String) token;
174           newja = new JSONArray();
175           newjo = new JSONObject();
176           if (arrayForm)
177           {
178             newja.put(tagName);
179             if (ja != null)
180             {
181               ja.put(newja);
182             }
183           }
184           else
185           {
186             newjo.put("tagName", tagName);
187             if (ja != null)
188             {
189               ja.put(newjo);
190             }
191           }
192           token = null;
193           for (;;)
194           {
195             if (token == null)
196             {
197               token = x.nextToken();
198             }
199             if (token == null)
200             {
201               throw x.syntaxError("Misshaped tag");
202             }
203             if (!(token instanceof String))
204             {
205               break;
206             }
207
208             // attribute = value
209
210             attribute = (String) token;
211             if (!arrayForm && ("tagName".equals(attribute)
212                     || "childNode".equals(attribute)))
213             {
214               throw x.syntaxError("Reserved attribute.");
215             }
216             token = x.nextToken();
217             if (token == XML.EQ)
218             {
219               token = x.nextToken();
220               if (!(token instanceof String))
221               {
222                 throw x.syntaxError("Missing value");
223               }
224               newjo.accumulate(attribute, keepStrings ? ((String) token)
225                       : XML.stringToValue((String) token));
226               token = null;
227             }
228             else
229             {
230               newjo.accumulate(attribute, "");
231             }
232           }
233           if (arrayForm && newjo.length() > 0)
234           {
235             newja.put(newjo);
236           }
237
238           // Empty tag <.../>
239
240           if (token == XML.SLASH)
241           {
242             if (x.nextToken() != XML.GT)
243             {
244               throw x.syntaxError("Misshaped tag");
245             }
246             if (ja == null)
247             {
248               if (arrayForm)
249               {
250                 return newja;
251               }
252               return newjo;
253             }
254
255             // Content, between <...> and </...>
256
257           }
258           else
259           {
260             if (token != XML.GT)
261             {
262               throw x.syntaxError("Misshaped tag");
263             }
264             closeTag = (String) parse(x, arrayForm, newja, keepStrings);
265             if (closeTag != null)
266             {
267               if (!closeTag.equals(tagName))
268               {
269                 throw x.syntaxError("Mismatched '" + tagName + "' and '"
270                         + closeTag + "'");
271               }
272               tagName = null;
273               if (!arrayForm && newja.length() > 0)
274               {
275                 newjo.put("childNodes", newja);
276               }
277               if (ja == null)
278               {
279                 if (arrayForm)
280                 {
281                   return newja;
282                 }
283                 return newjo;
284               }
285             }
286           }
287         }
288       }
289       else
290       {
291         if (ja != null)
292         {
293           ja.put(token instanceof String
294                   ? keepStrings ? XML.unescape((String) token)
295                           : XML.stringToValue((String) token)
296                   : token);
297         }
298       }
299     }
300   }
301
302   /**
303    * Convert a well-formed (but not necessarily valid) XML string into a
304    * JSONArray using the JsonML transform. Each XML tag is represented as a
305    * JSONArray in which the first element is the tag name. If the tag has
306    * attributes, then the second element will be JSONObject containing the
307    * name/value pairs. If the tag contains children, then strings and JSONArrays
308    * will represent the child tags. Comments, prologs, DTDs, and
309    * <code>&lt;[ [ ]]></code> are ignored.
310    * 
311    * @param string
312    *          The source string.
313    * @return A JSONArray containing the structured data from the XML string.
314    * @throws JSONException
315    *           Thrown on error converting to a JSONArray
316    */
317   public static JSONArray toJSONArray(String string) throws JSONException
318   {
319     return (JSONArray) parse(new XMLTokener(string), true, null, false);
320   }
321
322   /**
323    * Convert a well-formed (but not necessarily valid) XML string into a
324    * JSONArray using the JsonML transform. Each XML tag is represented as a
325    * JSONArray in which the first element is the tag name. If the tag has
326    * attributes, then the second element will be JSONObject containing the
327    * name/value pairs. If the tag contains children, then strings and JSONArrays
328    * will represent the child tags. As opposed to toJSONArray this method does
329    * not attempt to convert any text node or attribute value to any type but
330    * just leaves it as a string. Comments, prologs, DTDs, and
331    * <code>&lt;[ [ ]]></code> are ignored.
332    * 
333    * @param string
334    *          The source string.
335    * @param keepStrings
336    *          If true, then values will not be coerced into boolean or numeric
337    *          values and will instead be left as strings
338    * @return A JSONArray containing the structured data from the XML string.
339    * @throws JSONException
340    *           Thrown on error converting to a JSONArray
341    */
342   public static JSONArray toJSONArray(String string, boolean keepStrings)
343           throws JSONException
344   {
345     return (JSONArray) parse(new XMLTokener(string), true, null,
346             keepStrings);
347   }
348
349   /**
350    * Convert a well-formed (but not necessarily valid) XML string into a
351    * JSONArray using the JsonML transform. Each XML tag is represented as a
352    * JSONArray in which the first element is the tag name. If the tag has
353    * attributes, then the second element will be JSONObject containing the
354    * name/value pairs. If the tag contains children, then strings and JSONArrays
355    * will represent the child content and tags. As opposed to toJSONArray this
356    * method does not attempt to convert any text node or attribute value to any
357    * type but just leaves it as a string. Comments, prologs, DTDs, and
358    * <code>&lt;[ [ ]]></code> are ignored.
359    * 
360    * @param x
361    *          An XMLTokener.
362    * @param keepStrings
363    *          If true, then values will not be coerced into boolean or numeric
364    *          values and will instead be left as strings
365    * @return A JSONArray containing the structured data from the XML string.
366    * @throws JSONException
367    *           Thrown on error converting to a JSONArray
368    */
369   public static JSONArray toJSONArray(XMLTokener x, boolean keepStrings)
370           throws JSONException
371   {
372     return (JSONArray) parse(x, true, null, keepStrings);
373   }
374
375   /**
376    * Convert a well-formed (but not necessarily valid) XML string into a
377    * JSONArray using the JsonML transform. Each XML tag is represented as a
378    * JSONArray in which the first element is the tag name. If the tag has
379    * attributes, then the second element will be JSONObject containing the
380    * name/value pairs. If the tag contains children, then strings and JSONArrays
381    * will represent the child content and tags. Comments, prologs, DTDs, and
382    * <code>&lt;[ [ ]]></code> are ignored.
383    * 
384    * @param x
385    *          An XMLTokener.
386    * @return A JSONArray containing the structured data from the XML string.
387    * @throws JSONException
388    *           Thrown on error converting to a JSONArray
389    */
390   public static JSONArray toJSONArray(XMLTokener x) throws JSONException
391   {
392     return (JSONArray) parse(x, true, null, false);
393   }
394
395   /**
396    * Convert a well-formed (but not necessarily valid) XML string into a
397    * JSONObject using the JsonML transform. Each XML tag is represented as a
398    * JSONObject with a "tagName" property. If the tag has attributes, then the
399    * attributes will be in the JSONObject as properties. If the tag contains
400    * children, the object will have a "childNodes" property which will be an
401    * array of strings and JsonML JSONObjects.
402    * 
403    * Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
404    * 
405    * @param string
406    *          The XML source text.
407    * @return A JSONObject containing the structured data from the XML string.
408    * @throws JSONException
409    *           Thrown on error converting to a JSONObject
410    */
411   public static JSONObject toJSONObject(String string) throws JSONException
412   {
413     return (JSONObject) parse(new XMLTokener(string), false, null, false);
414   }
415
416   /**
417    * Convert a well-formed (but not necessarily valid) XML string into a
418    * JSONObject using the JsonML transform. Each XML tag is represented as a
419    * JSONObject with a "tagName" property. If the tag has attributes, then the
420    * attributes will be in the JSONObject as properties. If the tag contains
421    * children, the object will have a "childNodes" property which will be an
422    * array of strings and JsonML JSONObjects.
423    * 
424    * Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
425    * 
426    * @param string
427    *          The XML source text.
428    * @param keepStrings
429    *          If true, then values will not be coerced into boolean or numeric
430    *          values and will instead be left as strings
431    * @return A JSONObject containing the structured data from the XML string.
432    * @throws JSONException
433    *           Thrown on error converting to a JSONObject
434    */
435   public static JSONObject toJSONObject(String string, boolean keepStrings)
436           throws JSONException
437   {
438     return (JSONObject) parse(new XMLTokener(string), false, null,
439             keepStrings);
440   }
441
442   /**
443    * Convert a well-formed (but not necessarily valid) XML string into a
444    * JSONObject using the JsonML transform. Each XML tag is represented as a
445    * JSONObject with a "tagName" property. If the tag has attributes, then the
446    * attributes will be in the JSONObject as properties. If the tag contains
447    * children, the object will have a "childNodes" property which will be an
448    * array of strings and JsonML JSONObjects.
449    * 
450    * Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
451    * 
452    * @param x
453    *          An XMLTokener of the XML source text.
454    * @return A JSONObject containing the structured data from the XML string.
455    * @throws JSONException
456    *           Thrown on error converting to a JSONObject
457    */
458   public static JSONObject toJSONObject(XMLTokener x) throws JSONException
459   {
460     return (JSONObject) parse(x, false, null, false);
461   }
462
463   /**
464    * Convert a well-formed (but not necessarily valid) XML string into a
465    * JSONObject using the JsonML transform. Each XML tag is represented as a
466    * JSONObject with a "tagName" property. If the tag has attributes, then the
467    * attributes will be in the JSONObject as properties. If the tag contains
468    * children, the object will have a "childNodes" property which will be an
469    * array of strings and JsonML JSONObjects.
470    * 
471    * Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
472    * 
473    * @param x
474    *          An XMLTokener of the XML source text.
475    * @param keepStrings
476    *          If true, then values will not be coerced into boolean or numeric
477    *          values and will instead be left as strings
478    * @return A JSONObject containing the structured data from the XML string.
479    * @throws JSONException
480    *           Thrown on error converting to a JSONObject
481    */
482   public static JSONObject toJSONObject(XMLTokener x, boolean keepStrings)
483           throws JSONException
484   {
485     return (JSONObject) parse(x, false, null, keepStrings);
486   }
487
488   /**
489    * Reverse the JSONML transformation, making an XML text from a JSONArray.
490    * 
491    * @param ja
492    *          A JSONArray.
493    * @return An XML string.
494    * @throws JSONException
495    *           Thrown on error converting to a string
496    */
497   public static String toString(JSONArray ja) throws JSONException
498   {
499     int i;
500     JSONObject jo;
501     int length;
502     Object object;
503     StringBuilder sb = new StringBuilder();
504     String tagName;
505
506     // Emit <tagName
507
508     tagName = ja.getString(0);
509     XML.noSpace(tagName);
510     tagName = XML.escape(tagName);
511     sb.append('<');
512     sb.append(tagName);
513
514     object = ja.opt(1);
515     if (object instanceof JSONObject)
516     {
517       i = 2;
518       jo = (JSONObject) object;
519
520       // Emit the attributes
521
522       // Don't use the new entrySet API to maintain Android support
523       for (final String key : jo.keySet())
524       {
525         final Object value = jo.opt(key);
526         XML.noSpace(key);
527         if (value != null)
528         {
529           sb.append(' ');
530           sb.append(XML.escape(key));
531           sb.append('=');
532           sb.append('"');
533           sb.append(XML.escape(value.toString()));
534           sb.append('"');
535         }
536       }
537     }
538     else
539     {
540       i = 1;
541     }
542
543     // Emit content in body
544
545     length = ja.length();
546     if (i >= length)
547     {
548       sb.append('/');
549       sb.append('>');
550     }
551     else
552     {
553       sb.append('>');
554       do
555       {
556         object = ja.get(i);
557         i += 1;
558         if (object != null)
559         {
560           if (object instanceof String)
561           {
562             sb.append(XML.escape(object.toString()));
563           }
564           else if (object instanceof JSONObject)
565           {
566             sb.append(toString((JSONObject) object));
567           }
568           else if (object instanceof JSONArray)
569           {
570             sb.append(toString((JSONArray) object));
571           }
572           else
573           {
574             sb.append(object.toString());
575           }
576         }
577       } while (i < length);
578       sb.append('<');
579       sb.append('/');
580       sb.append(tagName);
581       sb.append('>');
582     }
583     return sb.toString();
584   }
585
586   /**
587    * Reverse the JSONML transformation, making an XML text from a JSONObject.
588    * The JSONObject must contain a "tagName" property. If it has children, then
589    * it must have a "childNodes" property containing an array of objects. The
590    * other properties are attributes with string values.
591    * 
592    * @param jo
593    *          A JSONObject.
594    * @return An XML string.
595    * @throws JSONException
596    *           Thrown on error converting to a string
597    */
598   public static String toString(JSONObject jo) throws JSONException
599   {
600     StringBuilder sb = new StringBuilder();
601     int i;
602     JSONArray ja;
603     int length;
604     Object object;
605     String tagName;
606     Object value;
607
608     // Emit <tagName
609
610     tagName = jo.optString("tagName");
611     if (tagName == null)
612     {
613       return XML.escape(jo.toString());
614     }
615     XML.noSpace(tagName);
616     tagName = XML.escape(tagName);
617     sb.append('<');
618     sb.append(tagName);
619
620     // Emit the attributes
621
622     // Don't use the new entrySet API to maintain Android support
623     for (final String key : jo.keySet())
624     {
625       if (!"tagName".equals(key) && !"childNodes".equals(key))
626       {
627         XML.noSpace(key);
628         value = jo.opt(key);
629         if (value != null)
630         {
631           sb.append(' ');
632           sb.append(XML.escape(key));
633           sb.append('=');
634           sb.append('"');
635           sb.append(XML.escape(value.toString()));
636           sb.append('"');
637         }
638       }
639     }
640
641     // Emit content in body
642
643     ja = jo.optJSONArray("childNodes");
644     if (ja == null)
645     {
646       sb.append('/');
647       sb.append('>');
648     }
649     else
650     {
651       sb.append('>');
652       length = ja.length();
653       for (i = 0; i < length; i += 1)
654       {
655         object = ja.get(i);
656         if (object != null)
657         {
658           if (object instanceof String)
659           {
660             sb.append(XML.escape(object.toString()));
661           }
662           else if (object instanceof JSONObject)
663           {
664             sb.append(toString((JSONObject) object));
665           }
666           else if (object instanceof JSONArray)
667           {
668             sb.append(toString((JSONArray) object));
669           }
670           else
671           {
672             sb.append(object.toString());
673           }
674         }
675       }
676       sb.append('<');
677       sb.append('/');
678       sb.append(tagName);
679       sb.append('>');
680     }
681     return sb.toString();
682   }
683 }