scummvm/common/formats/json.cpp

1234 lines
30 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*
* Files JSON.cpp and JSONValue.cpp part of the SimpleJSON Library - https://github.com/MJPA/SimpleJSON
*
* Copyright (C) 2010 Mike Anchor
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "common/formats/json.h"
#ifdef __MINGW32__
#define wcsncasecmp wcsnicmp
#endif
// Macros to free an array/object
#define FREE_ARRAY(x) { JSONArray::iterator iter; for (iter = x.begin(); iter != x.end(); iter++) { delete *iter; } }
#define FREE_OBJECT(x) { JSONObject::iterator iter; for (iter = x.begin(); iter != x.end(); iter++) { delete (*iter)._value; } }
namespace Common {
/**
* Blocks off the public constructor
*
* @access private
*
*/
JSON::JSON() {}
/**
* Parses a complete JSON encoded string (UNICODE input version)
*
* @access public
*
* @param char* data The JSON text
*
* @return JSONValue* Returns a JSON Value representing the root, or NULL on error
*/
JSONValue *JSON::parse(const char *data) {
// Skip any preceding whitespace, end of data = no JSON = fail
if (!skipWhitespace(&data))
return nullptr;
// We need the start of a value here now...
JSONValue *value = JSONValue::parse(&data);
if (value == nullptr)
return nullptr;
// Can be white space now and should be at the end of the string then...
if (skipWhitespace(&data)) {
delete value;
return nullptr;
}
// We're now at the end of the string
return value;
}
/**
* Turns the passed in JSONValue into a JSON encode string
*
* @access public
*
* @param JSONValue* value The root value
*
* @return String Returns a JSON encoded string representation of the given value
*/
String JSON::stringify(const JSONValue *value) {
if (value != nullptr)
return value->stringify();
else
return "";
}
/**
* Skips over any whitespace characters (space, tab, \r or \n) defined by the JSON spec
*
* @access protected
*
* @param char** data Pointer to a char* that contains the JSON text
*
* @return bool Returns true if there is more data, or false if the end of the text was reached
*/
bool JSON::skipWhitespace(const char **data) {
while (**data != 0 && (**data == ' ' || **data == '\t' || **data == '\r' || **data == '\n'))
(*data)++;
return **data != 0;
}
/**
* Extracts a JSON String as defined by the spec - "<some chars>"
* Any escaped characters are swapped out for their unescaped values
*
* @access protected
*
* @param char** data Pointer to a char* that contains the JSON text
* @param String& str Reference to a String to receive the extracted string
*
* @return bool Returns true on success, false on failure
*/
bool JSON::extractString(const char **data, String &str) {
str = "";
while (**data != 0) {
// Save the char so we can change it if need be
char next_char = **data;
uint32 next_uchar = 0;
// Escaping something?
if (next_char == '\\') {
// Move over the escape char
(*data)++;
// Deal with the escaped char
switch (**data) {
case '"': next_char = '"';
break;
case '\\': next_char = '\\';
break;
case '/': next_char = '/';
break;
case 'b': next_char = '\b';
break;
case 'f': next_char = '\f';
break;
case 'n': next_char = '\n';
break;
case 'r': next_char = '\r';
break;
case 't': next_char = '\t';
break;
case 'u': {
next_char = 0;
next_uchar = parseUnicode(data);
// If the codepoint is a high surrogate, we should have a low surrogate now
if (next_uchar >= 0xD800 && next_uchar <= 0xDBFF) {
(*data)++;
if (**data != '\\')
return false;
(*data)++;
uint32 low_surrogate = parseUnicode(data);
if (low_surrogate < 0xDC00 || low_surrogate > 0xDFFF)
return false;
//next_uchar = 0x10000 + (next_uchar - 0xD800) * 0x400 + (low_surrogate - 0xDC00);
next_uchar = (next_uchar << 10) + low_surrogate - 0x35FDC00u;
} else if (next_uchar >= 0xDC00 && next_uchar <= 0xDFFF)
return false; // low surrogate, which should only follow a high surrogate
// Check this is a valid code point
if (next_uchar > 0x10FFFF)
return false;
break;
}
// By the spec, only the above cases are allowed
default:
return false;
}
}
// End of the string?
else if (next_char == '"') {
(*data)++;
//str.reserve(); // Remove unused capacity //TODO
return true;
}
// Disallowed char?
else if (next_char < ' ' && next_char != '\t') {
// SPEC Violation: Allow tabs due to real world cases
return false;
}
// Add the next char
if (next_char != 0)
str += next_char;
else {
if (next_uchar < 0x80)
// 1-byte character (ASCII)
str += (char)next_uchar;
else if (next_uchar <= 0x7FF) {
// 2-byte characters: 110xxxxx 10xxxxxx
str += (char)(0xC0 | (next_uchar >> 6));
str += (char)(0x80 | (next_uchar & 0x3F));
} else if (next_uchar <= 0xFFFF) {
// 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx
str += (char)(0xE0 | (next_uchar >> 12));
str += (char)(0x80 | ((next_uchar >> 6) & 0x3F));
str += (char)(0x80 | (next_uchar & 0x3F));
} else {
// 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
str += (char)(0xF0 | (next_uchar >> 18));
str += (char)(0x80 | ((next_uchar >> 12) & 0x3F));
str += (char)(0x80 | ((next_uchar >> 6) & 0x3F));
str += (char)(0x80 | (next_uchar & 0x3F));
}
}
// Move on
(*data)++;
}
// If we're here, the string ended incorrectly
return false;
}
/**
* Parses some text as though it is a unicode hexadecimal sequence.
* It assumes that the data is currently pointing on the 'u' part of '\uXXXX`.
*
* @access protected
*
* @param char** data Pointer to a char* that contains the JSON text
* @param String& str Reference to a String to receive the extracted string
*
* @return uint32 Returns the unicode code point value or 0xFFFFFFFF in case of error.
*/
uint32 JSON::parseUnicode(const char **data) {
if (**data != 'u')
return 0xFFFFFFFF;
// We need 5 chars (4 hex + the 'u') or its not valid
if (!simplejson_wcsnlen(*data, 5))
return 0xFFFFFFFF;
// Deal with the chars
uint32 codepoint = 0;
for (int i = 0; i < 4; i++) {
// Do it first to move off the 'u' and leave us on the
// final hex digit as we move on by one later on
(*data)++;
codepoint <<= 4;
// Parse the hex digit
if (**data >= '0' && **data <= '9')
codepoint |= (**data - '0');
else if (**data >= 'A' && **data <= 'F')
codepoint |= (10 + (**data - 'A'));
else if (**data >= 'a' && **data <= 'f')
codepoint |= (10 + (**data - 'a'));
else {
// Invalid hex digit
return 0xFFFFFFFF;
}
}
return codepoint;
}
/**
* Parses some text as though it is an integer
*
* @access protected
*
* @param char** data Pointer to a char* that contains the JSON text
*
* @return double Returns the double value of the number found
*/
double JSON::parseInt(const char **data) {
double integer = 0;
while (**data != 0 && **data >= '0' && **data <= '9')
integer = integer * 10 + (*(*data)++ - '0');
return integer;
}
/**
* Parses some text as though it is a decimal
*
* @access protected
*
* @param char** data Pointer to a char* that contains the JSON text
*
* @return double Returns the double value of the decimal found
*/
double JSON::parseDecimal(const char **data) {
double decimal = 0.0;
double factor = 0.1;
while (**data != 0 && **data >= '0' && **data <= '9') {
int digit = (*(*data)++ - '0');
decimal = decimal + digit * factor;
factor *= 0.1;
}
return decimal;
}
/**
* Parses a JSON encoded value to a JSONValue object
*
* @access protected
*
* @param char** data Pointer to a char* that contains the data
*
* @return JSONValue* Returns a pointer to a JSONValue object on success, NULL on error
*/
JSONValue *JSONValue::parse(const char **data) {
// Is it a string?
if (**data == '"') {
String str;
if (!JSON::extractString(&(++(*data)), str))
return nullptr;
else
return new JSONValue(str);
}
// Is it a boolean?
else if ((simplejson_wcsnlen(*data, 4) && scumm_strnicmp(*data, "true", 4) == 0) || (simplejson_wcsnlen(*data, 5) && scumm_strnicmp(*data, "false", 5) == 0)) {
bool value = scumm_strnicmp(*data, "true", 4) == 0;
(*data) += value ? 4 : 5;
return new JSONValue(value);
}
// Is it a null?
else if (simplejson_wcsnlen(*data, 4) && scumm_strnicmp(*data, "null", 4) == 0) {
(*data) += 4;
return new JSONValue();
}
// Is it a number?
else if (**data == '-' || (**data >= '0' && **data <= '9')) {
// Negative?
bool neg = **data == '-';
if (neg) (*data)++;
long long int integer = 0;
double number = 0.0;
bool onlyInteger = true;
// Parse the whole part of the number - only if it wasn't 0
if (**data == '0')
(*data)++;
else if (**data >= '1' && **data <= '9')
number = integer = JSON::parseInt(data);
else
return nullptr;
// Could be a decimal now...
if (**data == '.') {
(*data)++;
// Not get any digits?
if (!(**data >= '0' && **data <= '9'))
return nullptr;
// Find the decimal and sort the decimal place out
// Use ParseDecimal as ParseInt won't work with decimals less than 0.1
// thanks to Javier Abadia for the report & fix
double decimal = JSON::parseDecimal(data);
// Save the number
number += decimal;
onlyInteger = false;
}
// Could be an exponent now...
if (**data == 'E' || **data == 'e') {
(*data)++;
// Check signage of expo
bool neg_expo = false;
if (**data == '-' || **data == '+') {
neg_expo = **data == '-';
(*data)++;
}
// Not get any digits?
if (!(**data >= '0' && **data <= '9'))
return nullptr;
// Sort the expo out
double expo = JSON::parseInt(data);
for (double i = 0.0; i < expo; i++)
number = neg_expo ? (number / 10.0) : (number * 10.0);
onlyInteger = false;
}
// Was it neg?
if (neg) number *= -1;
if (onlyInteger)
return new JSONValue(neg ? -integer : integer);
return new JSONValue(number);
}
// An object?
else if (**data == '{') {
JSONObject object;
(*data)++;
while (**data != 0) {
// Whitespace at the start?
if (!JSON::skipWhitespace(data)) {
FREE_OBJECT(object);
return nullptr;
}
// Special case - empty object
if (object.size() == 0 && **data == '}') {
(*data)++;
return new JSONValue(object);
}
// We want a string now...
String name;
if (!JSON::extractString(&(++(*data)), name)) {
FREE_OBJECT(object);
return nullptr;
}
// More whitespace?
if (!JSON::skipWhitespace(data)) {
FREE_OBJECT(object);
return nullptr;
}
// Need a : now
if (*((*data)++) != ':') {
FREE_OBJECT(object);
return nullptr;
}
// More whitespace?
if (!JSON::skipWhitespace(data)) {
FREE_OBJECT(object);
return nullptr;
}
// The value is here
JSONValue *value = parse(data);
if (value == nullptr) {
FREE_OBJECT(object);
return nullptr;
}
// Add the name:value
if (object.find(name) != object.end())
delete object[name];
object[name] = value;
// More whitespace?
if (!JSON::skipWhitespace(data)) {
FREE_OBJECT(object);
return nullptr;
}
// End of object?
if (**data == '}') {
(*data)++;
return new JSONValue(object);
}
// Want a , now
if (**data != ',') {
FREE_OBJECT(object);
return nullptr;
}
(*data)++;
}
// Only here if we ran out of data
FREE_OBJECT(object);
return nullptr;
}
// An array?
else if (**data == '[') {
JSONArray array;
(*data)++;
while (**data != 0) {
// Whitespace at the start?
if (!JSON::skipWhitespace(data)) {
FREE_ARRAY(array);
return nullptr;
}
// Special case - empty array
if (array.size() == 0 && **data == ']') {
(*data)++;
return new JSONValue(array);
}
// Get the value
JSONValue *value = parse(data);
if (value == nullptr) {
FREE_ARRAY(array);
return nullptr;
}
// Add the value
array.push_back(value);
// More whitespace?
if (!JSON::skipWhitespace(data)) {
FREE_ARRAY(array);
return nullptr;
}
// End of array?
if (**data == ']') {
(*data)++;
return new JSONValue(array);
}
// Want a , now
if (**data != ',') {
FREE_ARRAY(array);
return nullptr;
}
(*data)++;
}
// Only here if we ran out of data
FREE_ARRAY(array);
return nullptr;
}
// Ran out of possibilites, it's bad!
else {
return nullptr;
}
}
/**
* Basic constructor for creating a JSON Value of type NULL
*
* @access public
*/
JSONValue::JSONValue(/*NULL*/) {
_type = JSONType_Null;
}
/**
* Basic constructor for creating a JSON Value of type String
*
* @access public
*
* @param char* m_char_value The string to use as the value
*/
JSONValue::JSONValue(const char *charValue) {
_type = JSONType_String;
_stringValue = new String(String(charValue));
}
/**
* Basic constructor for creating a JSON Value of type String
*
* @access public
*
* @param String m_string_value The string to use as the value
*/
JSONValue::JSONValue(const String &stringValue) {
_type = JSONType_String;
_stringValue = new String(stringValue);
}
/**
* Basic constructor for creating a JSON Value of type Bool
*
* @access public
*
* @param bool m_bool_value The bool to use as the value
*/
JSONValue::JSONValue(bool boolValue) {
_type = JSONType_Bool;
_boolValue = boolValue;
}
/**
* Basic constructor for creating a JSON Value of type Number
*
* @access public
*
* @param double m_number_value The number to use as the value
*/
JSONValue::JSONValue(double numberValue) {
_type = JSONType_Number;
_numberValue = numberValue;
}
/**
* Basic constructor for creating a JSON Value of type Number (Integer)
*
* @access public
*
* @param int numberValue The number to use as the value
*/
JSONValue::JSONValue(long long int numberValue) {
_type = JSONType_IntegerNumber;
_integerValue = numberValue;
}
/**
* Basic constructor for creating a JSON Value of type Array
*
* @access public
*
* @param JSONArray m_array_value The JSONArray to use as the value
*/
JSONValue::JSONValue(const JSONArray &arrayValue) {
_type = JSONType_Array;
_arrayValue = new JSONArray(arrayValue);
}
/**
* Basic constructor for creating a JSON Value of type Object
*
* @access public
*
* @param JSONObject m_object_value The JSONObject to use as the value
*/
JSONValue::JSONValue(const JSONObject &objectValue) {
_type = JSONType_Object;
_objectValue = new JSONObject(objectValue);
}
/**
* Copy constructor to perform a deep copy of array / object values
*
* @access public
*
* @param JSONValue m_source The source JSONValue that is being copied
*/
JSONValue::JSONValue(const JSONValue &source) {
_type = source._type;
switch (_type) {
case JSONType_String:
_stringValue = new String(*source._stringValue);
break;
case JSONType_Bool:
_boolValue = source._boolValue;
break;
case JSONType_Number:
_numberValue = source._numberValue;
break;
case JSONType_IntegerNumber:
_integerValue = source._integerValue;
break;
case JSONType_Array: {
JSONArray source_array = *source._arrayValue;
JSONArray::iterator iter;
_arrayValue = new JSONArray();
for (iter = source_array.begin(); iter != source_array.end(); iter++)
_arrayValue->push_back(new JSONValue(**iter));
break;
}
case JSONType_Object: {
JSONObject source_object = *source._objectValue;
_objectValue = new JSONObject();
JSONObject::iterator iter;
for (iter = source_object.begin(); iter != source_object.end(); iter++) {
String name = (*iter)._key;
(*_objectValue)[name] = new JSONValue(*((*iter)._value));
}
break;
}
default:
// fallthrough intended
case JSONType_Null:
// Nothing to do.
break;
}
}
/**
* The destructor for the JSON Value object
* Handles deleting the objects in the array or the object value
*
* @access public
*/
JSONValue::~JSONValue() {
if (_type == JSONType_Array) {
JSONArray::iterator iter;
for (iter = _arrayValue->begin(); iter != _arrayValue->end(); iter++)
delete *iter;
delete _arrayValue;
} else if (_type == JSONType_Object) {
JSONObject::iterator iter;
for (iter = _objectValue->begin(); iter != _objectValue->end(); iter++) {
delete (*iter)._value;
}
delete _objectValue;
} else if (_type == JSONType_String) {
delete _stringValue;
}
}
/**
* Checks if the value is a NULL
*
* @access public
*
* @return bool Returns true if it is a NULL value, false otherwise
*/
bool JSONValue::isNull() const {
return _type == JSONType_Null;
}
/**
* Checks if the value is a String
*
* @access public
*
* @return bool Returns true if it is a String value, false otherwise
*/
bool JSONValue::isString() const {
return _type == JSONType_String;
}
/**
* Checks if the value is a Bool
*
* @access public
*
* @return bool Returns true if it is a Bool value, false otherwise
*/
bool JSONValue::isBool() const {
return _type == JSONType_Bool;
}
/**
* Checks if the value is a Number
*
* @access public
*
* @return bool Returns true if it is a Number value, false otherwise
*/
bool JSONValue::isNumber() const {
return _type == JSONType_Number;
}
/**
* Checks if the value is an Integer
*
* @access public
*
* @return bool Returns true if it is an Integer value, false otherwise
*/
bool JSONValue::isIntegerNumber() const {
return _type == JSONType_IntegerNumber;
}
/**
* Checks if the value is an Array
*
* @access public
*
* @return bool Returns true if it is an Array value, false otherwise
*/
bool JSONValue::isArray() const {
return _type == JSONType_Array;
}
/**
* Checks if the value is an Object
*
* @access public
*
* @return bool Returns true if it is an Object value, false otherwise
*/
bool JSONValue::isObject() const {
return _type == JSONType_Object;
}
/**
* Retrieves the String value of this JSONValue
* Use isString() before using this method.
*
* @access public
*
* @return String Returns the string value
*/
const String &JSONValue::asString() const {
return (*_stringValue);
}
/**
* Retrieves the Bool value of this JSONValue
* Use isBool() before using this method.
*
* @access public
*
* @return bool Returns the bool value
*/
bool JSONValue::asBool() const {
return _boolValue;
}
/**
* Retrieves the Number value of this JSONValue
* Use isNumber() before using this method.
*
* @access public
*
* @return double Returns the number value
*/
double JSONValue::asNumber() const {
return _numberValue;
}
/**
* Retrieves the Integer value of this JSONValue
* Use isIntegerNumber() before using this method.
*
* @access public
*
* @return int Returns the number value
*/
long long int JSONValue::asIntegerNumber() const {
return _integerValue;
}
/**
* Retrieves the Array value of this JSONValue
* Use isArray() before using this method.
*
* @access public
*
* @return JSONArray Returns the array value
*/
const JSONArray &JSONValue::asArray() const {
return (*_arrayValue);
}
/**
* Retrieves the Object value of this JSONValue
* Use isObject() before using this method.
*
* @access public
*
* @return JSONObject Returns the object value
*/
const JSONObject &JSONValue::asObject() const {
return (*_objectValue);
}
/**
* Retrieves the number of children of this JSONValue.
* This number will be 0 or the actual number of children
* if isArray() or isObject().
*
* @access public
*
* @return The number of children.
*/
std::size_t JSONValue::countChildren() const {
switch (_type) {
case JSONType_Array:
return _arrayValue->size();
case JSONType_Object:
return _objectValue->size();
default:
return 0;
}
}
/**
* Checks if this JSONValue has a child at the given index.
* Use isArray() before using this method.
*
* @access public
*
* @return bool Returns true if the array has a value at the given index.
*/
bool JSONValue::hasChild(std::size_t index) const {
if (_type == JSONType_Array) {
return index < _arrayValue->size();
} else {
return false;
}
}
/**
* Retrieves the child of this JSONValue at the given index.
* Use isArray() before using this method.
*
* @access public
*
* @return JSONValue* Returns JSONValue at the given index or NULL
* if it doesn't exist.
*/
JSONValue *JSONValue::child(std::size_t index) {
if (index < _arrayValue->size()) {
return (*_arrayValue)[index];
} else {
return nullptr;
}
}
/**
* Checks if this JSONValue has a child at the given key.
* Use isObject() before using this method.
*
* @access public
*
* @return bool Returns true if the object has a value at the given key.
*/
bool JSONValue::hasChild(const char *name) const {
if (_type == JSONType_Object) {
return _objectValue->find(name) != _objectValue->end();
} else {
return false;
}
}
/**
* Retrieves the child of this JSONValue at the given key.
* Use isObject() before using this method.
*
* @access public
*
* @return JSONValue* Returns JSONValue for the given key in the object
* or NULL if it doesn't exist.
*/
JSONValue *JSONValue::child(const char *name) {
JSONObject::const_iterator it = _objectValue->find(name);
if (it != _objectValue->end()) {
return it->_value;
} else {
return nullptr;
}
}
/**
* Retrieves the keys of the JSON Object or an empty vector
* if this value is not an object.
*
* @access public
*
* @return std::vector<String> A vector containing the keys.
*/
Array<String> JSONValue::objectKeys() const {
Array<String> keys;
if (_type == JSONType_Object) {
JSONObject::const_iterator iter = _objectValue->begin();
while (iter != _objectValue->end()) {
keys.push_back(iter->_key);
iter++;
}
}
return keys;
}
/**
* Creates a JSON encoded string for the value with all necessary characters escaped
*
* @access public
*
* @param bool prettyprint Enable prettyprint
*
* @return String Returns the JSON string
*/
String JSONValue::stringify(bool const prettyprint) const {
size_t const indentDepth = prettyprint ? 1 : 0;
return stringifyImpl(indentDepth);
}
/**
* Creates a JSON encoded string for the value with all necessary characters escaped
*
* @access private
*
* @param size_t indentDepth The prettyprint indentation depth (0 : no prettyprint)
*
* @return String Returns the JSON string
*/
String JSONValue::stringifyImpl(size_t const indentDepth) const {
String ret_string;
size_t const indentDepth1 = indentDepth ? indentDepth + 1 : 0;
String const indentStr = indent(indentDepth);
String const indentStr1 = indent(indentDepth1);
switch (_type) {
default:
// fallthrough intended
case JSONType_Null:
ret_string = "null";
break;
case JSONType_String:
ret_string = stringifyString(*_stringValue);
break;
case JSONType_Bool:
ret_string = _boolValue ? "true" : "false";
break;
case JSONType_Number: {
if (isinf(_numberValue) || isnan(_numberValue))
ret_string = "null";
else {
ret_string = String::format("%g", _numberValue);
}
break;
}
case JSONType_IntegerNumber: {
ret_string = String::format("%lld", _integerValue);
break;
}
case JSONType_Array: {
ret_string = indentDepth ? "[\n" + indentStr1 : "[";
JSONArray::const_iterator iter = _arrayValue->begin();
while (iter != _arrayValue->end()) {
ret_string += (*iter)->stringifyImpl(indentDepth1);
// Not at the end - add a separator
if (++iter != _arrayValue->end())
ret_string += ",";
}
ret_string += indentDepth ? "\n" + indentStr + "]" : "]";
break;
}
case JSONType_Object: {
ret_string = indentDepth ? "{\n" + indentStr1 : "{";
JSONObject::const_iterator iter = _objectValue->begin();
while (iter != _objectValue->end()) {
ret_string += stringifyString((*iter)._key);
ret_string += ":";
ret_string += (*iter)._value->stringifyImpl(indentDepth1);
// Not at the end - add a separator
if (++iter != _objectValue->end())
ret_string += ",";
}
ret_string += indentDepth ? "\n" + indentStr + "}" : "}";
break;
}
}
return ret_string;
}
/**
* Creates a JSON encoded string with all required fields escaped
* Works from https://www.ecma-international.org/wp-content/uploads/ECMA-262_5.1_edition_june_2011.pdf
* Section 15.12.3.
*
* @access private
*
* @param String str The string that needs to have the characters escaped
*
* @return String Returns the JSON string
*/
String JSONValue::stringifyString(const String &str) {
String str_out = "\"";
String::const_iterator iter = str.begin();
while (iter != str.end()) {
uint32 uchr = decodeUtf8Char(iter, str.end());
if (uchr == 0xFFFFFFFF)
break; // error - truncate the result
if (uchr == '"' || uchr == '\\' || uchr == '/') {
str_out += '\\';
str_out += (char)uchr;
} else if (uchr == '\b') {
str_out += "\\b";
} else if (uchr == '\f') {
str_out += "\\f";
} else if (uchr == '\n') {
str_out += "\\n";
} else if (uchr == '\r') {
str_out += "\\r";
} else if (uchr == '\t') {
str_out += "\\t";
} else if (uchr >= ' ' && uchr <= 126 ) {
str_out += (char)uchr;
} else {
if (uchr <= 0xFFFF)
str_out += String::format("\\u%04x", uchr);
else
str_out += String::format("\\u%04x\\u%04x", 0xD7C0 + (uchr >> 10), 0xDC00 + (uchr & 0x3FF));
}
iter++;
}
str_out += "\"";
return str_out;
}
/**
* Decode the next utf-8 character in the String pointed to by begin.
*
* @param String::const_iterator &iter Iterator pointing to the start of the character to decode.
*
* @param const String::const_iterator &end Iterator pointing past the end of the string being decoded.
*
* @return The codepoint value for the next utf-8 character starting at the current iterator position,
* or 0xFFFFFFFF in case of error.
*/
uint32 JSONValue::decodeUtf8Char(String::const_iterator &iter, const String::const_iterator &end) {
uint8 state = 0;
uint32 codepoint = 0;
int nbRead = 0;
do {
uint8 byte = uint8(*iter);
state = decodeUtf8Byte(state, codepoint, byte);
++nbRead;
if (state == 0)
return codepoint;
} while (state != 1 && ++iter != end);
if (state == 1) {
// We failed to read this as a UTF-8 character. The string might be encoded differently, which
// would be invalid (since the json standard indicate the string has to be in utf-8) but rather
// that return 0FFFFFFFF and truncate, try to recover from it by rewinding and returning the
// raw byte.
while (--nbRead > 0) { --iter; }
uint8 byte = uint8(*iter);
warning("Invalid UTF-8 character 0x%x in JSON string.", byte);
return byte;
}
return 0xFFFFFFFF;
}
/**
* Decode one byte from a UTF-8 string.
*
* The function must initially (for the first byte) be called with a state of 0, and then
* with the state from the previous byte until it returns 0 (success) or 1 (failure).
*
* Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
* See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
*
* @access private
*
* @param uint8 state The state from the previous byte, or 0 when decoding the first byte.
*
* @param uint32 &codepoint The codepoint value. Unless the returned state is 0, the codepoint is
* a partial reasult and the function needs to be called again with the next byte.
*
* @param uint8 byte The byte to decode.
*
* @return The state of the utf8 decoder: 0 if a character has been decoded, 1 in case of
* error, and any other value for decoding in progress.
*/
uint8 JSONValue::decodeUtf8Byte(uint8 state, uint32 &codepoint, uint8 byte) {
static const uint8 utf8d[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
};
const uint8 type = utf8d[byte];
codepoint = state != 0 ?
(codepoint << 6) | (byte & 0x3f) :
(0xFF >> type) & byte;
return utf8d[256 + state * 16 + type];
}
/**
* Creates the indentation string for the depth given
*
* @access private
*
* @param size_t indent The prettyprint indentation depth (0 : no indentation)
*
* @return String Returns the string
*/
String JSONValue::indent(size_t depth) {
const size_t indent_step = 2;
depth ? --depth : 0;
String indentStr;
for (size_t i = 0; i < depth * indent_step; ++i) indentStr += ' ';
return indentStr;
}
} // End of namespace Common