mirror of
https://github.com/Heretek-AI/GDevelop.git
synced 2026-07-22 01:55:25 -04:00
505 lines
15 KiB
C++
505 lines
15 KiB
C++
/*
|
|
* GDevelop Core
|
|
* Copyright 2008-2014 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
|
|
* This project is released under the MIT License.
|
|
*/
|
|
|
|
#include "GDCore/Serialization/SerializerElement.h"
|
|
#include "GDCore/Serialization/Serializer.h"
|
|
#include "GDCore/CommonTools.h"
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <utility>
|
|
#include <iomanip>
|
|
#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<std::string, SerializerValue> & attributes = element.GetAllAttributes();
|
|
for (std::map<std::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<std::string, boost::shared_ptr<SerializerElement> > > & children = element.GetAllChildren();
|
|
for (size_t i = 0; i < children.size(); ++i)
|
|
{
|
|
if (children[i].second == boost::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 )
|
|
{
|
|
std::string name = attr->Name();
|
|
if (attr->Value())
|
|
element.SetAttribute(name, std::string(attr->Value()));
|
|
}
|
|
|
|
attr = attr->Next();
|
|
}
|
|
|
|
const TiXmlElement * child = xmlElement->FirstChildElement();
|
|
while(child)
|
|
{
|
|
if (child->Value())
|
|
{
|
|
std::string name = child->Value();
|
|
SerializerElement & childElement = element.AddChild(name);
|
|
FromXML(childElement, child);
|
|
}
|
|
|
|
child = child->NextSiblingElement();
|
|
}
|
|
|
|
if (xmlElement->GetText())
|
|
{
|
|
SerializerValue value;
|
|
value.Set(xmlElement->GetText());
|
|
element.SetValue(value);
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
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/).
|
|
*/
|
|
std::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 std::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 result;
|
|
}
|
|
|
|
std::string ValueToJSON(const SerializerValue & val)
|
|
{
|
|
if (val.IsBoolean())
|
|
return val.GetBool() ? "true" : "false";
|
|
else if (val.IsInt())
|
|
return gd::ToString(val.GetInt());
|
|
else if (val.IsDouble())
|
|
return gd::ToString(val.GetDouble());
|
|
else
|
|
return StringToQuotedJSONString(val.GetString().c_str());
|
|
}
|
|
}
|
|
|
|
std::string Serializer::ToJSON(const SerializerElement & element)
|
|
{
|
|
if (element.IsValueUndefined())
|
|
{
|
|
if ( !element.ConsideredAsArrayOf().empty() )
|
|
{
|
|
//Store the element as an array in JSON:
|
|
std::string str = "[";
|
|
bool firstChild = true;
|
|
|
|
if ( element.GetAllAttributes().size() > 0 )
|
|
{
|
|
std::cout << "WARNING: A SerializerElement is considered as an array of " << element.ConsideredAsArrayOf()
|
|
<< " but has attributes. These attributes won't be saved!" << std::endl;
|
|
}
|
|
|
|
const std::vector< std::pair<std::string, boost::shared_ptr<SerializerElement> > > & children = element.GetAllChildren();
|
|
for (size_t i = 0; i < children.size(); ++i)
|
|
{
|
|
if (children[i].second == boost::shared_ptr<SerializerElement>())
|
|
continue;
|
|
if (children[i].first != element.ConsideredAsArrayOf())
|
|
{
|
|
std::cout << "WARNING: A SerializerElement is considered as an array of " << element.ConsideredAsArrayOf()
|
|
<< " but has a children called " << children[i].first << ". This children won't be saved!" << std::endl;
|
|
continue;
|
|
}
|
|
|
|
if ( !firstChild ) str += ",";
|
|
str += ToJSON(*children[i].second);
|
|
|
|
firstChild = false;
|
|
}
|
|
|
|
str += "]";
|
|
return str;
|
|
}
|
|
else
|
|
{
|
|
std::string str = "{";
|
|
bool firstChild = true;
|
|
|
|
const std::map<std::string, SerializerValue> & attributes = element.GetAllAttributes();
|
|
for (std::map<std::string, SerializerValue>::const_iterator it = attributes.begin();
|
|
it != attributes.end();++it)
|
|
{
|
|
if ( !firstChild ) str += ",";
|
|
str += "\""+it->first+"\": "+ValueToJSON(it->second);
|
|
|
|
firstChild = false;
|
|
}
|
|
|
|
const std::vector< std::pair<std::string, boost::shared_ptr<SerializerElement> > > & children = element.GetAllChildren();
|
|
for (size_t i = 0; i < children.size(); ++i)
|
|
{
|
|
if (children[i].second == boost::shared_ptr<SerializerElement>())
|
|
continue;
|
|
|
|
if ( !firstChild ) str += ",";
|
|
str += "\""+children[i].first+"\": "+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(childName));
|
|
|
|
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
|
|
{
|
|
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(str);
|
|
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(ToDouble(str));
|
|
return endPos;
|
|
}
|
|
}
|
|
}
|
|
|
|
SerializerElement Serializer::FromJSON(const std::string & jsonStr)
|
|
{
|
|
SerializerElement element;
|
|
if ( !jsonStr.empty() ) gd::ParseJSONObject(jsonStr, 0, element);
|
|
return element;
|
|
}
|
|
|
|
|
|
}
|