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