Line data Source code
1 : /* Copyright (C) 2021 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "AtlasObject.h"
19 : #include "AtlasObjectImpl.h"
20 :
21 : #include <cassert>
22 : #include <libxml/parser.h>
23 : #include <string>
24 :
25 : // TODO: replace most of the asserts below (e.g. for when it fails to load
26 : // a file) with some proper logging/reporting system
27 :
28 : static AtSmartPtr<AtNode> ConvertNode(xmlNodePtr node);
29 :
30 13 : AtObj AtlasObject::LoadFromXML(const std::string& xml)
31 : {
32 13 : xmlDocPtr doc = xmlReadMemory(xml.c_str(), xml.length(), "noname.xml", NULL, XML_PARSE_NONET|XML_PARSE_NOCDATA);
33 13 : if (doc == NULL)
34 0 : return AtObj();
35 : // TODO: Need to report the error message somehow
36 :
37 13 : xmlNodePtr root = xmlDocGetRootElement(doc);
38 26 : AtObj obj;
39 13 : obj.m_Node = ConvertNode(root);
40 :
41 26 : AtObj rootObj;
42 13 : rootObj.set((const char*)root->name, obj);
43 :
44 13 : xmlFreeDoc(doc);
45 :
46 13 : return rootObj;
47 : }
48 :
49 : // Convert from a DOMElement to an AtNode
50 21 : static AtSmartPtr<AtNode> ConvertNode(xmlNodePtr node)
51 : {
52 21 : AtSmartPtr<AtNode> obj (new AtNode());
53 :
54 : // Loop through all attributes
55 28 : for (xmlAttrPtr cur_attr = node->properties; cur_attr; cur_attr = cur_attr->next)
56 : {
57 14 : std::string name ("@");
58 7 : name += reinterpret_cast<const char*>(cur_attr->name);
59 7 : xmlChar* content = xmlNodeGetContent(cur_attr->children);
60 14 : std::string value = reinterpret_cast<char*>(content);
61 7 : xmlFree(content);
62 :
63 7 : AtNode* newNode = new AtNode(value.c_str());
64 14 : obj->m_Children.insert(AtNode::child_pairtype(
65 14 : name.c_str(), AtNode::Ptr(newNode)
66 7 : ));
67 : }
68 :
69 : // Loop through all child elements
70 41 : for (xmlNodePtr cur_node = node->children; cur_node; cur_node = cur_node->next)
71 : {
72 20 : if (cur_node->type == XML_ELEMENT_NODE)
73 : {
74 16 : obj->m_Children.insert(AtNode::child_pairtype(
75 16 : reinterpret_cast<const char*>(cur_node->name), ConvertNode(cur_node)
76 8 : ));
77 : }
78 12 : else if (cur_node->type == XML_TEXT_NODE)
79 : {
80 10 : xmlChar* content = xmlNodeGetContent(cur_node);
81 20 : std::string value = reinterpret_cast<char*>(content);
82 10 : xmlFree(content);
83 10 : obj->m_Value += value;
84 : }
85 : }
86 :
87 : // Trim whitespace surrounding the string value
88 42 : const std::string whitespace = " \t\r\n";
89 21 : size_t first = obj->m_Value.find_first_not_of(whitespace);
90 21 : if (first == std::string::npos)
91 13 : obj->m_Value = "";
92 : else
93 : {
94 8 : size_t last = obj->m_Value.find_last_not_of(whitespace);
95 8 : obj->m_Value = obj->m_Value.substr(first, 1+last-first);
96 : }
97 :
98 42 : return obj;
99 : }
100 :
101 : // Build a DOM node from a given AtNode
102 34 : static void BuildDOMNode(xmlDocPtr doc, xmlNodePtr node, AtNode::Ptr p)
103 : {
104 34 : if (p)
105 : {
106 34 : if (p->m_Value.length())
107 8 : xmlNodeAddContent(node, reinterpret_cast<const xmlChar*>(p->m_Value.c_str()));
108 :
109 62 : for (const AtNode::child_maptype::value_type& child : p->m_Children)
110 : {
111 28 : const xmlChar* first_child = reinterpret_cast<const xmlChar*>(child.first.c_str());
112 :
113 : // Test for attribute nodes (whose names start with @)
114 28 : if (child.first.length() && child.first[0] == '@')
115 : {
116 7 : assert(child.second);
117 7 : assert(child.second->m_Children.empty());
118 7 : xmlNewProp(node, first_child + 1, reinterpret_cast<const xmlChar*>(child.second->m_Value.c_str()));
119 : }
120 : else
121 : {
122 : // First node in the document - needs to be made the root node
123 21 : if (node == nullptr)
124 : {
125 13 : xmlNodePtr root = xmlNewNode(nullptr, first_child);
126 13 : xmlDocSetRootElement(doc, root);
127 13 : BuildDOMNode(doc, root, child.second);
128 : }
129 : else
130 : {
131 8 : xmlNodePtr newChild = xmlNewChild(node, nullptr, first_child, nullptr);
132 8 : BuildDOMNode(doc, newChild, child.second);
133 : }
134 : }
135 : }
136 : }
137 34 : }
138 :
139 13 : std::string AtlasObject::SaveToXML(AtObj& obj)
140 : {
141 13 : if (!obj.m_Node || obj.m_Node->m_Children.size() != 1)
142 : {
143 0 : assert(! "SaveToXML: root must only have one child");
144 : return "";
145 : }
146 :
147 26 : AtNode::Ptr firstChild (obj.m_Node->m_Children.begin()->second);
148 :
149 13 : xmlDocPtr doc = xmlNewDoc((const xmlChar*)"1.0");
150 13 : BuildDOMNode(doc, nullptr, obj.m_Node);
151 :
152 : xmlChar* buf;
153 : int size;
154 13 : xmlDocDumpFormatMemoryEnc(doc, &buf, &size, "utf-8", 1);
155 :
156 26 : std::string ret((const char*)buf, size);
157 :
158 13 : xmlFree(buf);
159 13 : xmlFreeDoc(doc);
160 :
161 : // TODO: handle errors better
162 :
163 13 : return ret;
164 : }
|