1 /*******************************************************************************
\r
2 * ============LICENSE_START==================================================
\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
11 * * http://www.apache.org/licenses/LICENSE-2.0
\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
20 * * ECOMP is a trademark and service mark of AT&T Intellectual Property.
\r
22 ******************************************************************************/
\r
26 Copyright (c) 2002 JSON.org
\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
35 The above copyright notice and this permission notice shall be included in all
\r
36 copies or substantial portions of the Software.
\r
38 The Software shall be used for Good, not Evil.
\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
49 import java.util.Iterator;
\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
56 * @version 2012-10-26
\r
60 /** The Character '&'. */
\r
61 public static final Character AMP = new Character('&');
\r
63 /** The Character '''. */
\r
64 public static final Character APOS = new Character('\'');
\r
66 /** The Character '!'. */
\r
67 public static final Character BANG = new Character('!');
\r
69 /** The Character '='. */
\r
70 public static final Character EQ = new Character('=');
\r
72 /** The Character '>'. */
\r
73 public static final Character GT = new Character('>');
\r
75 /** The Character '<'. */
\r
76 public static final Character LT = new Character('<');
\r
78 /** The Character '?'. */
\r
79 public static final Character QUEST = new Character('?');
\r
81 /** The Character '"'. */
\r
82 public static final Character QUOT = new Character('"');
\r
84 /** The Character '/'. */
\r
85 public static final Character SLASH = new Character('/');
\r
88 * Replace special characters with XML escapes:
\r
90 * & <small>(ampersand)</small> is replaced by &amp;
\r
91 * < <small>(less than)</small> is replaced by &lt;
\r
92 * > <small>(greater than)</small> is replaced by &gt;
\r
93 * " <small>(double quote)</small> is replaced by &quot;
\r
95 * @param string The string to be escaped.
\r
96 * @return The escaped string.
\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
104 sb.append("&");
\r
113 sb.append(""");
\r
116 sb.append("'");
\r
122 return sb.toString();
\r
126 * Throw an exception if the string contains whitespace.
\r
127 * Whitespace is not allowed in tagNames and attributes.
\r
129 * @throws JSONException
\r
131 public static void noSpace(String string) throws JSONException {
\r
132 int i, length = string.length();
\r
134 throw new JSONException("Empty string.");
\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
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
152 private static boolean parse(XMLTokener x, JSONObject context,
\r
153 String name) throws JSONException {
\r
156 JSONObject jsonobject = null;
\r
161 // Test for and skip past these forms:
\r
166 // Report errors for these forms:
\r
171 token = x.nextToken();
\r
175 if (token == BANG) {
\r
178 if (x.next() == '-') {
\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
194 throw x.syntaxError("Expected 'CDATA['");
\r
198 token = x.nextMeta();
\r
199 if (token == null) {
\r
200 throw x.syntaxError("Missing '>' after '<!'.");
\r
201 } else if (token == LT) {
\r
203 } else if (token == GT) {
\r
208 } else if (token == QUEST) {
\r
214 } else if (token == SLASH) {
\r
218 token = x.nextToken();
\r
219 if (name == null) {
\r
220 throw x.syntaxError("Mismatched close tag " + token);
\r
222 if (!token.equals(name)) {
\r
223 throw x.syntaxError("Mismatched " + name + " and " + token);
\r
225 if (x.nextToken() != GT) {
\r
226 throw x.syntaxError("Misshaped close tag");
\r
230 } else if (token instanceof Character) {
\r
231 throw x.syntaxError("Misshaped tag");
\r
236 tagName = (String)token;
\r
238 jsonobject = new JSONObject();
\r
240 if (token == null) {
\r
241 token = x.nextToken();
\r
244 // attribute = value
\r
246 if (token instanceof String) {
\r
247 string = (String)token;
\r
248 token = x.nextToken();
\r
250 token = x.nextToken();
\r
251 if (!(token instanceof String)) {
\r
252 throw x.syntaxError("Missing value");
\r
254 jsonobject.accumulate(string,
\r
255 XML.stringToValue((String)token));
\r
258 jsonobject.accumulate(string, "");
\r
261 // Empty tag <.../>
\r
263 } else if (token == SLASH) {
\r
264 if (x.nextToken() != GT) {
\r
265 throw x.syntaxError("Misshaped tag");
\r
267 if (jsonobject.length() > 0) {
\r
268 context.accumulate(tagName, jsonobject);
\r
270 context.accumulate(tagName, "");
\r
274 // Content, between <...> and </...>
\r
276 } else if (token == GT) {
\r
278 token = x.nextContent();
\r
279 if (token == null) {
\r
280 if (tagName != null) {
\r
281 throw x.syntaxError("Unclosed tag " + tagName);
\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
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
302 context.accumulate(tagName, jsonobject);
\r
309 throw x.syntaxError("Misshaped tag");
\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
322 * @param string A String.
\r
323 * @return A simple JSON value.
\r
325 public static Object stringToValue(String string) {
\r
326 if ("".equals(string)) {
\r
329 if ("true".equalsIgnoreCase(string)) {
\r
330 return Boolean.TRUE;
\r
332 if ("false".equalsIgnoreCase(string)) {
\r
333 return Boolean.FALSE;
\r
335 if ("null".equalsIgnoreCase(string)) {
\r
336 return JSONObject.NULL;
\r
338 if ("0".equals(string)) {
\r
339 return new Integer(0);
\r
342 // If it might be a number, try converting it. If that doesn't work,
\r
343 // return the string.
\r
346 char initial = string.charAt(0);
\r
347 boolean negative = false;
\r
348 if (initial == '-') {
\r
349 initial = string.charAt(1);
\r
352 if (initial == '0' && string.charAt(negative ? 2 : 1) == '0') {
\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
367 } catch (Exception ignore) {
\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><[ [ ]]></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
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
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
403 public static String toString(Object object) throws JSONException {
\r
404 return toString(object, null);
\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
415 public static String toString(Object object, String tagName)
\r
416 throws JSONException {
\r
417 StringBuffer sb = new StringBuffer();
\r
422 Iterator<String> keys;
\r
426 if (object instanceof JSONObject) {
\r
430 if (tagName != null) {
\r
432 sb.append(tagName);
\r
436 // Loop thru the keys.
\r
438 jo = (JSONObject)object;
\r
440 while (keys.hasNext()) {
\r
441 key = keys.next().toString();
\r
442 value = jo.opt(key);
\r
443 if (value == null) {
\r
446 if (value instanceof String) {
\r
447 string = (String)value;
\r
452 // Emit content in body
\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
462 sb.append(escape(ja.get(i).toString()));
\r
465 sb.append(escape(value.toString()));
\r
468 // Emit an array of similar keys
\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
475 if (value instanceof JSONArray) {
\r
479 sb.append(toString(value));
\r
484 sb.append(toString(value, key));
\r
487 } else if ("".equals(value)) {
\r
492 // Emit a new tag <k>
\r
495 sb.append(toString(value, key));
\r
498 if (tagName != null) {
\r
500 // Emit the </tagname> close tag
\r
503 sb.append(tagName);
\r
506 return sb.toString();
\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
512 if (object.getClass().isArray()) {
\r
513 object = new JSONArray(object);
\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
521 return sb.toString();
\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