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{
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{
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 len = str.size();
314 auto* p = static_cast<char*>(allocator.allocate(len + 1, alignof(char)));
315 memcpy(p, str.data(), len);
316 p[len] = '\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(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) {
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*/)
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.begin(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 stream.end(elem.getName());
475}
476
477void XMLDocument::serialize(XmlOutputArchive& ar, unsigned /*version*/)
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
513{
514 root = clone(elem);
515}
516
517
518static std::unique_ptr<FileContext> lastSerializedFileContext;
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 datastructures 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 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)
Definition: one_of.hh:7
void read(void *buffer, size_t num)
Read from file.
Definition: File.cc:91
size_t getSize()
Returns the size of this file.
Definition: File.cc:111
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:662
std::string_view getValue() const
Definition: XMLElement.hh:55
void attribute(std::string_view name, std::string_view value)
Definition: XMLElement.cc:261
void doctype(std::string_view txt)
Definition: XMLElement.cc:270
void start(std::string_view name)
Definition: XMLElement.cc:237
XMLDocumentHandler(XMLDocument &doc_)
Definition: XMLElement.cc:231
std::string_view getSystemID() const
Definition: XMLElement.cc:235
void text(std::string_view text)
Definition: XMLElement.cc:257
XMLElement * setChildData(XMLElement &parent, const char *childName, const char *childData)
Definition: XMLElement.cc:189
void serialize(MemInputArchive &ar, unsigned version)
Definition: XMLElement.cc:389
XMLElement * getOrCreateChild(XMLElement &parent, const char *childName, const char *childData)
Definition: XMLElement.cc:170
void setAttribute(XMLElement &elem, const char *attrName, const char *attrValue)
Definition: XMLElement.cc:209
const char * allocateString(std::string_view str)
Definition: XMLElement.cc:311
XMLElement * allocateElement(const char *name)
Definition: XMLElement.cc:293
XMLAttribute * allocateAttribute(const char *name, const char *value)
Definition: XMLElement.cc:305
void load(const std::string &filename, std::string_view systemID)
Definition: XMLElement.cc:320
std::string_view getName() const
Definition: XMLElement.hh:170
size_t numChildren() const
Definition: XMLElement.cc:89
XMLAttribute ** findAttributePointer(std::string_view attrName)
Definition: XMLElement.cc:145
int getChildDataAsInt(std::string_view childName, int defaultValue) const
Definition: XMLElement.cc:81
AttributeRange getAttributes() const
Definition: XMLElement.hh:209
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)
Definition: XMLElement.cc:156
std::string_view getAttributeValue(std::string_view attrName) const
Definition: XMLElement.cc:114
size_t numAttributes() const
Definition: XMLElement.cc:162
bool hasChildren() const
Definition: XMLElement.hh:181
int getAttributeValueAsInt(std::string_view attrName, int defaultValue) const
Definition: XMLElement.cc:134
const XMLAttribute & getAttribute(std::string_view attrName) const
Definition: XMLElement.cc:106
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
Definition: XMLElement.hh:173
bool getChildDataAsBool(std::string_view childName, bool defaultValue) const
Definition: XMLElement.cc:75
bool getAttributeValueAsBool(std::string_view attrName, bool defaultValue) const
Definition: XMLElement.cc:127
ChildRange getChildren() const
Definition: XMLElement.hh:194
const XMLElement * getFirstChild() const
Definition: XMLElement.hh:182
const XMLElement * currentElement() const
Definition: serialize.hh:965
constexpr double e
Definition: Math.hh:18
bool stringToBool(string_view str)
Definition: StringOp.cc:12
std::optional< Context > context
Definition: GLContext.cc:9
This file implemented 3 utility functions:
Definition: Autofire.cc:9
constexpr const char *const filename
constexpr size_t EXTRA_BUFFER_SPACE
Definition: rapidsax.hh:41
size_t size(std::string_view utf8)
auto distance(octet_iterator first, octet_iterator last)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1009
void serialize(Archive &ar, unsigned version)
Definition: XMLElement.cc:536
std::vector< OldXMLElement > children
Definition: XMLElement.hh:329
std::vector< std::pair< std::string, std::string > > attributes
Definition: XMLElement.hh:330
static std::unique_ptr< FileContext > getLastSerializedFileContext()
Definition: XMLElement.cc:519
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
Definition: xrange.hh:148
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)