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