/* * GDevelop Core * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights * reserved. This project is released under the MIT License. */ #include "GDCore/Serialization/Serializer.h" #include #include #include #include #include #include "GDCore/CommonTools.h" #include "GDCore/Serialization/SerializerElement.h" #if !defined(EMSCRIPTEN) #include "GDCore/TinyXml/tinyxml.h" #endif namespace gd { #if !defined(EMSCRIPTEN) void Serializer::ToXML(SerializerElement& element, TiXmlElement* xmlElement) { if (!xmlElement) return; if (element.IsValueUndefined()) { const std::map& attributes = element.GetAllAttributes(); for (std::map::const_iterator it = attributes.begin(); it != attributes.end(); ++it) { const SerializerValue& attr = it->second; if (attr.IsBoolean()) xmlElement->SetAttribute(it->first.c_str(), attr.GetBool() ? "true" : "false"); else if (attr.IsString()) xmlElement->SetAttribute(it->first.c_str(), attr.GetString().c_str()); else if (attr.IsInt()) xmlElement->SetAttribute(it->first.c_str(), attr.GetInt()); else if (attr.IsDouble()) xmlElement->SetDoubleAttribute(it->first.c_str(), attr.GetDouble()); else xmlElement->SetAttribute(it->first.c_str(), attr.GetString().c_str()); } const std::vector< std::pair > >& children = element.GetAllChildren(); for (size_t i = 0; i < children.size(); ++i) { if (children[i].second == std::shared_ptr()) continue; TiXmlElement* xmlChild = new TiXmlElement(children[i].first.c_str()); xmlElement->LinkEndChild(xmlChild); ToXML(*children[i].second, xmlChild); } } else { TiXmlText* xmlValue = new TiXmlText(element.GetValue().GetString().c_str()); xmlElement->LinkEndChild(xmlValue); } } void Serializer::FromXML(SerializerElement& element, const TiXmlElement* xmlElement) { if (!xmlElement) return; const TiXmlAttribute* attr = xmlElement->FirstAttribute(); while (attr) { if (attr->Name() != NULL) { gd::String name = gd::String::FromUTF8(attr->Name()).ReplaceInvalid(); if (attr->Value()) element.SetAttribute( name, gd::String::FromUTF8(attr->Value()).ReplaceInvalid()); } attr = attr->Next(); } const TiXmlElement* child = xmlElement->FirstChildElement(); while (child) { if (child->Value()) { gd::String name = gd::String::FromUTF8(child->Value()).ReplaceInvalid(); SerializerElement& childElement = element.AddChild(name); FromXML(childElement, child); } child = child->NextSiblingElement(); } if (xmlElement->GetText()) { SerializerValue value; value.Set(gd::String::FromUTF8(xmlElement->GetText()).ReplaceInvalid()); element.SetValue(value); } } #endif gd::String Serializer::ToEscapedXMLString(const gd::String& str) { return str.FindAndReplace("&", "&") .FindAndReplace("'", "'") .FindAndReplace("\"", """) .FindAndReplace("<", "<") .FindAndReplace(">", ">"); } namespace { /** * Adapted from public domain library "jsoncpp" * (http://sourceforge.net/projects/jsoncpp/). */ static inline bool isControlCharacter(char ch) { return ch > 0 && ch <= 0x1F; } /** * Adapted from public domain library "jsoncpp" * (http://sourceforge.net/projects/jsoncpp/). */ static bool containsControlCharacter(const char* str) { while (*str) { if (isControlCharacter(*(str++))) return true; } return false; } /** * Tool function converting a string to a quoted string that can be inserted * into a JSON file. Adapted from public domain library "jsoncpp" * (http://sourceforge.net/projects/jsoncpp/). */ gd::String StringToQuotedJSONString(const char* value) { if (value == NULL) return ""; // Not sure how to handle unicode... if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && !containsControlCharacter(value)) return gd::String("\"") + value + "\""; // We have to walk value and escape any special characters. // Appending to std::string is not efficient, but this should be rare. // (Note: forward slashes are *not* rare, but I am not escaping them.) std::string::size_type maxsize = strlen(value) * 2 + 3; // allescaped+quotes+NULL std::string result; result.reserve(maxsize); // to avoid lots of mallocs result += "\""; for (const char* c = value; *c != 0; ++c) { switch (*c) { case '\"': result += "\\\""; break; case '\\': result += "\\\\"; break; case '\b': result += "\\b"; break; case '\f': result += "\\f"; break; case '\n': result += "\\n"; break; case '\r': result += "\\r"; break; case '\t': result += "\\t"; break; // case '/': // Even though \/ is considered a legal escape in JSON, a bare // slash is also legal, so I see no reason to escape it. // (I hope I am not misunderstanding something. // blep notes: actually escaping \/ may be useful in javascript to avoid // (*c); result += oss.str(); } else { result += *c; } break; } } result += "\""; return gd::String::FromUTF8(result); } gd::String ValueToJSON(const SerializerValue& val) { if (val.IsBoolean()) return val.GetBool() ? "true" : "false"; else if (val.IsInt()) return gd::String::From(val.GetInt()); else if (val.IsDouble()) return gd::String::From(val.GetDouble()); else return StringToQuotedJSONString(val.GetString().c_str()); } } // namespace gd::String Serializer::ToJSON(const SerializerElement& element) { if (element.IsValueUndefined()) { if (element.ConsideredAsArray()) { // Store the element as an array in JSON: gd::String str = "["; bool firstChild = true; if (element.GetAllAttributes().size() > 0) { std::cout << "ERROR: A SerializerElement is considered as an array of " << (element.ConsideredAsArrayOf().empty() ? "[unnamed elements]" : element.ConsideredAsArrayOf()) << " but has attributes. These attributes won't be saved!" << std::endl; } const std::vector< std::pair > >& children = element.GetAllChildren(); for (size_t i = 0; i < children.size(); ++i) { if (children[i].second == std::shared_ptr()) continue; if (children[i].first != element.ConsideredAsArrayOf()) { std::cout << "ERROR: A SerializerElement is considered as an array of " << (element.ConsideredAsArrayOf().empty() ? "[unnamed elements]" : element.ConsideredAsArrayOf()) << " but has a child called \"" << children[i].first << "\". This child won't be saved!" << std::endl; continue; } if (!firstChild) str += ","; str += ToJSON(*children[i].second); firstChild = false; } str += "]"; return str; } else { gd::String str = "{"; bool firstChild = true; const std::map& attributes = element.GetAllAttributes(); for (std::map::const_iterator it = attributes.begin(); it != attributes.end(); ++it) { if (!firstChild) str += ","; str += StringToQuotedJSONString(it->first.c_str()) + ": " + ValueToJSON(it->second); firstChild = false; } const std::vector< std::pair > >& children = element.GetAllChildren(); for (size_t i = 0; i < children.size(); ++i) { if (children[i].second == std::shared_ptr()) continue; if (attributes.find(children[i].first) != attributes.end()) { std::cout << "ERROR: An attribute and a children called \"" << children[i].first << "\" both exist. The children will erase the attribute - " "fix the usage of the attribute or (better) use " "children methods only." << std::endl; } if (!firstChild) str += ","; str += StringToQuotedJSONString(children[i].first.c_str()) + ": " + ToJSON(*children[i].second); firstChild = false; } str += "}"; return str; } } else { return ValueToJSON(element.GetValue()); } } // Private functions for JSON parsing namespace { size_t SkipBlankChar(const std::string& str, size_t pos) { const std::string blankChar = " \n"; return str.find_first_not_of(blankChar, pos); } /** * Adapted from https://github.com/hjiang/jsonxx */ std::string DecodeString(const std::string& original) { std::string value; value.reserve(original.size()); std::istringstream input("\"" + original + "\""); char ch = '\0', delimiter = '"'; input.get(ch); if (ch != delimiter) return ""; while (!input.eof() && input.good()) { input.get(ch); if (ch == delimiter) { break; } if (ch == '\\') { input.get(ch); switch (ch) { case '\\': case '/': value.push_back(ch); break; case 'b': value.push_back('\b'); break; case 'f': value.push_back('\f'); break; case 'n': value.push_back('\n'); break; case 'r': value.push_back('\r'); break; case 't': value.push_back('\t'); break; case 'u': { int i; std::stringstream ss; for (i = 0; (!input.eof() && input.good()) && i < 4; ++i) { input.get(ch); ss << ch; } if (input.good() && (ss >> i)) value.push_back(i); } break; default: if (ch != delimiter) { value.push_back('\\'); value.push_back(ch); } else value.push_back(ch); break; } } else { value.push_back(ch); } } if (input && ch == delimiter) { return value; } else { return ""; } } /** * Return the position of the end of the string. Blank are skipped if necessary * @param str The string to be used * @param startPos The start position * @param strContent A reference to a string that will be filled with the string * content. */ size_t SkipString(const std::string& str, size_t startPos, std::string& strContent) { startPos = SkipBlankChar(str, startPos); if (startPos >= str.length()) return std::string::npos; size_t endPos = startPos; if (str[startPos] == '"') { if (startPos + 1 >= str.length()) return std::string::npos; while (endPos == startPos || (str[endPos - 1] == '\\')) { endPos = str.find_first_of('\"', endPos + 1); if (endPos == std::string::npos) return std::string::npos; // Invalid string } strContent = DecodeString(str.substr(startPos + 1, endPos - 1 - startPos)); return endPos; } endPos = str.find_first_of(" \n,:"); if (endPos >= str.length()) return std::string::npos; // Invalid string strContent = DecodeString(str.substr(startPos, endPos - 1 - startPos)); return endPos - 1; } /** * Parse a JSON string, starting from pos, and storing the result into the * specified element. Note that the parsing is stopped as soon as a valid object * is parsed. \return The position at the end of the valid object stored into * the element. */ size_t ParseJSONObject(const std::string& jsonStr, size_t startPos, gd::SerializerElement& element) { size_t pos = SkipBlankChar(jsonStr, startPos); if (pos >= jsonStr.length()) return std::string::npos; if (jsonStr[pos] == '{') // Object { bool firstChild = true; while (firstChild || jsonStr[pos] == ',') { pos++; if (pos < jsonStr.length() && jsonStr[pos] == '}') break; std::string childName; pos = SkipString(jsonStr, pos, childName); pos++; pos = SkipBlankChar(jsonStr, pos); if (pos >= jsonStr.length() || jsonStr[pos] != ':') return std::string::npos; pos++; pos = ParseJSONObject( jsonStr, pos, element.AddChild(gd::String::FromUTF8(childName).ReplaceInvalid())); pos = SkipBlankChar(jsonStr, pos); if (pos >= jsonStr.length()) return std::string::npos; firstChild = false; } if (jsonStr[pos] != '}') { std::cout << "Parsing error: Object not properly formed."; return std::string::npos; } return pos + 1; } else if (jsonStr[pos] == '[') // Array { element.ConsiderAsArray(); unsigned int index = 0; while (index == 0 || jsonStr[pos] == ',') { pos++; if (pos < jsonStr.length() && jsonStr[pos] == ']') break; pos = ParseJSONObject(jsonStr, pos, element.AddChild("")); pos = SkipBlankChar(jsonStr, pos); if (pos >= jsonStr.length()) { std::cout << "Parsing error: element of array not properly formed."; return std::string::npos; } index++; } if (jsonStr[pos] != ']') { std::cout << "Parsing error: array not properly ended"; return std::string::npos; } return pos + 1; } else if (jsonStr[pos] == '"') // String { std::string str; pos = SkipString(jsonStr, pos, str); if (pos >= jsonStr.length()) { std::cout << "Parsing error: Invalid string"; return std::string::npos; } element.SetValue(gd::String::FromUTF8(str).ReplaceInvalid()); return pos + 1; } else // Number or boolean { std::string str; size_t endPos = pos; const std::string separators = " \n,}]"; while (endPos < jsonStr.length() && separators.find_first_of(jsonStr[endPos]) == std::string::npos) { endPos++; } str = jsonStr.substr(pos, endPos - pos); if (str == "true") element.SetValue(true); else if (str == "false") element.SetValue(false); else element.SetValue(gd::String::FromUTF8(str).To()); return endPos; } } } // namespace SerializerElement Serializer::FromJSON(const std::string& jsonStr) { SerializerElement element; if (!jsonStr.empty()) gd::ParseJSONObject(jsonStr, 0, element); return element; } } // namespace gd