All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.org.json.JsonTokener.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.json

// Note: this class was written without inspecting the non-free org.Json sourcecode.
/**
 * Parses a Json ([RFC 4627](http://www.ietf.org/rfc/rfc4627.txt))
 * encoded string into the corresponding object. Most clients of
 * this class will use only need the [constructor][.JsonTokener]
 * and [.nextValue] method. Example usage: 
 * String Json = "{"
 * + "  \"query\": \"Pizza\", "
 * + "  \"locations\": [ 94043, 90210 ] "
 * + "}";
 *
 * JsonObject object = (JsonObject) new JsonTokener(Json).nextValue();
 * String query = object.getString("query");
 * JsonArray locations = object.getJsonArray("locations");
* * * For best interoperability and performance use Json that complies with * RFC 4627, such as that generated by [JsonStringer]. For legacy reasons * this parser is lenient, so a successful parse does not indicate that the * input string was valid Json. All of the following syntax errors will be * ignored: * * * End of line comments starting with `//` or `#` and ending * with a newline character. * * C-style comments starting with `/ *` and ending with * `*``/`. Such comments may not be nested. * * Strings that are unquoted or `'single quoted'`. * * Hexadecimal integers prefixed with `0x` or `0X`. * * Octal integers prefixed with `0`. * * Array elements separated by `;`. * * Unnecessary array separators. These are interpreted as if null was the * omitted value. * * Key-value pairs separated by `=` or `=>`. * * Key-value pairs separated by `;`. * * * * Each tokener may be used to parse a single Json string. Instances of this * class are not thread safe. Although this class is nonfinal, it was not * designed for inheritance and should not be subclassed. In particular, * self-use by overrideable methods is not specified. See *Effective Java* * Item 17, "Design and Document or inheritance or else prohibit it" for further * information. */ open class JsonTokener(`in`: String?) { /** The input Json. */ private val `in`: String? /** * The index of the next character to be returned by [.next]. When * the input is exhausted, this equals the input's length. */ private var pos = 0 /** * Returns the next value from the input. * * @return a [JsonObject], [JsonArray], String, Boolean, * Integer, Long, Double or [JsonObject.NULL]. * @throws JsonException if the input is malformed. */ @Throws(JsonException::class) fun nextValue(): Any { val c = nextCleanInternal() when (c) { -1 -> throw syntaxError("End of input") '{'.code -> return readObject() '['.code -> return readArray() '\''.code, '"'.code -> return nextString(c.toChar()) else -> { pos-- return readLiteral() } } } @Throws(JsonException::class) private fun nextCleanInternal(): Int { while (pos < `in`!!.length) { val c = `in`[pos++].code when (c) { '\t'.code, ' '.code, '\n'.code, '\r'.code -> continue '/'.code -> { if (pos == `in`.length) { return c } val peek = `in`[pos] when (peek) { '*' -> { // skip a /* c-style comment */ pos++ val commentEnd = `in`.indexOf("*/", pos) if (commentEnd == -1) { throw syntaxError("Unterminated comment") } pos = commentEnd + 2 continue } '/' -> { // skip a // end-of-line comment pos++ skipToEndOfLine() continue } else -> return c } } '#'.code -> { /* * Skip a # hash end-of-line comment. The Json RFC doesn't * specify this behavior, but it's required to parse * existing documents. See http://b/2571423. */skipToEndOfLine() continue } else -> return c } } return -1 } /** * Advances the position until after the next newline character. If the line * is terminated by "\r\n", the '\n' must be consumed as whitespace by the * caller. */ private fun skipToEndOfLine() { while (pos < `in`!!.length) { val c = `in`[pos] if (c == '\r' || c == '\n') { pos++ break } pos++ } } /** * Returns the string up to but not including `quote`, unescaping any * character escape sequences encountered along the way. The opening quote * should have already been read. This consumes the closing quote, but does * not include it in the returned string. * * @param quote either ' or ". */ @Throws(JsonException::class) fun nextString(quote: Char): String { /* * For strings that are free of escape sequences, we can just extract * the result as a substring of the input. But if we encounter an escape * sequence, we need to use a StringBuilder to compose the result. */ var builder: StringBuilder? = null /* the index of the first character not yet appended to the builder. */ var start = pos while (pos < `in`!!.length) { val c = `in`[pos++].code if (c == quote.code) { if (builder == null) { // a new string avoids leaking memory return `in`.substring(start, pos - 1) } else { builder.append(`in`, start, pos - 1) return builder.toString() } } if (c == '\\'.toInt()) { if (pos == `in`.length) { throw syntaxError("Unterminated escape sequence") } if (builder == null) { builder = StringBuilder() } builder.append(`in`, start, pos - 1) builder.append(readEscapeCharacter()) start = pos } } throw syntaxError("Unterminated string") } /** * Unescapes the character identified by the character or characters that * immediately follow a backslash. The backslash '\' should have already * been read. This supports both unicode escapes "u000A" and two-character * escapes "\n". */ @Throws(JsonException::class) private fun readEscapeCharacter(): Char { val escaped = `in`!![pos++] when (escaped) { 'u' -> { if (pos + 4 > `in`.length) { throw syntaxError("Unterminated escape sequence") } val hex = `in`.substring(pos, pos + 4) pos += 4 try { return hex.toInt(16).toChar() } catch (nfe: NumberFormatException) { throw syntaxError("Invalid escape sequence: $hex") } return '\t' } 't' -> return '\t' 'b' -> return '\b' 'n' -> return '\n' 'r' -> return '\r' // TODO form feed character not supported 'f' -> return '\f' '\'', '"', '\\' -> return escaped else -> return escaped } } /** * Reads a null, boolean, numeric or unquoted string literal value. Numeric * values will be returned as an Integer, Long, or Double, in that order of * preference. */ @Throws(JsonException::class) private fun readLiteral(): Any { val literal = nextToInternal("{}[]/\\:,=;# \t")// TODO form feed \f if (literal.length == 0) { throw syntaxError("Expected literal value") } else if ("null".equals(literal, ignoreCase = true)) { return JsonObject.NULL } else if ("true".equals(literal, ignoreCase = true)) { return true } else if ("false".equals(literal, ignoreCase = true)) { return false } /* try to parse as an integral type... */if (literal.indexOf('.') == -1) { var base = 10 var number = literal if (number.startsWith("0x") || number.startsWith("0X")) { number = number.substring(2) base = 16 } else if (number.startsWith("0") && number.length > 1) { number = number.substring(1) base = 8 } try { val longValue = number.toLong(base) return if (longValue <= Int.MAX_VALUE && longValue >= Int.MIN_VALUE) { longValue.toInt() } else { longValue } } catch (e: NumberFormatException) { /* * This only happens for integral numbers greater than * Long.MAX_VALUE, numbers in exponential form (5e-10) and * unquoted strings. Fall through to try floating point. */ } } /* ...next try to parse as a floating point... */try { return literal.toDouble() } catch (ignored: NumberFormatException) { } /* ... finally give up. We have an unquoted string */return literal.toCharArray() // a new string avoids leaking memory } /** * Returns the string up to but not including any of the given characters or * a newline character. This does not consume the excluded character. */ private fun nextToInternal(excluded: String): String { val start = pos while (pos < `in`!!.length) { val c = `in`[pos] if (c == '\r' || c == '\n' || excluded.indexOf(c) != -1) { return `in`.substring(start, pos) } pos++ } return `in`.substring(start) } /** * Reads a sequence of key/value pairs and the trailing closing brace '}' of * an object. The opening brace '{' should have already been read. */ @Throws(JsonException::class) private fun readObject(): JsonObject { val result = JsonObject() /* Peek to see if this is the empty object. */ val first = nextCleanInternal() if (first == '}'.toInt()) { return result } else if (first != -1) { pos-- } while (true) { val name: Any = nextValue() if (name !is String) { if (name == null) { throw syntaxError("Names cannot be null") } else { throw syntaxError( "Names must be strings, but " + name + " is of type " + name::class.qualifiedName ) } } /* * Expect the name/value separator to be either a colon ':', an * equals sign '=', or an arrow "=>". The last two are bogus but we * include them because that's what the original implementation did. */ val separator = nextCleanInternal() if (separator != ':'.toInt() && separator != '='.toInt()) { throw syntaxError("Expected ':' after $name") } if (pos < `in`!!.length && `in`[pos] == '>') { pos++ } result.put(name as String?, nextValue()) when (nextCleanInternal()) { '}'.code -> return result ';'.code, ','.code -> continue else -> throw syntaxError("Unterminated object") } } } /** * Reads a sequence of values and the trailing closing brace ']' of an * array. The opening brace '[' should have already been read. Note that * "[]" yields an empty array, but "[,]" returns a two-element array * equivalent to "[null,null]". */ @Throws(JsonException::class) private fun readArray(): JsonArray { val result = JsonArray() /* to cover input that ends with ",]". */ var hasTrailingSeparator = false while (true) { when (nextCleanInternal()) { -1 -> throw syntaxError("Unterminated array") ']'.code -> { if (hasTrailingSeparator) { result.put(null) } return result } ','.code, ';'.code -> { /* A separator without a value first means "null". */result.put(null) hasTrailingSeparator = true continue } else -> pos-- } result.put(nextValue()) when (nextCleanInternal()) { ']'.code -> return result ','.code, ';'.code -> { hasTrailingSeparator = true continue } else -> throw syntaxError("Unterminated array") } } } /** * Returns an exception containing the given message plus the current * position and the entire input string. */ fun syntaxError(message: String): JsonException { return JsonException(message + this) } /** * Returns the current position and the entire input string. */ override fun toString(): String { // consistent with the original implementation return " at character $pos of $`in`" } /* * Legacy APIs. * * None of the methods below are on the critical path of parsing Json * documents. They exist only because they were exposed by the original * implementation and may be used by some clients. */ /** * Returns true until the input has been exhausted. */ fun more(): Boolean { return pos < `in`!!.length } /** * Returns the next available character, or the null character '\0' if all * input has been exhausted. The return value of this method is ambiguous * for Json strings that contain the character '\0'. */ operator fun next(): Char { return if (pos < `in`!!.length) `in`[pos++] else '\u0000' } /** * Returns the next available character if it equals `c`. Otherwise an * exception is thrown. */ @Throws(JsonException::class) fun next(c: Char): Char { val result = next() if (result != c) { throw syntaxError("Expected $c but was $result") } return result } /** * Returns the next character that is not whitespace and does not belong to * a comment. If the input is exhausted before such a character can be * found, the null character '\0' is returned. The return value of this * method is ambiguous for Json strings that contain the character '\0'. */ @Throws(JsonException::class) fun nextClean(): Char { val nextCleanInt = nextCleanInternal() return if (nextCleanInt == -1) '\u0000' else nextCleanInt.toChar() } /** * Returns the next `length` characters of the input. * * * The returned string shares its backing character array with this * tokener's input string. If a reference to the returned string may be held * indefinitely, you should use `new String(result)` to copy it first * to avoid memory leaks. * * @throws JsonException if the remaining input is not long enough to * satisfy this request. */ @Throws(JsonException::class) fun next(length: Int): String { if (pos + length > `in`!!.length) { throw syntaxError("$length is out of bounds") } val result = `in`.substring(pos, pos + length) pos += length return result } /** * Returns the [trimmed][String.trim] string holding the characters up * to but not including the first of: * * * any character in `excluded` * * a newline character '\n' * * a carriage return '\r' * * * * The returned string shares its backing character array with this * tokener's input string. If a reference to the returned string may be held * indefinitely, you should use `new String(result)` to copy it first * to avoid memory leaks. * * @return a possibly-empty string */ fun nextTo(excluded: String?): String { if (excluded == null) { throw NullPointerException("excluded == null") } return nextToInternal(excluded).trim { it <= ' ' } } /** * Equivalent to `nextTo(String.valueOf(excluded))`. */ fun nextTo(excluded: Char): String { return nextToInternal(excluded.toString()).trim { it <= ' ' } } /** * Advances past all input up to and including the next occurrence of * `thru`. If the remaining input doesn't contain `thru`, the * input is exhausted. */ open fun skipPast(thru: String) { val thruStart = `in`!!.indexOf(thru, pos) pos = if (thruStart == -1) `in`.length else (thruStart + thru.length) } /** * Advances past all input up to but not including the next occurrence of * `to`. If the remaining input doesn't contain `to`, the input * is unchanged. */ fun skipTo(to: Char): Char { val index = `in`!!.indexOf(to, pos) if (index != -1) { pos = index return to } else { return '\u0000' } } /** * Unreads the most recent character of input. If no input characters have * been read, the input is unchanged. */ fun back() { if (--pos == -1) { pos = 0 } } companion object { /** * Returns the integer [0..15] value for the given hex character, or -1 * for non-hex input. * * @param hex a character in the ranges [0-9], [A-F] or [a-f]. Any other * character will yield a -1 result. */ fun dehexchar(hex: Char): Int { if (hex >= '0' && hex <= '9') { return hex - '0' } else if (hex >= 'A' && hex <= 'F') { return hex - 'A' + 10 } else return if (hex >= 'a' && hex <= 'f') { hex - 'a' + 10 } else { -1 } } } /** * @param in Json encoded string. Null is not permitted and will yield a * tokener that throws `NullPointerExceptions` when methods are * called. */ init { // consume an optional byte order mark (BOM) if it exists var `in` = `in` if (`in` != null && `in`.startsWith("\ufeff")) { `in` = `in`.substring(1) } this.`in` = `in` } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy