Files
GDevelop/Core/GDCore/Serialization/Serializer.cpp

505 lines
15 KiB
C++

/*
* 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 <iomanip>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#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<gd::String, SerializerValue>& attributes =
element.GetAllAttributes();
for (std::map<gd::String, SerializerValue>::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<gd::String, std::shared_ptr<SerializerElement> > >& children =
element.GetAllChildren();
for (size_t i = 0; i < children.size(); ++i) {
if (children[i].second == std::shared_ptr<SerializerElement>()) 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("&", "&amp;")
.FindAndReplace("'", "&apos;")
.FindAndReplace("\"", "&quot;")
.FindAndReplace("<", "&lt;")
.FindAndReplace(">", "&gt;");
}
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
// </ sequence. Should add a flag to allow this compatibility mode and
// prevent this sequence from occurring.
default:
if (isControlCharacter(*c)) {
std::ostringstream oss;
oss << "\\u" << std::hex << std::uppercase << std::setfill('0')
<< std::setw(4) << static_cast<int>(*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<gd::String, std::shared_ptr<SerializerElement> > >&
children = element.GetAllChildren();
for (size_t i = 0; i < children.size(); ++i) {
if (children[i].second == std::shared_ptr<SerializerElement>())
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<gd::String, SerializerValue>& attributes =
element.GetAllAttributes();
for (std::map<gd::String, SerializerValue>::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<gd::String, std::shared_ptr<SerializerElement> > >&
children = element.GetAllChildren();
for (size_t i = 0; i < children.size(); ++i) {
if (children[i].second == std::shared_ptr<SerializerElement>())
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<double>());
return endPos;
}
}
} // namespace
SerializerElement Serializer::FromJSON(const std::string& jsonStr) {
SerializerElement element;
if (!jsonStr.empty()) gd::ParseJSONObject(jsonStr, 0, element);
return element;
}
} // namespace gd