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 "precompiled.h"
19 :
20 : #include "XMLWriter.h"
21 :
22 : #include "ps/CLogger.h"
23 : #include "ps/Filesystem.h"
24 : #include "ps/XML/Xeromyces.h"
25 : #include "lib/utf8.h"
26 : #include "lib/allocators/shared_ptr.h"
27 : #include "lib/sysdep/cpu.h"
28 : #include "maths/Fixed.h"
29 :
30 :
31 : // TODO (maybe): Write to the file frequently, instead of buffering
32 : // the entire file, so that large files get written faster.
33 :
34 : namespace
35 : {
36 5 : CStr escapeAttributeValue(const char* input)
37 : {
38 : // Spec says:
39 : // AttValue ::= '"' ([^<&"] | Reference)* '"'
40 : // so > is allowed in attribute values, so we don't bother escaping it.
41 :
42 5 : CStr ret = input;
43 5 : ret.Replace("&", "&");
44 5 : ret.Replace("<", "<");
45 5 : ret.Replace("\"", """);
46 5 : return ret;
47 : }
48 :
49 12 : CStr escapeCharacterData(const char* input)
50 : {
51 : // CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*)
52 :
53 12 : CStr ret = input;
54 12 : ret.Replace("&", "&");
55 12 : ret.Replace("<", "<");
56 12 : ret.Replace("]]>", "]]>");
57 12 : return ret;
58 : }
59 :
60 1 : CStr escapeCDATA(const char* input)
61 : {
62 1 : CStr ret = input;
63 1 : ret.Replace("]]>", "]]>]]><![CDATA[");
64 1 : return ret;
65 : }
66 :
67 3 : CStr escapeComment(const char* input)
68 : {
69 : // Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->'
70 : // This just avoids double-hyphens, and doesn't enforce the no-hyphen-at-end
71 : // rule, since it's only used in contexts where there's already a space
72 : // between this data and the -->.
73 3 : CStr ret = input;
74 3 : ret.Replace("--", "\xE2\x80\x90\xE2\x80\x90");
75 : // replace with U+2010 HYPHEN, because it's close enough and it's
76 : // probably nicer than inserting spaces or deleting hyphens or
77 : // any alternative
78 3 : return ret;
79 : }
80 : }
81 :
82 : enum { EL_ATTR, EL_TEXT, EL_SUBEL };
83 :
84 9 : XMLWriter_File::XMLWriter_File()
85 : : m_Indent(0), m_LastElement(NULL),
86 9 : m_PrettyPrint(true)
87 : {
88 : // Encoding is always UTF-8 - that's one of the only two guaranteed to be
89 : // supported by XML parsers (along with UTF-16), and there's not much need
90 : // to let people choose another.
91 9 : m_Data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
92 9 : }
93 :
94 0 : bool XMLWriter_File::StoreVFS(const PIVFS& vfs, const VfsPath& pathname)
95 : {
96 0 : if (m_LastElement) debug_warn(L"ERROR: Saving XML while an element is still open");
97 :
98 0 : const size_t size = m_Data.length();
99 0 : std::shared_ptr<u8> data;
100 0 : AllocateAligned(data, size, maxSectorSize);
101 0 : memcpy(data.get(), m_Data.data(), size);
102 0 : Status ret = vfs->CreateFile(pathname, data, size);
103 0 : if (ret < 0)
104 : {
105 0 : LOGERROR("Error saving XML data through VFS: %lld '%s'", (long long)ret, pathname.string8());
106 0 : return false;
107 : }
108 0 : return true;
109 : }
110 :
111 9 : const CStr& XMLWriter_File::GetOutput()
112 : {
113 9 : return m_Data;
114 : }
115 :
116 :
117 0 : void XMLWriter_File::XMB(const XMBData& xmb)
118 : {
119 0 : ElementXMB(xmb, xmb.GetRoot());
120 0 : }
121 :
122 0 : void XMLWriter_File::ElementXMB(const XMBData& xmb, XMBElement el)
123 : {
124 0 : XMLWriter_Element writer(*this, xmb.GetElementString(el.GetNodeName()));
125 :
126 0 : XERO_ITER_ATTR(el, attr)
127 0 : writer.Attribute(xmb.GetAttributeString(attr.Name), attr.Value);
128 :
129 0 : XERO_ITER_EL(el, child)
130 0 : ElementXMB(xmb, child);
131 0 : }
132 :
133 3 : void XMLWriter_File::Comment(const char* text)
134 : {
135 3 : ElementStart(NULL, "!-- ");
136 3 : m_Data += escapeComment(text);
137 3 : m_Data += " -->";
138 3 : --m_Indent;
139 3 : }
140 :
141 27 : CStr XMLWriter_File::Indent()
142 : {
143 27 : return std::string(m_Indent, '\t');
144 : }
145 :
146 24 : void XMLWriter_File::ElementStart(XMLWriter_Element* element, const char* name)
147 : {
148 24 : if (m_LastElement) m_LastElement->Close(EL_SUBEL);
149 24 : m_LastElement = element;
150 :
151 24 : if (m_PrettyPrint)
152 : {
153 21 : m_Data += "\n";
154 21 : m_Data += Indent();
155 : }
156 24 : m_Data += "<";
157 24 : m_Data += name;
158 :
159 24 : ++m_Indent;
160 24 : }
161 :
162 18 : void XMLWriter_File::ElementClose()
163 : {
164 18 : m_Data += ">";
165 18 : }
166 :
167 21 : void XMLWriter_File::ElementEnd(const char* name, int type)
168 : {
169 21 : --m_Indent;
170 21 : m_LastElement = NULL;
171 :
172 21 : switch (type)
173 : {
174 2 : case EL_ATTR:
175 2 : m_Data += "/>";
176 2 : break;
177 11 : case EL_TEXT:
178 11 : m_Data += "</";
179 11 : m_Data += name;
180 11 : m_Data += ">";
181 11 : break;
182 8 : case EL_SUBEL:
183 8 : if (m_PrettyPrint)
184 : {
185 6 : m_Data += "\n";
186 6 : m_Data += Indent();
187 : }
188 8 : m_Data += "</";
189 8 : m_Data += name;
190 8 : m_Data += ">";
191 8 : break;
192 0 : default:
193 0 : DEBUG_WARN_ERR(ERR::LOGIC);
194 : }
195 21 : }
196 :
197 13 : void XMLWriter_File::ElementText(const char* text, bool cdata)
198 : {
199 13 : if (cdata)
200 : {
201 1 : m_Data += "<![CDATA[";
202 1 : m_Data += escapeCDATA(text);
203 1 : m_Data += "]]>";
204 : }
205 : else
206 : {
207 12 : m_Data += escapeCharacterData(text);
208 : }
209 13 : }
210 :
211 :
212 20 : XMLWriter_Element::XMLWriter_Element(XMLWriter_File& file, const char* name)
213 20 : : m_File(&file), m_Name(name), m_Type(EL_ATTR)
214 : {
215 20 : m_File->ElementStart(this, name);
216 20 : }
217 :
218 :
219 40 : XMLWriter_Element::~XMLWriter_Element()
220 : {
221 20 : m_File->ElementEnd(m_Name.c_str(), m_Type);
222 20 : }
223 :
224 :
225 20 : void XMLWriter_Element::Close(int type)
226 : {
227 20 : if (m_Type == type)
228 2 : return;
229 :
230 18 : m_File->ElementClose();
231 18 : m_Type = type;
232 : }
233 :
234 :
235 : // Template specialisations for various string types:
236 :
237 12 : template <> void XMLWriter_Element::Text<const char*>(const char* text, bool cdata)
238 : {
239 12 : Close(EL_TEXT);
240 12 : m_File->ElementText(text, cdata);
241 12 : }
242 :
243 1 : template <> void XMLWriter_Element::Text<const wchar_t*>(const wchar_t* text, bool cdata)
244 : {
245 1 : Text( CStrW(text).ToUTF8().c_str(), cdata );
246 1 : }
247 :
248 : //
249 :
250 6 : template <> void XMLWriter_File::ElementAttribute<const char*>(const char* name, const char* const& value, bool newelement)
251 : {
252 6 : if (newelement)
253 : {
254 1 : ElementStart(NULL, name);
255 1 : m_Data += ">";
256 1 : ElementText(value, false);
257 1 : ElementEnd(name, EL_TEXT);
258 : }
259 : else
260 : {
261 5 : ENSURE(m_LastElement && m_LastElement->m_Type == EL_ATTR);
262 5 : m_Data += " ";
263 5 : m_Data += name;
264 5 : m_Data += "=\"";
265 5 : m_Data += escapeAttributeValue(value);
266 5 : m_Data += "\"";
267 : }
268 6 : }
269 :
270 : // Attribute/setting value-to-string template specialisations.
271 : //
272 : // These only deal with basic types. Anything more complicated should
273 : // be converted into a basic type by whatever is making use of XMLWriter,
274 : // to keep game-related logic out of the not-directly-game-related code here.
275 :
276 0 : template <> void XMLWriter_File::ElementAttribute<CStr>(const char* name, const CStr& value, bool newelement)
277 : {
278 0 : ElementAttribute(name, value.c_str(), newelement);
279 0 : }
280 0 : template <> void XMLWriter_File::ElementAttribute<std::string>(const char* name, const std::string& value, bool newelement)
281 : {
282 0 : ElementAttribute(name, value.c_str(), newelement);
283 0 : }
284 : // Encode Unicode strings as UTF-8
285 0 : template <> void XMLWriter_File::ElementAttribute<CStrW>(const char* name, const CStrW& value, bool newelement)
286 : {
287 0 : ElementAttribute(name, value.ToUTF8(), newelement);
288 0 : }
289 0 : template <> void XMLWriter_File::ElementAttribute<std::wstring>(const char* name, const std::wstring& value, bool newelement)
290 : {
291 0 : ElementAttribute(name, utf8_from_wstring(value).c_str(), newelement);
292 0 : }
293 :
294 0 : template <> void XMLWriter_File::ElementAttribute<fixed>(const char* name, const fixed& value, bool newelement)
295 : {
296 0 : ElementAttribute(name, value.ToString().c_str(), newelement);
297 0 : }
298 :
299 1 : template <> void XMLWriter_File::ElementAttribute<int>(const char* name, const int& value, bool newelement)
300 : {
301 2 : std::stringstream ss;
302 1 : ss << value;
303 1 : ElementAttribute(name, ss.str().c_str(), newelement);
304 1 : }
305 :
306 0 : template <> void XMLWriter_File::ElementAttribute<unsigned int>(const char* name, const unsigned int& value, bool newelement)
307 : {
308 0 : std::stringstream ss;
309 0 : ss << value;
310 0 : ElementAttribute(name, ss.str().c_str(), newelement);
311 0 : }
312 :
313 0 : template <> void XMLWriter_File::ElementAttribute<float>(const char* name, const float& value, bool newelement)
314 : {
315 0 : std::stringstream ss;
316 0 : ss << value;
317 0 : ElementAttribute(name, ss.str().c_str(), newelement);
318 0 : }
319 :
320 2 : template <> void XMLWriter_File::ElementAttribute<double>(const char* name, const double& value, bool newelement)
321 : {
322 4 : std::stringstream ss;
323 2 : ss << value;
324 2 : ElementAttribute(name, ss.str().c_str(), newelement);
325 5 : }
|