diff options
Diffstat (limited to 'common/json.cpp')
-rw-r--r-- | common/json.cpp | 1064 |
1 files changed, 1064 insertions, 0 deletions
diff --git a/common/json.cpp b/common/json.cpp new file mode 100644 index 0000000000..f84ad70eb8 --- /dev/null +++ b/common/json.cpp @@ -0,0 +1,1064 @@ +/* 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 2 +* 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, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +/* + * Files JSON.cpp and JSONValue.cpp part of the SimpleJSON Library - http://mjpa.in/json + * + * 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 "JSON.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <math.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 NULL; + + // We need the start of a value here now... + JSONValue* value = JSONValue::Parse(&data); + if (value == NULL) + return NULL; + + // Can be white space now and should be at the end of the string then... + if (SkipWhitespace(&data)) { + delete value; + return NULL; + } + + // 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 != NULL) + 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; + + // 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': { + // We need 5 chars (4 hex + the 'u') or its not valid + if (!simplejson_wcsnlen(*data, 5)) + return false; + + // Deal with the chars + next_char = 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)++; + + next_char <<= 4; + + // Parse the hex digit + if (**data >= '0' && **data <= '9') + next_char |= (**data - '0'); + else if (**data >= 'A' && **data <= 'F') + next_char |= (10 + (**data - 'A')); + else if (**data >= 'a' && **data <= 'f') + next_char |= (10 + (**data - 'a')); + else { + // Invalid hex digit = invalid JSON + 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 + str += next_char; + + // Move on + (*data)++; + } + + // If we're here, the string ended incorrectly + return false; +} + +/** +* 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 NULL; + else + return new JSONValue(str); + } + + // Is it a boolean? + else if ((simplejson_wcsnlen(*data, 4) && wcsncasecmp(*data, "true", 4) == 0) || (simplejson_wcsnlen(*data, 5) && wcsncasecmp(*data, "false", 5) == 0)) { + bool value = wcsncasecmp(*data, "true", 4) == 0; + (*data) += value ? 4 : 5; + return new JSONValue(value); + } + + // Is it a null? + else if (simplejson_wcsnlen(*data, 4) && wcsncasecmp(*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)++; + + double number = 0.0; + + // Parse the whole part of the number - only if it wasn't 0 + if (**data == '0') + (*data)++; + else if (**data >= '1' && **data <= '9') + number = JSON::ParseInt(data); + else + return NULL; + + // Could be a decimal now... + if (**data == '.') { + (*data)++; + + // Not get any digits? + if (!(**data >= '0' && **data <= '9')) + return NULL; + + // 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; + } + + // 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 NULL; + + // 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); + } + + // Was it neg? + if (neg) number *= -1; + + 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 NULL; + } + + // 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 NULL; + } + + // More whitespace? + if (!JSON::SkipWhitespace(data)) { + FREE_OBJECT(object); + return NULL; + } + + // Need a : now + if (*((*data)++) != ':') { + FREE_OBJECT(object); + return NULL; + } + + // More whitespace? + if (!JSON::SkipWhitespace(data)) { + FREE_OBJECT(object); + return NULL; + } + + // The value is here + JSONValue* value = Parse(data); + if (value == NULL) { + FREE_OBJECT(object); + return NULL; + } + + // 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 NULL; + } + + // End of object? + if (**data == '}') { + (*data)++; + return new JSONValue(object); + } + + // Want a , now + if (**data != ',') { + FREE_OBJECT(object); + return NULL; + } + + (*data)++; + } + + // Only here if we ran out of data + FREE_OBJECT(object); + return NULL; + } + + // An array? + else if (**data == '[') { + JSONArray array; + + (*data)++; + + while (**data != 0) { + // Whitespace at the start? + if (!JSON::SkipWhitespace(data)) { + FREE_ARRAY(array); + return NULL; + } + + // Special case - empty array + if (array.size() == 0 && **data == ']') { + (*data)++; + return new JSONValue(array); + } + + // Get the value + JSONValue* value = Parse(data); + if (value == NULL) { + FREE_ARRAY(array); + return NULL; + } + + // Add the value + array.push_back(value); + + // More whitespace? + if (!JSON::SkipWhitespace(data)) { + FREE_ARRAY(array); + return NULL; + } + + // End of array? + if (**data == ']') { + (*data)++; + return new JSONValue(array); + } + + // Want a , now + if (**data != ',') { + FREE_ARRAY(array); + return NULL; + } + + (*data)++; + } + + // Only here if we ran out of data + FREE_ARRAY(array); + return NULL; + } + + // Ran out of possibilites, it's bad! + else { + return NULL; + } +} + +/** +* 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* m_char_value) { + type = JSONType_String; + string_value = new String(String(m_char_value)); +} + +/** +* 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& m_string_value) { + type = JSONType_String; + string_value = new String(m_string_value); +} + +/** +* 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 m_bool_value) { + type = JSONType_Bool; + bool_value = m_bool_value; +} + +/** +* 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 m_number_value) { + type = JSONType_Number; + number_value = m_number_value; +} + +/** +* 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& m_array_value) { + type = JSONType_Array; + array_value = new JSONArray(m_array_value); +} + +/** +* 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& m_object_value) { + type = JSONType_Object; + object_value = new JSONObject(m_object_value); +} + +/** +* 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& m_source) { + type = m_source.type; + + switch (type) { + case JSONType_String: + string_value = new String(*m_source.string_value); + break; + + case JSONType_Bool: + bool_value = m_source.bool_value; + break; + + case JSONType_Number: + number_value = m_source.number_value; + break; + + case JSONType_Array: { + JSONArray source_array = *m_source.array_value; + JSONArray::iterator iter; + array_value = new JSONArray(); + for (iter = source_array.begin(); iter != source_array.end(); iter++) + array_value->push_back(new JSONValue(**iter)); + break; + } + + case JSONType_Object: { + JSONObject source_object = *m_source.object_value; + object_value = new JSONObject(); + JSONObject::iterator iter; + for (iter = source_object.begin(); iter != source_object.end(); iter++) { + String name = (*iter)._key; + (*object_value)[name] = new JSONValue(*((*iter)._value)); + } + break; + } + + 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 = array_value->begin(); iter != array_value->end(); iter++) + delete *iter; + delete array_value; + } + else if (type == JSONType_Object) { + JSONObject::iterator iter; + for (iter = object_value->begin(); iter != object_value->end(); iter++) { + delete (*iter)._value; + } + delete object_value; + } + else if (type == JSONType_String) { + delete string_value; + } +} + +/** +* 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 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 (*string_value); +} + +/** +* 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 bool_value; +} + +/** +* 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 number_value; +} + +/** +* 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 (*array_value); +} + +/** +* 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 (*object_value); +} + +/** +* 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 array_value->size(); + case JSONType_Object: + return object_value->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 < array_value->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 < array_value->size()) { + return (*array_value)[index]; + } + else { + return NULL; + } +} + +/** +* 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 object_value->find(name) != object_value->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 = object_value->find(name); + if (it != object_value->end()) { + return it->_value; + } + else { + return NULL; + } +} + +/** +* 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 = object_value->begin(); + while (iter != object_value->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) { + case JSONType_Null: + ret_string = "null"; + break; + + case JSONType_String: + ret_string = StringifyString(*string_value); + break; + + case JSONType_Bool: + ret_string = bool_value ? "true" : "false"; + break; + + case JSONType_Number: { + if (isinf(number_value) || isnan(number_value)) + ret_string = "null"; + else { + char str[80]; + sprintf(str, "%.15Lf", number_value); //ss.precision(15); + ret_string = str; + } + break; + } + + case JSONType_Array: { + ret_string = indentDepth ? "[\n" + indentStr1 : "["; + JSONArray::const_iterator iter = array_value->begin(); + while (iter != array_value->end()) { + ret_string += (*iter)->StringifyImpl(indentDepth1); + + // Not at the end - add a separator + if (++iter != array_value->end()) + ret_string += ","; + } + ret_string += indentDepth ? "\n" + indentStr + "]" : "]"; + break; + } + + case JSONType_Object: { + ret_string = indentDepth ? "{\n" + indentStr1 : "{"; + JSONObject::const_iterator iter = object_value->begin(); + while (iter != object_value->end()) { + ret_string += StringifyString((*iter)._key); + ret_string += ":"; + ret_string += (*iter)._value->StringifyImpl(indentDepth1); + + // Not at the end - add a separator + if (++iter != object_value->end()) + ret_string += ","; + } + ret_string += indentDepth ? "\n" + indentStr + "}" : "}"; + break; + } + } + + return ret_string; +} + +/** +* Creates a JSON encoded string with all required fields escaped +* Works from http://www.ecma-internationl.org/publications/files/ECMA-ST/ECMA-262.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()) { + char chr = *iter; + + if (chr == '"' || chr == '\\' || chr == '/') { + str_out += '\\'; + str_out += chr; + } + else if (chr == '\b') { + str_out += "\\b"; + } + else if (chr == '\f') { + str_out += "\\f"; + } + else if (chr == '\n') { + str_out += "\\n"; + } + else if (chr == '\r') { + str_out += "\\r"; + } + else if (chr == '\t') { + str_out += "\\t"; + } + else if (chr < ' ' || chr > 126) { + str_out += "\\u"; + for (int i = 0; i < 4; i++) { + int value = (chr >> 12) & 0xf; + if (value >= 0 && value <= 9) + str_out += (char)('0' + value); + else if (value >= 10 && value <= 15) + str_out += (char)('A' + (value - 10)); + chr <<= 4; + } + } + else { + str_out += chr; + } + + iter++; + } + + str_out += "\""; + return str_out; +} + +/** +* 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 (int i = 0; i < depth * indent_step; ++i) indentStr += ' '; + return indentStr; +} + +} // End of namespace Common |