openMSX
XMLElement.cc
Go to the documentation of this file.
1#include "XMLElement.hh"
2#include "ConfigException.hh"
3#include "File.hh"
4#include "FileContext.hh" // for bw compat
5#include "FileException.hh"
6#include "StringOp.hh"
7#include "XMLException.hh"
8#include "XMLOutputStream.hh"
9#include "rapidsax.hh"
10#include "serialize.hh"
11#include "serialize_meta.hh"
12#include "serialize_stl.hh"
13#include <cassert>
14#include <vector>
15
16namespace openmsx {
17
18// Returns nullptr when not found.
19const XMLElement* XMLElement::findChild(std::string_view childName) const
20{
21 for (const auto* child = firstChild; child; child = child->nextSibling) {
22 if (child->name == childName) {
23 return child;
24 }
25 }
26 return nullptr;
27}
28
29// Similar to above, but instead of starting the search from the start of the
30// list, start searching at 'hint'. This 'hint' parameter must be initialized
31// with 'firstChild', on each successful search this parameter is updated to
32// point to the next child (so that the next search starts from there). If the
33// end of the list is reached we restart the search from the start (so that the
34// full list is searched before giving up).
35const XMLElement* XMLElement::findChild(std::string_view childName, const XMLElement*& hint) const
36{
37 for (const auto* current = hint; current; current = current->nextSibling) {
38 if (current->name == childName) {
39 hint = current->nextSibling;
40 return current;
41 }
42 }
43 for (const auto* current = firstChild; current != hint; current = current->nextSibling) {
44 if (current->name == childName) {
45 hint = current->nextSibling;
46 return current;
47 }
48 }
49 return nullptr;
50}
51
52// Like findChild(), but throws when not found.
53const XMLElement& XMLElement::getChild(std::string_view childName) const
54{
55 if (const auto* elem = findChild(childName)) {
56 return *elem;
57 }
58 throw ConfigException("Missing tag \"", childName, "\".");
59}
60
61// Throws when not found.
62std::string_view XMLElement::getChildData(std::string_view childName) const
63{
64 return getChild(childName).getData();
65}
66
67// Returns default value when not found.
68std::string_view XMLElement::getChildData(
69 std::string_view childName, std::string_view defaultValue) const
70{
71 const auto* child = findChild(childName);
72 return child ? child->getData() : defaultValue;
73}
74
75bool XMLElement::getChildDataAsBool(std::string_view childName, bool defaultValue) const
76{
77 const auto* child = findChild(childName);
78 return child ? StringOp::stringToBool(child->getData()) : defaultValue;
79}
80
81int XMLElement::getChildDataAsInt(std::string_view childName, int defaultValue) const
82{
83 const auto* child = findChild(childName);
84 if (!child) return defaultValue;
85 auto r = StringOp::stringTo<int>(child->getData());
86 return r ? *r : defaultValue;
87}
88
90{
91 return std::distance(getChildren().begin(), getChildren().end());
92}
93
94// Return nullptr when not found.
95const XMLAttribute* XMLElement::findAttribute(std::string_view attrName) const
96{
97 for (const auto* attr = firstAttribute; attr; attr = attr->nextAttribute) {
98 if (attr->getName() == attrName) {
99 return attr;
100 }
101 }
102 return nullptr;
103}
104
105// Throws when not found.
106const XMLAttribute& XMLElement::getAttribute(std::string_view attrName) const
107{
108 const auto* result = findAttribute(attrName);
109 if (result) return *result;
110 throw ConfigException("Missing attribute \"", attrName, "\".");
111}
112
113// Throws when not found.
114std::string_view XMLElement::getAttributeValue(std::string_view attrName) const
115{
116 return getAttribute(attrName).getValue();
117}
118
119std::string_view XMLElement::getAttributeValue(std::string_view attrName,
120 std::string_view defaultValue) const
121{
122 const auto* attr = findAttribute(attrName);
123 return attr ? attr->getValue() : defaultValue;
124}
125
126// Returns default value when not found.
127bool XMLElement::getAttributeValueAsBool(std::string_view attrName,
128 bool defaultValue) const
129{
130 const auto* attr = findAttribute(attrName);
131 return attr ? StringOp::stringToBool(attr->getValue()) : defaultValue;
132}
133
134int XMLElement::getAttributeValueAsInt(std::string_view attrName,
135 int defaultValue) const
136{
137 const auto* attr = findAttribute(attrName);
138 if (!attr) return defaultValue;
139 auto r = StringOp::stringTo<int>(attr->getValue());
140 return r ? *r : defaultValue;
141}
142
143// Like findAttribute(), but returns a pointer-to-the-XMLAttribute-pointer.
144// This is mainly useful in combination with removeAttribute().
146{
147 for (auto** attr = &firstAttribute; *attr; attr = &(*attr)->nextAttribute) {
148 if ((*attr)->getName() == attrName) {
149 return attr;
150 }
151 }
152 return nullptr;
153}
154
155// Remove a specific attribute from the (single-linked) list.
157{
158 auto* attr = *attrPtr;
159 *attrPtr = attr->nextAttribute;
160}
161
163{
164 return std::distance(getAttributes().begin(), getAttributes().end());
165}
166
167
168// Search for child with given name, if it doesn't exist yet, then create it now
169// with given data. Don't change the data of an already existing child.
170XMLElement* XMLDocument::getOrCreateChild(XMLElement& parent, const char* childName, const char* childData)
171{
172 auto** elem = &parent.firstChild;
173 while (true) {
174 if (!*elem) {
175 auto* n = allocateElement(childName);
176 n->setData(childData);
177 *elem = n;
178 return n;
179 }
180 if ((*elem)->getName() == childName) {
181 return *elem;
182 }
183 elem = &(*elem)->nextSibling;
184 }
185}
186
187// Search for child with given name, and change the data of that child. If the
188// child didn't exist yet, then create it now (also with given data).
189XMLElement* XMLDocument::setChildData(XMLElement& parent, const char* childName, const char* childData)
190{
191 auto** elem = &parent.firstChild;
192 while (true) {
193 if (!*elem) {
194 auto* n = allocateElement(childName);
195 *elem = n;
196 (*elem)->setData(childData);
197 return *elem;
198 }
199 if ((*elem)->getName() == childName) {
200 (*elem)->setData(childData);
201 return *elem;
202 }
203 elem = &(*elem)->nextSibling;
204 }
205}
206
207// Set attribute to new value, if that attribute didn't exist yet, then also
208// create it.
209void XMLDocument::setAttribute(XMLElement& elem, const char* attrName, const char* attrValue)
210{
211 auto** attr = &elem.firstAttribute;
212 while (true) {
213 if (!*attr) {
214 auto* a = allocateAttribute(attrName, attrValue);
215 *attr = a;
216 return;
217 }
218 if ((*attr)->getName() == attrName) {
219 (*attr)->setValue(attrValue);
220 return;
221 }
222 attr = &(*attr)->nextAttribute;
223 }
224}
225
226
227// Helper to parse a XML file into a XMLDocument.
229{
230public:
232 : doc(doc_)
233 , nextElement(&doc.root) {}
234
235 [[nodiscard]] std::string_view getSystemID() const { return systemID; }
236
237 void start(std::string_view name) {
238 stack.push_back(currentElement);
239
240 auto* n = doc.allocateElement(name.data());
241 currentElement = n;
242
243 assert(*nextElement == nullptr);
244 *nextElement = n;
245 nextElement = &n->firstChild;
246
247 nextAttribute = &n->firstAttribute;
248 }
249
250 void stop() {
251 nextElement = &currentElement->nextSibling;
252 nextAttribute = nullptr;
253 currentElement = stack.back();
254 stack.pop_back();
255 }
256
257 void text(std::string_view text) {
258 currentElement->data = text.data();
259 }
260
261 void attribute(std::string_view name, std::string_view value) {
262 auto* a = doc.allocateAttribute(name.data(), value.data());
263
264 assert(nextAttribute);
265 assert(*nextAttribute == nullptr);
266 *nextAttribute = a;
267 nextAttribute = &a->nextAttribute;
268 }
269
270 void doctype(std::string_view txt) {
271 auto pos1 = txt.find(" SYSTEM ");
272 if (pos1 == std::string_view::npos) return;
273 if ((pos1 + 8) >= txt.size()) return;
274 char q = txt[pos1 + 8];
275 if (q != one_of('"', '\'')) return;
276 auto t = txt.substr(pos1 + 9);
277 auto pos2 = t.find(q);
278 if (pos2 == std::string_view::npos) return;
279
280 systemID = t.substr(0, pos2);
281 }
282
283private:
284 XMLDocument& doc;
285
286 std::string_view systemID;
287 std::vector<XMLElement*> stack;
288 XMLElement* currentElement = nullptr;
289 XMLElement** nextElement = nullptr;
290 XMLAttribute** nextAttribute = nullptr;
291};
292
294{
295 void* p = allocator.allocate(sizeof(XMLElement), alignof(XMLElement));
296 return new (p) XMLElement(name);
297}
298
299XMLElement* XMLDocument::allocateElement(const char* name, const char* data)
300{
301 void* p = allocator.allocate(sizeof(XMLElement), alignof(XMLElement));
302 return new (p) XMLElement(name, data);
303}
304
305XMLAttribute* XMLDocument::allocateAttribute(const char* name, const char* value)
306{
307 void* p = allocator.allocate(sizeof(XMLAttribute), alignof(XMLAttribute));
308 return new (p) XMLAttribute(name, value);
309}
310
311const char* XMLDocument::allocateString(std::string_view str)
312{
313 auto* p = static_cast<char*>(allocator.allocate(str.size() + 1, alignof(char)));
314 auto e = ranges::copy(str, p);
315 *e = '\0';
316 return p;
317}
318
319void XMLDocument::load(const std::string& filename, std::string_view systemID)
320{
321 assert(!root);
322
323 try {
324 File file(filename);
325 auto size = file.getSize();
327 file.read(std::span{buf.data(), size});
328 buf[size] = 0;
329 } catch (FileException& e) {
330 throw XMLException(filename, ": failed to read: ", e.getMessage());
331 }
332
333 XMLDocumentHandler handler(*this);
334 try {
335 rapidsax::parse<rapidsax::zeroTerminateStrings>(handler, buf.data());
336 } catch (rapidsax::ParseError& e) {
337 throw XMLException(filename, ": Document parsing failed: ", e.what());
338 }
339 if (!root) {
340 throw XMLException(filename,
341 ": Document doesn't contain mandatory root Element");
342 }
343 if (handler.getSystemID().empty()) {
344 throw XMLException(filename, ": Missing systemID.\n"
345 "You're probably using an old incompatible file format.");
346 }
347 if (handler.getSystemID() != systemID) {
348 throw XMLException(filename, ": systemID doesn't match "
349 "(expected ", systemID, ", got ", handler.getSystemID(), ")\n"
350 "You're probably using an old incompatible file format.");
351 }
352}
353
354XMLElement* XMLDocument::loadElement(MemInputArchive& ar)
355{
356 auto name = ar.loadStr();
357 if (name.empty()) return nullptr; // should only happen for empty document
358 auto* elem = allocateElement(allocateString(name));
359
360 unsigned numAttrs; ar.load(numAttrs);
361 auto** attrPtr = &elem->firstAttribute;
362 repeat(numAttrs, [&] {
363 const char* n = allocateString(ar.loadStr());
364 const char* v = allocateString(ar.loadStr());
365 auto* attr = allocateAttribute(n, v);
366 *attrPtr = attr;
367 attrPtr = &attr->nextAttribute;
368 });
369
370 unsigned numElems; ar.load(numElems);
371 if (numElems) {
372 auto** elemPtr = &elem->firstChild;
373 repeat(numElems, [&] {
374 auto* n = loadElement(ar);
375 *elemPtr = n;
376 elemPtr = &n->nextSibling;
377 });
378 } else {
379 auto data = ar.loadStr();
380 if (!data.empty()) {
381 elem->setData(allocateString(data));
382 }
383 }
384
385 return elem;
386}
387
388void XMLDocument::serialize(MemInputArchive& ar, unsigned /*version*/)
389{
390 root = loadElement(ar);
391}
392
393static void saveElement(MemOutputArchive& ar, const XMLElement& elem)
394{
395 ar.save(elem.getName());
396
397 ar.save(unsigned(elem.numAttributes()));
398 for (const auto& attr : elem.getAttributes()) {
399 ar.save(attr.getName());
400 ar.save(attr.getValue());
401 }
402
403 auto numElems = unsigned(elem.numChildren());
404 ar.save(numElems);
405 if (numElems) {
406 for (const auto& child : elem.getChildren()) {
407 saveElement(ar, child);
408 }
409 } else {
410 ar.save(elem.getData());
411 }
412}
413
414void XMLDocument::serialize(MemOutputArchive& ar, unsigned /*version*/)
415{
416 if (root) {
417 saveElement(ar, *root);
418 } else {
419 std::string_view empty;
420 ar.save(empty);
421 }
422}
423
424XMLElement* XMLDocument::clone(const XMLElement& inElem)
425{
426 auto* outElem = allocateElement(allocateString(inElem.getName()));
427
428 auto** attrPtr = &outElem->firstAttribute;
429 for (const auto& inAttr : inElem.getAttributes()) {
430 const char* n = allocateString(inAttr.getName());
431 const char* v = allocateString(inAttr.getValue());
432 auto* outAttr = allocateAttribute(n, v);
433 *attrPtr = outAttr;
434 attrPtr = &outAttr->nextAttribute;
435 }
436
437 if (auto data = inElem.getData() ; !data.empty()) {
438 outElem->setData(allocateString(data));
439 }
440
441 auto** childPtr = &outElem->firstChild;
442 for (const auto& inChild : inElem.getChildren()) {
443 auto* outChild = clone(inChild);
444 *childPtr = outChild;
445 childPtr = &outChild->nextSibling;
446 }
447
448 return outElem;
449}
450
451void XMLDocument::serialize(XmlInputArchive& ar, unsigned /*version*/)
452{
453 const auto* current = ar.currentElement();
454 if (const auto* elem = current->getFirstChild()) {
455 assert(elem->nextSibling == nullptr); // at most 1 child
456 root = clone(*elem);
457 }
458}
459
460static void saveElement(XMLOutputStream<XmlOutputArchive>& stream, const XMLElement& elem)
461{
462 stream.begin(elem.getName());
463 for (const auto& attr : elem.getAttributes()) {
464 stream.attribute(attr.getName(), attr.getValue());
465 }
466 if (elem.hasChildren()) {
467 for (const auto& child : elem.getChildren()) {
468 saveElement(stream, child);
469 }
470 } else {
471 stream.data(elem.getData());
472 }
473 stream.end(elem.getName());
474}
475
476void XMLDocument::serialize(XmlOutputArchive& ar, unsigned /*version*/)
477{
478 auto& stream = ar.getXMLOutputStream();
479 if (root) {
480 saveElement(stream, *root);
481 }
482}
483
484XMLElement* XMLDocument::clone(const OldXMLElement& inElem)
485{
486 auto* outElem = allocateElement(allocateString(inElem.name));
487
488 auto** attrPtr = &outElem->firstAttribute;
489 for (const auto& [inName, inValue] : inElem.attributes) {
490 const char* n = allocateString(inName);
491 const char* v = allocateString(inValue);
492 auto* outAttr = allocateAttribute(n, v);
493 *attrPtr = outAttr;
494 attrPtr = &outAttr->nextAttribute;
495 }
496
497 if (!inElem.data.empty()) {
498 outElem->setData(allocateString(inElem.data));
499 }
500
501 auto** childPtr = &outElem->firstChild;
502 for (const auto& inChild : inElem.children) {
503 auto* outChild = clone(inChild);
504 *childPtr = outChild;
505 childPtr = &outChild->nextSibling;
506 }
507
508 return outElem;
509}
510
512{
513 root = clone(elem);
514}
515
516
517static std::unique_ptr<FileContext> lastSerializedFileContext;
519{
520 return std::move(lastSerializedFileContext);
521}
522// version 1: initial version
523// version 2: removed 'context' tag
524// also removed 'parent', but that was never serialized
525// 2b: (no need to increase version) name and data members are
526// serialized as normal members instead of constructor parameters
527// 2c: (no need to increase version) attributes were initially stored as
528// map<string, string>, later this was changed to
529// vector<pair<string, string>>. To keep bw-compat the serialize()
530// method converted between these two formats. Though (by luck) in
531// the XML output both data structures are serialized to the same
532// format, so we can drop this conversion step without breaking
533// bw-compat.
534template<typename Archive>
535void OldXMLElement::serialize(Archive& ar, unsigned version)
536{
537 assert(Archive::IS_LOADER);
538 ar.serialize("name", name,
539 "data", data,
540 "attributes", attributes,
541 "children", children);
542
543 if (ar.versionBelow(version, 2)) {
544 std::unique_ptr<FileContext> context;
545 ar.serialize("context", context);
546 if (context) {
547 lastSerializedFileContext = std::move(context);
548 }
549 }
550}
552
553} // namespace openmsx
TclObject t
'XMLOutputStream' is a helper to write an XML file in a streaming way.
void attribute(std::string_view name, std::string_view value)
void begin(std::string_view tag)
void end(std::string_view tag)
void data(std::string_view value)
void * allocate(size_t bytes, size_t alignment)
void read(std::span< uint8_t > buffer)
Read from file.
Definition File.cc:92
size_t getSize()
Returns the size of this file.
Definition File.cc:112
void resize(size_t size)
Grow or shrink the memory block.
Definition MemBuffer.hh:111
const T * data() const
Returns pointer to the start of the memory buffer.
Definition MemBuffer.hh:81
std::string_view loadStr()
Definition serialize.cc:200
void save(const T &t)
Definition serialize.hh:674
std::string_view getValue() const
Definition XMLElement.hh:55
void attribute(std::string_view name, std::string_view value)
void doctype(std::string_view txt)
void start(std::string_view name)
XMLDocumentHandler(XMLDocument &doc_)
std::string_view getSystemID() const
void text(std::string_view text)
XMLElement * setChildData(XMLElement &parent, const char *childName, const char *childData)
void serialize(MemInputArchive &ar, unsigned version)
XMLElement * getOrCreateChild(XMLElement &parent, const char *childName, const char *childData)
void setAttribute(XMLElement &elem, const char *attrName, const char *attrValue)
const char * allocateString(std::string_view str)
XMLElement * allocateElement(const char *name)
XMLAttribute * allocateAttribute(const char *name, const char *value)
void load(const std::string &filename, std::string_view systemID)
std::string_view getName() const
size_t numChildren() const
Definition XMLElement.cc:89
XMLAttribute ** findAttributePointer(std::string_view attrName)
int getChildDataAsInt(std::string_view childName, int defaultValue) const
Definition XMLElement.cc:81
AttributeRange getAttributes() const
const XMLAttribute * findAttribute(std::string_view attrName) const
Definition XMLElement.cc:95
const XMLElement * findChild(std::string_view childName) const
Definition XMLElement.cc:19
static void removeAttribute(XMLAttribute **attrPtr)
std::string_view getAttributeValue(std::string_view attrName) const
size_t numAttributes() const
bool hasChildren() const
int getAttributeValueAsInt(std::string_view attrName, int defaultValue) const
const XMLAttribute & getAttribute(std::string_view attrName) const
const XMLElement & getChild(std::string_view childName) const
Definition XMLElement.cc:53
std::string_view getChildData(std::string_view childName) const
Definition XMLElement.cc:62
std::string_view getData() const
bool getChildDataAsBool(std::string_view childName, bool defaultValue) const
Definition XMLElement.cc:75
bool getAttributeValueAsBool(std::string_view attrName, bool defaultValue) const
ChildRange getChildren() const
const XMLElement * getFirstChild() const
const XMLElement * currentElement() const
Definition serialize.hh:977
bool stringToBool(string_view str)
Definition StringOp.cc:12
This file implemented 3 utility functions:
Definition Autofire.cc:9
auto copy(InputRange &&range, OutputIter out)
Definition ranges.hh:250
constexpr size_t EXTRA_BUFFER_SPACE
Definition rapidsax.hh:42
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
void serialize(Archive &ar, unsigned version)
std::vector< OldXMLElement > children
std::vector< std::pair< std::string, std::string > > attributes
static std::unique_ptr< FileContext > getLastSerializedFileContext()
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
Definition xrange.hh:147
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)