openMSX
XMLElement.hh
Go to the documentation of this file.
1#ifndef XMLELEMENT_HH
2#define XMLELEMENT_HH
3
4#include "MemBuffer.hh"
6#include "serialize_meta.hh"
7
8#include <cassert>
9#include <concepts>
10#include <cstddef>
11#include <iterator>
12//#include <memory_resource>
13#include <string>
14#include <string_view>
15#include <vector>
16
17namespace openmsx {
18
19struct OldXMLElement; // for backwards compatible savestates
20
21// The classes XMLDocument, XMLElement and XMLAttribute together form an
22// in-memory representation of XML files (or at least the subset of the features
23// needed for openMSX).
24//
25// This representation is optimized for fast parsing and to be compact in
26// memory. This is achieved by:
27// - Keeping a buffer to the actual xml file, and modifying that buffer.
28// For example the strings are modified in place (resolve escape sequences
29// and add zero-terminators) and the actual XML classes refer to this buffer.
30// - The 'XMLElement' and 'XMLAttribute' objects are allocated from a monotonic
31// allocator.
32// - Both the file buffer and the monotonic allocator are owned by the
33// XMLDocument class.
34
35// Modifying the information (e.g. after it has been parsed from a file) is
36// possible. But this is not the main intended use-case. For example it is
37// possible to assign a new value-string to an attribute or an xml element. That
38// string will be allocated from the monotonic allocator, but the memory for the
39// old string will not be freed. So it's probably not a good idea to use these
40// classes in a heavy-modification scenario.
41
42
43// XMLAttribute is a name-value pair. This class only refers to the name and
44// value strings, it does not own these strings. The owner could be either
45// XMLDocument (in the file buffer or in the monotonic allocator) or it could
46// be a string with static lifetime (e.g. a string-literal).
47//
48// XMLAttributes are organized in a (single-)linked list. So this class also
49// contains a pointer to the next element in the list. The last element in the
50// list contains a nullptr.
52{
53public:
54 XMLAttribute(const char* name_, const char* value_)
55 : name(name_), value(value_) {}
56 [[nodiscard]] std::string_view getName() const { return name; }
57 [[nodiscard]] std::string_view getValue() const { return value; }
58 void setValue(const char* value_) { value = value_; }
59
61 assert(!nextAttribute);
62 nextAttribute = attribute;
63 return attribute;
64 }
65
66private:
67 const char* name;
68 const char* value;
69 XMLAttribute* nextAttribute = nullptr;
70
71 friend class XMLElement;
72 friend class XMLDocument;
73 friend class XMLDocumentHandler;
74};
75
76// This XMLElement class represents a single XML-element (or XML-node). It has a
77// name, attributes, a value (could be empty) and zero or more children. The
78// value and the children are mutually exclusive, in other words: elements with
79// children cannot have a non-empty value.
80//
81// String-ownership is the same as with XMLAttribute.
82//
83// Attributes are organized in a (single-)linked list. This class points to the
84// first attribute (possibly nullptr when there are no attributes) and that
85// attribute points to the next and so on.
86//
87// Hierarchy is achieved via two pointers. This class has a pointer to its first
88// child and to its next sibling. Thus getting all children of a specific
89// elements requires to first follow the 'firstChild' pointer, and from there on
90// follow the 'nextSibling' pointers (and stop when any of these pointers in
91// nullptr). XMLElement objects do not have a pointer to their parent.
93{
94 // iterator classes for children and attributes
95 // TODO c++20: use iterator + sentinel instead of 2 x iterator
96 struct ChildIterator {
97 using difference_type = ptrdiff_t;
98 using value_type = const XMLElement;
99 using pointer = value_type*;
100 using reference = value_type&;
101 using iterator_category = std::forward_iterator_tag;
102
103 const XMLElement* elem;
104
105 const XMLElement& operator*() const { return *elem; }
106 ChildIterator& operator++() { elem = elem->nextSibling; return *this; }
107 ChildIterator operator++(int) { auto result = *this; elem = elem->nextSibling; return result; }
108 [[nodiscard]] bool operator==(const ChildIterator& i) const = default;
109 };
110 static_assert(std::forward_iterator<ChildIterator>);
111 struct ChildRange {
112 const XMLElement* elem;
113 [[nodiscard]] ChildIterator begin() const { return {elem->firstChild}; }
114 [[nodiscard]] ChildIterator end() const { return {nullptr}; }
115 };
116 static_assert(std::ranges::range<ChildRange>);
117
118 struct NamedChildIterator {
119 using difference_type = ptrdiff_t;
120 using value_type = const XMLElement*;
121 using pointer = value_type*;
122 using reference = value_type&;
123 using iterator_category = std::forward_iterator_tag;
124
125 const XMLElement* elem;
126 std::string_view name;
127
128 NamedChildIterator() : elem(nullptr) {}
129 NamedChildIterator(const XMLElement* elem_, std::string_view name_)
130 : elem(elem_), name(name_)
131 {
132 while (elem && elem->getName() != name) {
133 elem = elem->nextSibling;
134 }
135 }
136
137 const XMLElement* operator*() const { return elem; }
138 NamedChildIterator& operator++() {
139 do {
140 elem = elem->nextSibling;
141 } while (elem && elem->getName() != name);
142 return *this;
143 }
144 NamedChildIterator operator++(int) { auto result = *this; ++(*this); return result; }
145 [[nodiscard]] bool operator==(const NamedChildIterator& i) const { return elem == i.elem; }
146 };
147 static_assert(std::forward_iterator<NamedChildIterator>);
148 struct NamedChildRange {
149 const XMLElement* elem;
150 std::string_view name;
151 [[nodiscard]] NamedChildIterator begin() const { return {elem->firstChild, name}; }
152 [[nodiscard]] NamedChildIterator end() const { return {nullptr, std::string_view{}}; }
153 };
154 static_assert(std::ranges::range<NamedChildRange>);
155
156 struct AttributeIterator {
157 using difference_type = ptrdiff_t;
158 using value_type = const XMLAttribute;
159 using pointer = value_type*;
160 using reference = value_type&;
161 using iterator_category = std::forward_iterator_tag;
162
163 const XMLAttribute* attr;
164
165 const XMLAttribute& operator*() const { return *attr; }
166 AttributeIterator& operator++() { attr = attr->nextAttribute; return *this; }
167 AttributeIterator operator++(int) { auto result = *this; attr = attr->nextAttribute; return result; }
168 [[nodiscard]] bool operator==(const AttributeIterator& i) const = default;
169 };
170 static_assert(std::forward_iterator<AttributeIterator>);
171 struct AttributeRange {
172 const XMLElement* elem;
173 [[nodiscard]] AttributeIterator begin() const { return {elem->firstAttribute}; }
174 [[nodiscard]] AttributeIterator end() const { return {nullptr}; }
175 };
176 static_assert(std::ranges::range<AttributeRange>);
177
178public:
179 explicit XMLElement(const char* name_) : name(name_) {}
180 XMLElement(const char* name_, const char* data_) : name(name_), data(data_) {}
181
182 [[nodiscard]] std::string_view getName() const { return name; }
183 void clearName() { name = ""; } // hack to 'remove' child from findChild()
184
185 [[nodiscard]] std::string_view getData() const {
186 return data ? std::string_view(data) : std::string_view();
187 }
188 XMLElement* setData(const char* data_) {
189 data = data_;
190 return this;
191 }
192
193 [[nodiscard]] bool hasChildren() const { return firstChild; }
194 [[nodiscard]] const XMLElement* getFirstChild() const { return firstChild; }
195 [[nodiscard]] const XMLElement* findChild(std::string_view childName) const;
196 [[nodiscard]] const XMLElement* findChild(std::string_view childName, const XMLElement*& hint) const;
197 [[nodiscard]] const XMLElement& getChild(std::string_view childName) const;
198
199 [[nodiscard]] std::string_view getChildData(std::string_view childName) const;
200 [[nodiscard]] std::string_view getChildData(std::string_view childName,
201 std::string_view defaultValue) const;
202 [[nodiscard]] bool getChildDataAsBool(std::string_view childName, bool defaultValue) const;
203 [[nodiscard]] int getChildDataAsInt(std::string_view childName, int defaultValue) const;
204
205 [[nodiscard]] size_t numChildren() const;
206 [[nodiscard]] ChildRange getChildren() const { return {this}; }
207 [[nodiscard]] NamedChildRange getChildren(std::string_view childName) const { return {this, childName}; }
208
209 [[nodiscard]] const XMLAttribute* findAttribute(std::string_view attrName) const;
210 [[nodiscard]] const XMLAttribute& getAttribute(std::string_view attrName) const;
211 [[nodiscard]] std::string_view getAttributeValue(std::string_view attrName) const;
212 [[nodiscard]] std::string_view getAttributeValue(std::string_view attrName,
213 std::string_view defaultValue) const;
214 [[nodiscard]] bool getAttributeValueAsBool(std::string_view attrName,
215 bool defaultValue) const;
216 [[nodiscard]] int getAttributeValueAsInt(std::string_view attrName,
217 int defaultValue) const;
218 [[nodiscard]] XMLAttribute** findAttributePointer(std::string_view attrName);
219 static void removeAttribute(XMLAttribute** attrPtr);
220 [[nodiscard]] size_t numAttributes() const;
221 [[nodiscard]] AttributeRange getAttributes() const { return {this}; }
222
224 assert(!firstChild);
225 firstChild = child;
226 return child;
227 }
229 assert(!nextSibling);
230 nextSibling = sibling;
231 return sibling;
232 }
234 assert(!firstAttribute);
235 firstAttribute = attribute;
236 return attribute;
237 }
238
239private:
240 const char* name;
241 const char* data = nullptr;
242 XMLElement* firstChild = nullptr;
243 XMLElement* nextSibling = nullptr;
244 XMLAttribute* firstAttribute = nullptr;
245
246 friend class XMLDocument;
247 friend class XMLDocumentHandler;
248};
249
250// This class mainly exists to manage ownership over the objects involved in a
251// full XML document. These are the XMLElement and XMLAttribute objects but also
252// the name/value/attribute strings. This class also has a pointer to the root
253// element.
254//
255// Because of the way how ownership is handled, most modifying operations are
256// part of this class API. For example there's a method 'setChildData()' which
257// searches for a child with a specific name, if not found such a child is
258// created, then the child-data is set to a new value. Because this operation
259// possibly requires to allocate a new XMLElement (and the allocator is part of
260// this class) this method is part of this class rather than the XMLElement
261// class.
263{
264public:
265 // singleton-like document, mainly used as owner for static XML snippets
266 // (so with lifetime the whole openMSX session)
268 static XMLDocument doc;
269 return doc;
270 }
271
272 XMLDocument(const XMLDocument&) = delete;
276
277 // Create an empty XMLDocument (root == nullptr).
278 XMLDocument() = default;
279
280 // Create an empty XMLDocument (root == nullptr). All constructor
281 // arguments are delegated to the monotonic allocator constructor.
282 template<typename T, typename ...Args>
283 requires(!std::same_as<XMLDocument, std::remove_cvref_t<T>>) // don't block copy-constructor
284 XMLDocument(T&& t, Args&& ...args)
285 : allocator(std::forward<T>(t), std::forward<Args>(args)...) {}
286
287 // Load/parse an xml file. Requires that the document is still empty.
288 void load(const std::string& filename, std::string_view systemID);
289
290 [[nodiscard]] const XMLElement* getRoot() const { return root; }
291 void setRoot(XMLElement* root_) { assert(!root); root = root_; }
292
293 [[nodiscard]] XMLElement* allocateElement(const char* name);
294 [[nodiscard]] XMLElement* allocateElement(const char* name, const char* data);
295 [[nodiscard]] XMLAttribute* allocateAttribute(const char* name, const char* value);
296 [[nodiscard]] const char* allocateString(std::string_view str);
297
298 [[nodiscard]] XMLElement* getOrCreateChild(XMLElement& parent, const char* childName, const char* childData);
299 XMLElement* setChildData(XMLElement& parent, const char* childName, const char* childData);
300 void setAttribute(XMLElement& elem, const char* attrName, const char* attrValue);
301
302 template<typename Range, typename UnaryOp>
303 void generateList(XMLElement& parent, const char* itemName, Range&& range, UnaryOp op) {
304 XMLElement** next = &parent.firstChild;
305 assert(!*next);
306 for (auto& r : range) {
307 auto* elem = allocateElement(itemName);
308 op(elem, r);
309 *next = elem;
310 next = &elem->nextSibling;
311 }
312 }
313
314 void load(const OldXMLElement& elem); // bw compat
315
316 void serialize(MemInputArchive& ar, unsigned version);
317 void serialize(MemOutputArchive& ar, unsigned version) const;
318 void serialize(XmlInputArchive& ar, unsigned version);
319 void serialize(XmlOutputArchive& ar, unsigned version) const;
320
321private:
322 XMLElement* loadElement(MemInputArchive& ar);
323 XMLElement* clone(const XMLElement& inElem);
324 XMLElement* clone(const OldXMLElement& elem);
325
326private:
327 XMLElement* root = nullptr;
328 MemBuffer<char> buf;
329 // part of c++17, but not yet implemented in libc++
330 // std::pmr::monotonic_buffer_resource allocator;
331 monotonic_allocator allocator;
332
333 friend class XMLDocumentHandler;
334};
335
336
337// For backwards-compatibility with old savestates
338// needed with HardwareConfig-version <= 5.
339class FileContext;
341{
342 template<typename Archive>
343 void serialize(Archive& ar, unsigned version);
344
345 // For backwards compatibility with version=1 savestates
346 static std::unique_ptr<FileContext> getLastSerializedFileContext();
347
348 std::string name;
349 std::string data;
350 std::vector<OldXMLElement> children;
351 std::vector<std::pair<std::string, std::string>> attributes;
352};
354
355} // namespace openmsx
356
357#endif
TclObject t
This class manages the lifetime of a block of memory.
Definition MemBuffer.hh:32
std::string_view getName() const
Definition XMLElement.hh:56
XMLAttribute * setNextAttribute(XMLAttribute *attribute)
Definition XMLElement.hh:60
void setValue(const char *value_)
Definition XMLElement.hh:58
XMLAttribute(const char *name_, const char *value_)
Definition XMLElement.hh:54
std::string_view getValue() const
Definition XMLElement.hh:57
XMLElement * setChildData(XMLElement &parent, const char *childName, const char *childData)
void serialize(MemInputArchive &ar, unsigned version)
XMLDocument(T &&t, Args &&...args)
XMLDocument & operator=(XMLDocument &&)=delete
XMLElement * getOrCreateChild(XMLElement &parent, const char *childName, const char *childData)
XMLDocument(const XMLDocument &)=delete
void generateList(XMLElement &parent, const char *itemName, Range &&range, UnaryOp op)
XMLDocument(XMLDocument &&)=delete
void setRoot(XMLElement *root_)
static XMLDocument & getStaticDocument()
void setAttribute(XMLElement &elem, const char *attrName, const char *attrValue)
const char * allocateString(std::string_view str)
XMLDocument & operator=(const XMLDocument &)=delete
const XMLElement * getRoot() const
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
NamedChildRange getChildren(std::string_view childName) const
XMLElement * setData(const char *data_)
XMLAttribute * setFirstAttribute(XMLAttribute *attribute)
XMLAttribute ** findAttributePointer(std::string_view attrName)
XMLElement * setNextSibling(XMLElement *sibling)
XMLElement(const char *name_)
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
XMLElement * setFirstChild(XMLElement *child)
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
XMLElement(const char *name_, const char *data_)
This file implemented 3 utility functions:
Definition Autofire.cc:11
bool operator==(const BooleanInput &x, const BooleanInput &y)
#define SERIALIZE_CLASS_VERSION(CLASS, VERSION)
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 auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)