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 
16 namespace openmsx {
17 
18 // Returns nullptr when not found.
19 const 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).
35 const 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.
53 const XMLElement& XMLElement::getChild(std::string_view childName) const
54 {
55  if (auto* elem = findChild(childName)) {
56  return *elem;
57  }
58  throw ConfigException("Missing tag \"", childName, "\".");
59 }
60 
61 // Throws when not found.
62 std::string_view XMLElement::getChildData(std::string_view childName) const
63 {
64  return getChild(childName).getData();
65 }
66 
67 // Returns default value when not found.
68 std::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 
75 bool 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 
81 int 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.
95 const 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.
106 const 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.
114 std::string_view XMLElement::getAttributeValue(std::string_view attrName) const
115 {
116  return getAttribute(attrName).getValue();
117 }
118 
119 std::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.
127 bool 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 
134 int 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().
145 XMLAttribute** XMLElement::findAttributePointer(std::string_view attrName)
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.
170 XMLElement* 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).
189 XMLElement* 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.
209 void 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 {
230 public:
232  : doc(doc_)
233  , nextElement(&doc.root) {}
234 
235  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 
283 private:
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 
299 XMLElement* 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 
305 XMLAttribute* 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 
311 const 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 
320 void 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) {
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 
355 XMLElement* 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 
389 void XMLDocument::serialize(MemInputArchive& ar, unsigned /*version*/)
390 {
391  root = loadElement(ar);
392 }
393 
394 static 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 
415 void 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 
425 XMLElement* 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 
452 void 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 
461 static 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 
477 void XMLDocument::serialize(XmlOutputArchive& ar, unsigned /*version*/)
478 {
479  auto& stream = ar.getXMLOutputStream();
480  if (root) {
481  saveElement(stream, *root);
482  }
483 }
484 
485 XMLElement* 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 
518 static std::unique_ptr<FileContext> lastSerializedFileContext;
519 std::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 datastructures are serialized to the same
533 // format, so we can drop this conversion step without breaking
534 // bw-compat.
535 template<typename Archive>
536 void 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
const std::string & getMessage() const &
Definition: MSXException.hh:23
const T * data() const
Returns pointer to the start of the memory buffer.
Definition: MemBuffer.hh:81
void resize(size_t size)
Grow or shrink the memory block.
Definition: MemBuffer.hh:111
std::string_view loadStr()
Definition: serialize.cc:201
void save(const T &t)
Definition: serialize.hh:659
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:173
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:212
const XMLAttribute * findAttribute(std::string_view attrName) const
Definition: XMLElement.cc:95
const XMLElement * findChild(std::string_view childName) const
Definition: XMLElement.cc:19
const XMLElement * getFirstChild() const
Definition: XMLElement.hh:185
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:184
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:176
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:197
const XMLElement * currentElement() const
Definition: serialize.hh:962
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:998
void serialize(Archive &ar, unsigned version)
Definition: XMLElement.cc:536
std::vector< OldXMLElement > children
Definition: XMLElement.hh:332
std::vector< std::pair< std::string, std::string > > attributes
Definition: XMLElement.hh:333
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:170
constexpr auto begin(const zstring_view &x)
Definition: zstring_view.hh:83
constexpr auto end(const zstring_view &x)
Definition: zstring_view.hh:84