/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "AtlasObject.h"
#include "AtlasObjectImpl.h"
#include
#include
#include
// TODO: replace most of the asserts below (e.g. for when it fails to load
// a file) with some proper logging/reporting system
static AtSmartPtr ConvertNode(xmlNodePtr node);
AtObj AtlasObject::LoadFromXML(const std::string& xml)
{
xmlDocPtr doc = xmlReadMemory(xml.c_str(), xml.length(), "noname.xml", NULL, XML_PARSE_NONET|XML_PARSE_NOCDATA);
if (doc == NULL)
return AtObj();
// TODO: Need to report the error message somehow
xmlNodePtr root = xmlDocGetRootElement(doc);
AtObj obj;
obj.m_Node = ConvertNode(root);
AtObj rootObj;
rootObj.set((const char*)root->name, obj);
xmlFreeDoc(doc);
return rootObj;
}
// Convert from a DOMElement to an AtNode
static AtSmartPtr ConvertNode(xmlNodePtr node)
{
AtSmartPtr obj (new AtNode());
// Loop through all attributes
for (xmlAttrPtr cur_attr = node->properties; cur_attr; cur_attr = cur_attr->next)
{
std::string name ("@");
name += reinterpret_cast(cur_attr->name);
xmlChar* content = xmlNodeGetContent(cur_attr->children);
std::string value = reinterpret_cast(content);
xmlFree(content);
AtNode* newNode = new AtNode(value.c_str());
obj->m_Children.insert(AtNode::child_pairtype(
name.c_str(), AtNode::Ptr(newNode)
));
}
// Loop through all child elements
for (xmlNodePtr cur_node = node->children; cur_node; cur_node = cur_node->next)
{
if (cur_node->type == XML_ELEMENT_NODE)
{
obj->m_Children.insert(AtNode::child_pairtype(
reinterpret_cast(cur_node->name), ConvertNode(cur_node)
));
}
else if (cur_node->type == XML_TEXT_NODE)
{
xmlChar* content = xmlNodeGetContent(cur_node);
std::string value = reinterpret_cast(content);
xmlFree(content);
obj->m_Value += value;
}
}
// Trim whitespace surrounding the string value
const std::string whitespace = " \t\r\n";
size_t first = obj->m_Value.find_first_not_of(whitespace);
if (first == std::string::npos)
obj->m_Value = "";
else
{
size_t last = obj->m_Value.find_last_not_of(whitespace);
obj->m_Value = obj->m_Value.substr(first, 1+last-first);
}
return obj;
}
// Build a DOM node from a given AtNode
static void BuildDOMNode(xmlDocPtr doc, xmlNodePtr node, AtNode::Ptr p)
{
if (p)
{
if (p->m_Value.length())
xmlNodeAddContent(node, reinterpret_cast(p->m_Value.c_str()));
for (const AtNode::child_maptype::value_type& child : p->m_Children)
{
const xmlChar* first_child = reinterpret_cast(child.first.c_str());
// Test for attribute nodes (whose names start with @)
if (child.first.length() && child.first[0] == '@')
{
assert(child.second);
assert(child.second->m_Children.empty());
xmlNewProp(node, first_child + 1, reinterpret_cast(child.second->m_Value.c_str()));
}
else
{
// First node in the document - needs to be made the root node
if (node == nullptr)
{
xmlNodePtr root = xmlNewNode(nullptr, first_child);
xmlDocSetRootElement(doc, root);
BuildDOMNode(doc, root, child.second);
}
else
{
xmlNodePtr newChild = xmlNewChild(node, nullptr, first_child, nullptr);
BuildDOMNode(doc, newChild, child.second);
}
}
}
}
}
std::string AtlasObject::SaveToXML(AtObj& obj)
{
if (!obj.m_Node || obj.m_Node->m_Children.size() != 1)
{
assert(! "SaveToXML: root must only have one child");
return "";
}
AtNode::Ptr firstChild (obj.m_Node->m_Children.begin()->second);
xmlDocPtr doc = xmlNewDoc((const xmlChar*)"1.0");
BuildDOMNode(doc, nullptr, obj.m_Node);
xmlChar* buf;
int size;
xmlDocDumpFormatMemoryEnc(doc, &buf, &size, "utf-8", 1);
std::string ret((const char*)buf, size);
xmlFree(buf);
xmlFreeDoc(doc);
// TODO: handle errors better
return ret;
}