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
27 import java.util.Iterator;
\r
32 /** The Character '&'. */
\r
33 public static final Character AMP = new Character('&');
\r
35 /** The Character '''. */
\r
36 public static final Character APOS = new Character('\'');
\r
38 /** The Character '!'. */
\r
39 public static final Character BANG = new Character('!');
\r
41 /** The Character '='. */
\r
42 public static final Character EQ = new Character('=');
\r
44 /** The Character '>'. */
\r
45 public static final Character GT = new Character('>');
\r
47 /** The Character '<'. */
\r
48 public static final Character LT = new Character('<');
\r
50 /** The Character '?'. */
\r
51 public static final Character QUEST = new Character('?');
\r
53 /** The Character '"'. */
\r
54 public static final Character QUOT = new Character('"');
\r
56 /** The Character '/'. */
\r
57 public static final Character SLASH = new Character('/');
\r
60 * Replace special characters with XML escapes:
\r
62 * & <small>(ampersand)</small> is replaced by &amp;
\r
63 * < <small>(less than)</small> is replaced by &lt;
\r
64 * > <small>(greater than)</small> is replaced by &gt;
\r
65 * " <small>(double quote)</small> is replaced by &quot;
\r
67 * @param string The string to be escaped.
\r
68 * @return The escaped string.
\r
70 public static String escape(String string) {
\r
71 StringBuffer sb = new StringBuffer();
\r
72 for (int i = 0, length = string.length(); i < length; i++) {
\r
73 char c = string.charAt(i);
\r
85 sb.append(""");
\r
88 sb.append("'");
\r
94 return sb.toString();
\r
98 * Throw an exception if the string contains whitespace.
\r
99 * Whitespace is not allowed in tagNames and attributes.
\r
101 * @throws JSONException
\r
103 public static void noSpace(String string) throws JSONException {
\r
104 int i, length = string.length();
\r
106 throw new JSONException("Empty string.");
\r
108 for (i = 0; i < length; i += 1) {
\r
109 if (Character.isWhitespace(string.charAt(i))) {
\r
110 throw new JSONException("'" + string +
\r
111 "' contains a space character.");
\r
117 * Scan the content following the named tag, attaching it to the context.
\r
118 * @param x The XMLTokener containing the source string.
\r
119 * @param context The JSONObject that will include the new material.
\r
120 * @param name The tag name.
\r
121 * @return true if the close tag is processed.
\r
122 * @throws JSONException
\r
124 private static boolean parse(XMLTokener x, JSONObject context,
\r
125 String name) throws JSONException {
\r
128 JSONObject jsonobject = null;
\r
133 // Test for and skip past these forms:
\r
138 // Report errors for these forms:
\r
143 token = x.nextToken();
\r
147 if (token == BANG) {
\r
150 if (x.next() == '-') {
\r
155 } else if (c == '[') {
\r
156 token = x.nextToken();
\r
157 if ("CDATA".equals(token)) {
\r
158 if (x.next() == '[') {
\r
159 string = x.nextCDATA();
\r
160 if (string.length() > 0) {
\r
161 context.accumulate("content", string);
\r
166 throw x.syntaxError("Expected 'CDATA['");
\r
170 token = x.nextMeta();
\r
171 if (token == null) {
\r
172 throw x.syntaxError("Missing '>' after '<!'.");
\r
173 } else if (token == LT) {
\r
175 } else if (token == GT) {
\r
180 } else if (token == QUEST) {
\r
186 } else if (token == SLASH) {
\r
190 token = x.nextToken();
\r
191 if (name == null) {
\r
192 throw x.syntaxError("Mismatched close tag " + token);
\r
194 if (!token.equals(name)) {
\r
195 throw x.syntaxError("Mismatched " + name + " and " + token);
\r
197 if (x.nextToken() != GT) {
\r
198 throw x.syntaxError("Misshaped close tag");
\r
202 } else if (token instanceof Character) {
\r
203 throw x.syntaxError("Misshaped tag");
\r
208 tagName = (String)token;
\r
210 jsonobject = new JSONObject();
\r
212 if (token == null) {
\r
213 token = x.nextToken();
\r
216 // attribute = value
\r
218 if (token instanceof String) {
\r
219 string = (String)token;
\r
220 token = x.nextToken();
\r
222 token = x.nextToken();
\r
223 if (!(token instanceof String)) {
\r
224 throw x.syntaxError("Missing value");
\r
226 jsonobject.accumulate(string,
\r
227 XML.stringToValue((String)token));
\r
230 jsonobject.accumulate(string, "");
\r
233 // Empty tag <.../>
\r
235 } else if (token == SLASH) {
\r
236 if (x.nextToken() != GT) {
\r
237 throw x.syntaxError("Misshaped tag");
\r
239 if (jsonobject.length() > 0) {
\r
240 context.accumulate(tagName, jsonobject);
\r
242 context.accumulate(tagName, "");
\r
246 // Content, between <...> and </...>
\r
248 } else if (token == GT) {
\r
250 token = x.nextContent();
\r
251 if (token == null) {
\r
252 if (tagName != null) {
\r
253 throw x.syntaxError("Unclosed tag " + tagName);
\r
256 } else if (token instanceof String) {
\r
257 string = (String)token;
\r
258 if (string.length() > 0) {
\r
259 jsonobject.accumulate("content",
\r
260 XML.stringToValue(string));
\r
265 } else if (token == LT) {
\r
266 if (parse(x, jsonobject, tagName)) {
\r
267 if (jsonobject.length() == 0) {
\r
268 context.accumulate(tagName, "");
\r
269 } else if (jsonobject.length() == 1 &&
\r
270 jsonobject.opt("content") != null) {
\r
271 context.accumulate(tagName,
\r
272 jsonobject.opt("content"));
\r
274 context.accumulate(tagName, jsonobject);
\r
281 throw x.syntaxError("Misshaped tag");
\r
289 * Try to convert a string into a number, boolean, or null. If the string
\r
290 * can't be converted, return the string. This is much less ambitious than
\r
291 * JSONObject.stringToValue, especially because it does not attempt to
\r
292 * convert plus forms, octal forms, hex forms, or E forms lacking decimal
\r
294 * @param string A String.
\r
295 * @return A simple JSON value.
\r
297 public static Object stringToValue(String string) {
\r
298 if ("".equals(string)) {
\r
301 if ("true".equalsIgnoreCase(string)) {
\r
302 return Boolean.TRUE;
\r
304 if ("false".equalsIgnoreCase(string)) {
\r
305 return Boolean.FALSE;
\r
307 if ("null".equalsIgnoreCase(string)) {
\r
308 return JSONObject.NULL;
\r
310 if ("0".equals(string)) {
\r
311 return new Integer(0);
\r
314 // If it might be a number, try converting it. If that doesn't work,
\r
315 // return the string.
\r
318 char initial = string.charAt(0);
\r
319 boolean negative = false;
\r
320 if (initial == '-') {
\r
321 initial = string.charAt(1);
\r
324 if (initial == '0' && string.charAt(negative ? 2 : 1) == '0') {
\r
327 if ((initial >= '0' && initial <= '9')) {
\r
328 if (string.indexOf('.') >= 0) {
\r
329 return Double.valueOf(string);
\r
330 } else if (string.indexOf('e') < 0 && string.indexOf('E') < 0) {
\r
331 Long myLong = new Long(string);
\r
332 if (myLong.longValue() == myLong.intValue()) {
\r
333 return new Integer(myLong.intValue());
\r
339 } catch (Exception ignore) {
\r
346 * Convert a well-formed (but not necessarily valid) XML string into a
\r
347 * JSONObject. Some information may be lost in this transformation
\r
348 * because JSON is a data format and XML is a document format. XML uses
\r
349 * elements, attributes, and content text, while JSON uses unordered
\r
350 * collections of name/value pairs and arrays of values. JSON does not
\r
351 * does not like to distinguish between elements and attributes.
\r
352 * Sequences of similar elements are represented as JSONArrays. Content
\r
353 * text may be placed in a "content" member. Comments, prologs, DTDs, and
\r
354 * <code><[ [ ]]></code> are ignored.
\r
355 * @param string The source string.
\r
356 * @return A JSONObject containing the structured data from the XML string.
\r
357 * @throws JSONException
\r
359 public static JSONObject toJSONObject(String string) throws JSONException {
\r
360 JSONObject jo = new JSONObject();
\r
361 XMLTokener x = new XMLTokener(string);
\r
362 while (x.more() && x.skipPast("<")) {
\r
363 parse(x, jo, null);
\r
370 * Convert a JSONObject into a well-formed, element-normal XML string.
\r
371 * @param object A JSONObject.
\r
372 * @return A string.
\r
373 * @throws JSONException
\r
375 public static String toString(Object object) throws JSONException {
\r
376 return toString(object, null);
\r
381 * Convert a JSONObject into a well-formed, element-normal XML string.
\r
382 * @param object A JSONObject.
\r
383 * @param tagName The optional name of the enclosing tag.
\r
384 * @return A string.
\r
385 * @throws JSONException
\r
387 public static String toString(Object object, String tagName)
\r
388 throws JSONException {
\r
389 StringBuffer sb = new StringBuffer();
\r
394 Iterator<String> keys;
\r
398 if (object instanceof JSONObject) {
\r
402 if (tagName != null) {
\r
404 sb.append(tagName);
\r
408 // Loop thru the keys.
\r
410 jo = (JSONObject)object;
\r
412 while (keys.hasNext()) {
\r
413 key = keys.next().toString();
\r
414 value = jo.opt(key);
\r
415 if (value == null) {
\r
418 if (value instanceof String) {
\r
419 string = (String)value;
\r
424 // Emit content in body
\r
426 if ("content".equals(key)) {
\r
427 if (value instanceof JSONArray) {
\r
428 ja = (JSONArray)value;
\r
429 length = ja.length();
\r
430 for (i = 0; i < length; i += 1) {
\r
434 sb.append(escape(ja.get(i).toString()));
\r
437 sb.append(escape(value.toString()));
\r
440 // Emit an array of similar keys
\r
442 } else if (value instanceof JSONArray) {
\r
443 ja = (JSONArray)value;
\r
444 length = ja.length();
\r
445 for (i = 0; i < length; i += 1) {
\r
447 if (value instanceof JSONArray) {
\r
451 sb.append(toString(value));
\r
456 sb.append(toString(value, key));
\r
459 } else if ("".equals(value)) {
\r
464 // Emit a new tag <k>
\r
467 sb.append(toString(value, key));
\r
470 if (tagName != null) {
\r
472 // Emit the </tagname> close tag
\r
475 sb.append(tagName);
\r
478 return sb.toString();
\r
480 // XML does not have good support for arrays. If an array appears in a place
\r
481 // where XML is lacking, synthesize an <array> element.
\r
484 if (object.getClass().isArray()) {
\r
485 object = new JSONArray(object);
\r
487 if (object instanceof JSONArray) {
\r
488 ja = (JSONArray)object;
\r
489 length = ja.length();
\r
490 for (i = 0; i < length; i += 1) {
\r
491 sb.append(toString(ja.opt(i), tagName == null ? "array" : tagName));
\r
493 return sb.toString();
\r
495 string = (object == null) ? "null" : escape(object.toString());
\r
496 return (tagName == null) ? "\"" + string + "\"" :
\r
497 (string.length() == 0) ? "<" + tagName + "/>" :
\r
498 "<" + tagName + ">" + string + "</" + tagName + ">";
\r