Files
GDevelop/Core/GDCore/Serialization/Serializer.cpp
T
2014-12-14 19:31:42 +01:00

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;
}
}