text: /*
   * 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
  