[DMAAP-48] Initial code import
[dmaap/datarouter.git] / datarouter-prov / src / main / java / org / json / XML.java
1 /*******************************************************************************\r
2  * ============LICENSE_START==================================================\r
3  * * org.onap.dmaap\r
4  * * ===========================================================================\r
5  * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.\r
6  * * ===========================================================================\r
7  * * Licensed under the Apache License, Version 2.0 (the "License");\r
8  * * you may not use this file except in compliance with the License.\r
9  * * You may obtain a copy of the License at\r
10  * * \r
11  *  *      http://www.apache.org/licenses/LICENSE-2.0\r
12  * * \r
13  *  * Unless required by applicable law or agreed to in writing, software\r
14  * * distributed under the License is distributed on an "AS IS" BASIS,\r
15  * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
16  * * See the License for the specific language governing permissions and\r
17  * * limitations under the License.\r
18  * * ============LICENSE_END====================================================\r
19  * *\r
20  * * ECOMP is a trademark and service mark of AT&T Intellectual Property.\r
21  * *\r
22  ******************************************************************************/\r
23 package org.json;\r
24 \r
25 /*\r
26 Copyright (c) 2002 JSON.org\r
27 \r
28 Permission is hereby granted, free of charge, to any person obtaining a copy\r
29 of this software and associated documentation files (the "Software"), to deal\r
30 in the Software without restriction, including without limitation the rights\r
31 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
32 copies of the Software, and to permit persons to whom the Software is\r
33 furnished to do so, subject to the following conditions:\r
34 \r
35 The above copyright notice and this permission notice shall be included in all\r
36 copies or substantial portions of the Software.\r
37 \r
38 The Software shall be used for Good, not Evil.\r
39 \r
40 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
41 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
42 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
43 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
44 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
45 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r
46 SOFTWARE.\r
47 */\r
48 \r
49 import java.util.Iterator;\r
50 \r
51 \r
52 /**\r
53  * This provides static methods to convert an XML text into a JSONObject,\r
54  * and to covert a JSONObject into an XML text.\r
55  * @author JSON.org\r
56  * @version 2012-10-26\r
57  */\r
58 public class XML {\r
59 \r
60     /** The Character '&'. */\r
61     public static final Character AMP   = new Character('&');\r
62 \r
63     /** The Character '''. */\r
64     public static final Character APOS  = new Character('\'');\r
65 \r
66     /** The Character '!'. */\r
67     public static final Character BANG  = new Character('!');\r
68 \r
69     /** The Character '='. */\r
70     public static final Character EQ    = new Character('=');\r
71 \r
72     /** The Character '>'. */\r
73     public static final Character GT    = new Character('>');\r
74 \r
75     /** The Character '<'. */\r
76     public static final Character LT    = new Character('<');\r
77 \r
78     /** The Character '?'. */\r
79     public static final Character QUEST = new Character('?');\r
80 \r
81     /** The Character '"'. */\r
82     public static final Character QUOT  = new Character('"');\r
83 \r
84     /** The Character '/'. */\r
85     public static final Character SLASH = new Character('/');\r
86 \r
87     /**\r
88      * Replace special characters with XML escapes:\r
89      * <pre>\r
90      * &amp; <small>(ampersand)</small> is replaced by &amp;amp;\r
91      * &lt; <small>(less than)</small> is replaced by &amp;lt;\r
92      * &gt; <small>(greater than)</small> is replaced by &amp;gt;\r
93      * &quot; <small>(double quote)</small> is replaced by &amp;quot;\r
94      * </pre>\r
95      * @param string The string to be escaped.\r
96      * @return The escaped string.\r
97      */\r
98     public static String escape(String string) {\r
99         StringBuffer sb = new StringBuffer();\r
100         for (int i = 0, length = string.length(); i < length; i++) {\r
101             char c = string.charAt(i);\r
102             switch (c) {\r
103             case '&':\r
104                 sb.append("&amp;");\r
105                 break;\r
106             case '<':\r
107                 sb.append("&lt;");\r
108                 break;\r
109             case '>':\r
110                 sb.append("&gt;");\r
111                 break;\r
112             case '"':\r
113                 sb.append("&quot;");\r
114                 break;\r
115             case '\'':\r
116                 sb.append("&apos;");\r
117                 break;\r
118             default:\r
119                 sb.append(c);\r
120             }\r
121         }\r
122         return sb.toString();\r
123     }\r
124 \r
125     /**\r
126      * Throw an exception if the string contains whitespace.\r
127      * Whitespace is not allowed in tagNames and attributes.\r
128      * @param string\r
129      * @throws JSONException\r
130      */\r
131     public static void noSpace(String string) throws JSONException {\r
132         int i, length = string.length();\r
133         if (length == 0) {\r
134             throw new JSONException("Empty string.");\r
135         }\r
136         for (i = 0; i < length; i += 1) {\r
137             if (Character.isWhitespace(string.charAt(i))) {\r
138                 throw new JSONException("'" + string +\r
139                         "' contains a space character.");\r
140             }\r
141         }\r
142     }\r
143 \r
144     /**\r
145      * Scan the content following the named tag, attaching it to the context.\r
146      * @param x       The XMLTokener containing the source string.\r
147      * @param context The JSONObject that will include the new material.\r
148      * @param name    The tag name.\r
149      * @return true if the close tag is processed.\r
150      * @throws JSONException\r
151      */\r
152     private static boolean parse(XMLTokener x, JSONObject context,\r
153                                  String name) throws JSONException {\r
154         char       c;\r
155         int        i;\r
156         JSONObject jsonobject = null;\r
157         String     string;\r
158         String     tagName;\r
159         Object     token;\r
160 \r
161 // Test for and skip past these forms:\r
162 //      <!-- ... -->\r
163 //      <!   ...   >\r
164 //      <![  ... ]]>\r
165 //      <?   ...  ?>\r
166 // Report errors for these forms:\r
167 //      <>\r
168 //      <=\r
169 //      <<\r
170 \r
171         token = x.nextToken();\r
172 \r
173 // <!\r
174 \r
175         if (token == BANG) {\r
176             c = x.next();\r
177             if (c == '-') {\r
178                 if (x.next() == '-') {\r
179                     x.skipPast("-->");\r
180                     return false;\r
181                 }\r
182                 x.back();\r
183             } else if (c == '[') {\r
184                 token = x.nextToken();\r
185                 if ("CDATA".equals(token)) {\r
186                     if (x.next() == '[') {\r
187                         string = x.nextCDATA();\r
188                         if (string.length() > 0) {\r
189                             context.accumulate("content", string);\r
190                         }\r
191                         return false;\r
192                     }\r
193                 }\r
194                 throw x.syntaxError("Expected 'CDATA['");\r
195             }\r
196             i = 1;\r
197             do {\r
198                 token = x.nextMeta();\r
199                 if (token == null) {\r
200                     throw x.syntaxError("Missing '>' after '<!'.");\r
201                 } else if (token == LT) {\r
202                     i += 1;\r
203                 } else if (token == GT) {\r
204                     i -= 1;\r
205                 }\r
206             } while (i > 0);\r
207             return false;\r
208         } else if (token == QUEST) {\r
209 \r
210 // <?\r
211 \r
212             x.skipPast("?>");\r
213             return false;\r
214         } else if (token == SLASH) {\r
215 \r
216 // Close tag </\r
217 \r
218             token = x.nextToken();\r
219             if (name == null) {\r
220                 throw x.syntaxError("Mismatched close tag " + token);\r
221             }\r
222             if (!token.equals(name)) {\r
223                 throw x.syntaxError("Mismatched " + name + " and " + token);\r
224             }\r
225             if (x.nextToken() != GT) {\r
226                 throw x.syntaxError("Misshaped close tag");\r
227             }\r
228             return true;\r
229 \r
230         } else if (token instanceof Character) {\r
231             throw x.syntaxError("Misshaped tag");\r
232 \r
233 // Open tag <\r
234 \r
235         } else {\r
236             tagName = (String)token;\r
237             token = null;\r
238             jsonobject = new JSONObject();\r
239             for (;;) {\r
240                 if (token == null) {\r
241                     token = x.nextToken();\r
242                 }\r
243 \r
244 // attribute = value\r
245 \r
246                 if (token instanceof String) {\r
247                     string = (String)token;\r
248                     token = x.nextToken();\r
249                     if (token == EQ) {\r
250                         token = x.nextToken();\r
251                         if (!(token instanceof String)) {\r
252                             throw x.syntaxError("Missing value");\r
253                         }\r
254                         jsonobject.accumulate(string,\r
255                                 XML.stringToValue((String)token));\r
256                         token = null;\r
257                     } else {\r
258                         jsonobject.accumulate(string, "");\r
259                     }\r
260 \r
261 // Empty tag <.../>\r
262 \r
263                 } else if (token == SLASH) {\r
264                     if (x.nextToken() != GT) {\r
265                         throw x.syntaxError("Misshaped tag");\r
266                     }\r
267                     if (jsonobject.length() > 0) {\r
268                         context.accumulate(tagName, jsonobject);\r
269                     } else {\r
270                         context.accumulate(tagName, "");\r
271                     }\r
272                     return false;\r
273 \r
274 // Content, between <...> and </...>\r
275 \r
276                 } else if (token == GT) {\r
277                     for (;;) {\r
278                         token = x.nextContent();\r
279                         if (token == null) {\r
280                             if (tagName != null) {\r
281                                 throw x.syntaxError("Unclosed tag " + tagName);\r
282                             }\r
283                             return false;\r
284                         } else if (token instanceof String) {\r
285                             string = (String)token;\r
286                             if (string.length() > 0) {\r
287                                 jsonobject.accumulate("content",\r
288                                         XML.stringToValue(string));\r
289                             }\r
290 \r
291 // Nested element\r
292 \r
293                         } else if (token == LT) {\r
294                             if (parse(x, jsonobject, tagName)) {\r
295                                 if (jsonobject.length() == 0) {\r
296                                     context.accumulate(tagName, "");\r
297                                 } else if (jsonobject.length() == 1 &&\r
298                                        jsonobject.opt("content") != null) {\r
299                                     context.accumulate(tagName,\r
300                                             jsonobject.opt("content"));\r
301                                 } else {\r
302                                     context.accumulate(tagName, jsonobject);\r
303                                 }\r
304                                 return false;\r
305                             }\r
306                         }\r
307                     }\r
308                 } else {\r
309                     throw x.syntaxError("Misshaped tag");\r
310                 }\r
311             }\r
312         }\r
313     }\r
314 \r
315 \r
316     /**\r
317      * Try to convert a string into a number, boolean, or null. If the string\r
318      * can't be converted, return the string. This is much less ambitious than\r
319      * JSONObject.stringToValue, especially because it does not attempt to\r
320      * convert plus forms, octal forms, hex forms, or E forms lacking decimal\r
321      * points.\r
322      * @param string A String.\r
323      * @return A simple JSON value.\r
324      */\r
325     public static Object stringToValue(String string) {\r
326         if ("".equals(string)) {\r
327             return string;\r
328         }\r
329         if ("true".equalsIgnoreCase(string)) {\r
330             return Boolean.TRUE;\r
331         }\r
332         if ("false".equalsIgnoreCase(string)) {\r
333             return Boolean.FALSE;\r
334         }\r
335         if ("null".equalsIgnoreCase(string)) {\r
336             return JSONObject.NULL;\r
337         }\r
338         if ("0".equals(string)) {\r
339             return new Integer(0);\r
340         }\r
341 \r
342 // If it might be a number, try converting it. If that doesn't work,\r
343 // return the string.\r
344 \r
345         try {\r
346             char initial = string.charAt(0);\r
347             boolean negative = false;\r
348             if (initial == '-') {\r
349                 initial = string.charAt(1);\r
350                 negative = true;\r
351             }\r
352             if (initial == '0' && string.charAt(negative ? 2 : 1) == '0') {\r
353                 return string;\r
354             }\r
355             if ((initial >= '0' && initial <= '9')) {\r
356                 if (string.indexOf('.') >= 0) {\r
357                     return Double.valueOf(string);\r
358                 } else if (string.indexOf('e') < 0 && string.indexOf('E') < 0) {\r
359                     Long myLong = new Long(string);\r
360                     if (myLong.longValue() == myLong.intValue()) {\r
361                         return new Integer(myLong.intValue());\r
362                     } else {\r
363                         return myLong;\r
364                     }\r
365                 }\r
366             }\r
367         }  catch (Exception ignore) {\r
368         }\r
369         return string;\r
370     }\r
371 \r
372 \r
373     /**\r
374      * Convert a well-formed (but not necessarily valid) XML string into a\r
375      * JSONObject. Some information may be lost in this transformation\r
376      * because JSON is a data format and XML is a document format. XML uses\r
377      * elements, attributes, and content text, while JSON uses unordered\r
378      * collections of name/value pairs and arrays of values. JSON does not\r
379      * does not like to distinguish between elements and attributes.\r
380      * Sequences of similar elements are represented as JSONArrays. Content\r
381      * text may be placed in a "content" member. Comments, prologs, DTDs, and\r
382      * <code>&lt;[ [ ]]></code> are ignored.\r
383      * @param string The source string.\r
384      * @return A JSONObject containing the structured data from the XML string.\r
385      * @throws JSONException\r
386      */\r
387     public static JSONObject toJSONObject(String string) throws JSONException {\r
388         JSONObject jo = new JSONObject();\r
389         XMLTokener x = new XMLTokener(string);\r
390         while (x.more() && x.skipPast("<")) {\r
391             parse(x, jo, null);\r
392         }\r
393         return jo;\r
394     }\r
395 \r
396 \r
397     /**\r
398      * Convert a JSONObject into a well-formed, element-normal XML string.\r
399      * @param object A JSONObject.\r
400      * @return  A string.\r
401      * @throws  JSONException\r
402      */\r
403     public static String toString(Object object) throws JSONException {\r
404         return toString(object, null);\r
405     }\r
406 \r
407 \r
408     /**\r
409      * Convert a JSONObject into a well-formed, element-normal XML string.\r
410      * @param object A JSONObject.\r
411      * @param tagName The optional name of the enclosing tag.\r
412      * @return A string.\r
413      * @throws JSONException\r
414      */\r
415     public static String toString(Object object, String tagName)\r
416             throws JSONException {\r
417         StringBuffer sb = new StringBuffer();\r
418         int          i;\r
419         JSONArray    ja;\r
420         JSONObject   jo;\r
421         String       key;\r
422         Iterator<String> keys;\r
423         int          length;\r
424         String       string;\r
425         Object       value;\r
426         if (object instanceof JSONObject) {\r
427 \r
428 // Emit <tagName>\r
429 \r
430             if (tagName != null) {\r
431                 sb.append('<');\r
432                 sb.append(tagName);\r
433                 sb.append('>');\r
434             }\r
435 \r
436 // Loop thru the keys.\r
437 \r
438             jo = (JSONObject)object;\r
439             keys = jo.keys();\r
440             while (keys.hasNext()) {\r
441                 key = keys.next().toString();\r
442                 value = jo.opt(key);\r
443                 if (value == null) {\r
444                     value = "";\r
445                 }\r
446                 if (value instanceof String) {\r
447                     string = (String)value;\r
448                 } else {\r
449                     string = null;\r
450                 }\r
451 \r
452 // Emit content in body\r
453 \r
454                 if ("content".equals(key)) {\r
455                     if (value instanceof JSONArray) {\r
456                         ja = (JSONArray)value;\r
457                         length = ja.length();\r
458                         for (i = 0; i < length; i += 1) {\r
459                             if (i > 0) {\r
460                                 sb.append('\n');\r
461                             }\r
462                             sb.append(escape(ja.get(i).toString()));\r
463                         }\r
464                     } else {\r
465                         sb.append(escape(value.toString()));\r
466                     }\r
467 \r
468 // Emit an array of similar keys\r
469 \r
470                 } else if (value instanceof JSONArray) {\r
471                     ja = (JSONArray)value;\r
472                     length = ja.length();\r
473                     for (i = 0; i < length; i += 1) {\r
474                         value = ja.get(i);\r
475                         if (value instanceof JSONArray) {\r
476                             sb.append('<');\r
477                             sb.append(key);\r
478                             sb.append('>');\r
479                             sb.append(toString(value));\r
480                             sb.append("</");\r
481                             sb.append(key);\r
482                             sb.append('>');\r
483                         } else {\r
484                             sb.append(toString(value, key));\r
485                         }\r
486                     }\r
487                 } else if ("".equals(value)) {\r
488                     sb.append('<');\r
489                     sb.append(key);\r
490                     sb.append("/>");\r
491 \r
492 // Emit a new tag <k>\r
493 \r
494                 } else {\r
495                     sb.append(toString(value, key));\r
496                 }\r
497             }\r
498             if (tagName != null) {\r
499 \r
500 // Emit the </tagname> close tag\r
501 \r
502                 sb.append("</");\r
503                 sb.append(tagName);\r
504                 sb.append('>');\r
505             }\r
506             return sb.toString();\r
507 \r
508 // XML does not have good support for arrays. If an array appears in a place\r
509 // where XML is lacking, synthesize an <array> element.\r
510 \r
511         } else {\r
512             if (object.getClass().isArray()) {\r
513                 object = new JSONArray(object);\r
514             }\r
515             if (object instanceof JSONArray) {\r
516                 ja = (JSONArray)object;\r
517                 length = ja.length();\r
518                 for (i = 0; i < length; i += 1) {\r
519                     sb.append(toString(ja.opt(i), tagName == null ? "array" : tagName));\r
520                 }\r
521                 return sb.toString();\r
522             } else {\r
523                 string = (object == null) ? "null" : escape(object.toString());\r
524                 return (tagName == null) ? "\"" + string + "\"" :\r
525                     (string.length() == 0) ? "<" + tagName + "/>" :\r
526                     "<" + tagName + ">" + string + "</" + tagName + ">";\r
527             }\r
528         }\r
529     }\r
530 }\r