GCC Code Coverage Report


Directory: gridformat/
File: gridformat/xml/element.hpp
Date: 2026-10-18 00:09:19
Exec Total Coverage
Lines: 147 153 96.1%
Functions: 54 143 37.8%
Branches: 80 145 55.2%

Line Branch Exec Source
1 // SPDX-FileCopyrightText: 2022-2023 Dennis Gläser <dennis.glaeser@iws.uni-stuttgart.de>
2 // SPDX-License-Identifier: MIT
3 /*!
4 * \ingroup XML
5 * \copydoc GridFormat::XMLElement
6 */
7 #ifndef GRIDFORMAT_XML_ELEMENT_HPP_
8 #define GRIDFORMAT_XML_ELEMENT_HPP_
9
10 #include <ranges>
11 #include <utility>
12 #include <string>
13 #include <ostream>
14 #include <iterator>
15 #include <string_view>
16 #include <type_traits>
17 #include <memory>
18 #include <list>
19 #include <any>
20 #include <array>
21 #include <algorithm>
22
23 #include <gridformat/common/path.hpp>
24 #include <gridformat/common/concepts.hpp>
25 #include <gridformat/common/indentation.hpp>
26 #include <gridformat/common/exceptions.hpp>
27 #include <gridformat/common/optional_reference.hpp>
28 #include <gridformat/xml/tag.hpp>
29
30 namespace GridFormat {
31
32 #ifndef DOXYGEN
33 namespace Detail {
34
35 template<typename Child>
36 1030436 auto _child_find_lambda(std::string_view name) {
37 2669931 return [name] (const Child& child) { return child.name() == name; };
38 }
39
40 } // end namespace Detail
41 #endif // DOXYGEN
42
43 /*!
44 * \ingroup XML
45 * \brief Class to represent an XML element, i.e. an XML tag with a data body.
46 */
47 class XMLElement : public XMLTag {
48 using ChildStorage = std::list<XMLElement>;
49
50 struct Content {
51 59588 virtual ~Content() {}
52
53 29794 Content() = default;
54 Content(const Content&) = delete;
55 Content& operator=(const Content&) = delete;
56
57 virtual void stream(std::ostream& os) const = 0;
58 virtual std::any get_as_any() const = 0;
59 };
60
61 template<Concepts::StreamableWith<std::ostream> C>
62 struct ContentImpl : public Content {
63 59588 explicit ContentImpl(C&& c)
64 59588 : _c(std::forward<C>(c))
65 59588 {}
66
67 59586 void stream(std::ostream& os) const override {
68 59586 os << _c;
69 59586 }
70
71 4 std::any get_as_any() const override {
72 if constexpr (std::is_constructible_v<std::any, C>)
73 4 return {_c};
74 else
75 throw InvalidState("Cannot parse content");
76 }
77
78 private:
79 std::remove_cvref_t<C> _c;
80 };
81
82 public:
83 269274 explicit XMLElement(std::string name)
84 269274 : XMLTag(std::move(name))
85 269274 {}
86
87 270364 XMLElement(XMLElement&&) = default;
88 XMLElement(const XMLElement&) = delete;
89 XMLElement() = delete;
90
91 1 XMLElement& parent() {
92 1 _check_parent();
93 1 return *_parent;
94 }
95
96 const XMLElement& parent() const {
97 _check_parent();
98 return *_parent;
99 }
100
101 bool has_parent() const {
102 return _parent;
103 }
104
105 256851 XMLElement& add_child(std::string name) {
106 256851 XMLElement child(std::move(name));
107 256851 child._parent = this;
108
1/2
✓ Branch 2 taken 256851 times.
✗ Branch 3 not taken.
256851 _children.emplace_back(std::move(child));
109 513702 return _children.back();
110 256851 }
111
112 1 bool remove_child(std::string_view child_name) {
113 1 return std::erase_if(
114 1 _children,
115 Detail::_child_find_lambda<XMLElement>(child_name)
116 1 );
117 }
118
119 433638 bool has_child(std::string_view child_name) const {
120 433638 return std::ranges::any_of(
121 433638 _children,
122 Detail::_child_find_lambda<XMLElement>(child_name)
123 433638 );
124 }
125
126 423490 XMLElement& get_child(std::string_view child_name) {
127 423490 auto it = std::ranges::find_if(
128
1/2
✓ Branch 1 taken 423490 times.
✗ Branch 2 not taken.
423490 _children,
129 Detail::_child_find_lambda<XMLElement>(child_name)
130 );
131
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 423490 times.
423490 if (it == _children.end())
132 ✗ throw ValueError("XMLElement has no child '" + std::string{child_name} + "'");
133 846980 return *it;
134 }
135
136 173307 const XMLElement& get_child(std::string_view child_name) const {
137 173307 auto it = std::ranges::find_if(
138
1/2
✓ Branch 1 taken 173307 times.
✗ Branch 2 not taken.
173307 _children,
139 Detail::_child_find_lambda<XMLElement>(child_name)
140 );
141
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 173307 times.
173307 if (it == _children.end())
142 ✗ throw ValueError("XMLElement has no child '" + std::string{child_name} + "'");
143 346614 return *it;
144 }
145
146 196909 std::size_t number_of_children() const {
147 196909 return _children.size();
148 }
149
150 template<Concepts::StreamableWith<std::ostream> C>
151 requires(!std::is_lvalue_reference_v<C>)
152 59588 void set_content(C&& content) {
153
1/2
✓ Branch 2 taken 29794 times.
✗ Branch 3 not taken.
59588 _content = std::make_unique<ContentImpl<C>>(std::forward<C>(content));
154 59588 }
155
156 29793 void stream_content(std::ostream& s) const {
157 29793 _content->stream(s);
158 29793 }
159
160 317096 bool has_content() const {
161 317096 return static_cast<bool>(_content);
162 }
163
164 template<typename T>
165 4 T get_content() const {
166
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
4 if (!has_content())
167 ✗ throw ValueError("XMLElement has no content");
168
3/4
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
4 return std::any_cast<T>(_content->get_as_any());
169 }
170
171 4 friend ChildStorage& children(XMLElement& e) {
172 4 return e._children;
173 }
174
175 105716 friend const ChildStorage& children(const XMLElement& e) {
176 105716 return e._children;
177 }
178
179 private:
180 1 void _check_parent() const {
181
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!_parent)
182 ✗ throw InvalidState("This xml element has no parent");
183 1 }
184
185 XMLElement* _parent{nullptr};
186 ChildStorage _children;
187 std::unique_ptr<Content> _content{nullptr};
188 };
189
190
191 /*!
192 * \ingroup XML
193 * \brief Return a reference to the element resulting from successively
194 * accessing the child elements as given by the provided path.
195 * \param path The relative path starting from the given element
196 * \param element The element at which to start traversing
197 * \param delimiter The delimiter used to separate path entries.
198 * \note If an element has multiple children with the same (matching) name,
199 * the first one will be selected.
200 */
201 template<typename Element> requires(std::same_as<XMLElement, std::remove_cvref_t<Element>>)
202 248574 OptionalReference<Element> access_at(std::string_view path, Element& element, char delimiter = '/') {
203
2/2
✓ Branch 2 taken 51183 times.
✓ Branch 3 taken 124958 times.
248574 if (path == "")
204 102366 return OptionalReference<Element>{element};
205 146208 Element* result = &element;
206
9/14
✓ Branch 1 taken 124958 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 124958 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 124958 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 200960 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 146855 times.
✓ Branch 14 taken 54105 times.
✓ Branch 16 taken 146855 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 200960 times.
✓ Branch 20 taken 70853 times.
918778 for (const auto& name : Path::elements_of(path, delimiter)) {
207
3/4
✓ Branch 2 taken 200960 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 54105 times.
✓ Branch 5 taken 146855 times.
246358 if (!result->has_child(name))
208 56356 return {};
209
1/2
✓ Branch 2 taken 146855 times.
✗ Branch 3 not taken.
190002 result = &result->get_child(name);
210 }
211 89852 return OptionalReference<Element>{*result};
212 }
213
214 /*!
215 * \ingroup XML
216 * \brief Return a reference to the element resulting from successively
217 * accessing or creating the child elements as given by the provided path.
218 * \param path The relative path starting from the given element
219 * \param element The element at which to start traversing
220 * \param delimiter The delimiter used to separate path entries.
221 * \note If an element has multiple children with the same (matching) name,
222 * the first one will be selected.
223 */
224 122501 inline XMLElement& access_or_create_at(std::string_view path, XMLElement& element, char delimiter = '/') {
225
2/2
✓ Branch 2 taken 15217 times.
✓ Branch 3 taken 107284 times.
122501 if (path == "")
226 15217 return element;
227 107284 XMLElement* result = &element;
228
2/4
✓ Branch 1 taken 107284 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 107284 times.
✗ Branch 5 not taken.
107284 std::ranges::for_each(Path::elements_of(path, delimiter), [&] (const auto& name) {
229
3/4
✓ Branch 2 taken 175024 times.
✓ Branch 3 taken 29743 times.
✓ Branch 6 taken 175024 times.
✗ Branch 7 not taken.
204767 result = result->has_child(name) ? &result->get_child(name)
230
4/8
✓ Branch 1 taken 29743 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 29743 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 29743 times.
✓ Branch 7 taken 175024 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
204767 : &result->add_child(name);
231 204767 });
232 107284 return *result;
233 }
234
235
236 #ifndef DOXYGEN
237 namespace XML::Detail {
238
239 //! Replace characters that are not allowed in (double-quoted) attribute values by entity references
240 756609 inline std::string escaped_attribute_value(std::string_view value) {
241 756609 std::string result;
242
1/2
✓ Branch 2 taken 756609 times.
✗ Branch 3 not taken.
756609 result.reserve(value.size());
243
2/2
✓ Branch 2 taken 6102544 times.
✓ Branch 3 taken 756609 times.
6859153 for (const char c : value) {
244
5/5
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 6102511 times.
6102544 switch (c) {
245
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 case '&': result += "&amp;"; break;
246
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 case '<': result += "&lt;"; break;
247
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 case '>': result += "&gt;"; break;
248
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 case '"': result += "&quot;"; break;
249
1/2
✓ Branch 1 taken 6102511 times.
✗ Branch 2 not taken.
6102511 default: result += c;
250 }
251 }
252 756609 return result;
253 ✗ }
254
255 //! Replace the predefined xml entity references by the characters they represent
256 136975 inline std::string unescaped(std::string_view value) {
257 static constexpr std::array<std::pair<std::string_view, char>, 5> entities{{
258 {"&amp;", '&'}, {"&lt;", '<'}, {"&gt;", '>'}, {"&quot;", '"'}, {"&apos;", '\''}
259 }};
260 136975 std::string result;
261
1/2
✓ Branch 2 taken 136975 times.
✗ Branch 3 not taken.
136975 result.reserve(value.size());
262
2/2
✓ Branch 1 taken 1201386 times.
✓ Branch 2 taken 136975 times.
1338361 while (!value.empty()) {
263
1/2
✓ Branch 1 taken 1201386 times.
✗ Branch 2 not taken.
1201386 const auto it = std::ranges::find_if(entities, [&] (const auto& entity) {
264 6006842 return value.starts_with(entity.first);
265 });
266
2/2
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 1201350 times.
1201386 if (it != entities.end()) {
267
1/2
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
36 result += it->second;
268 36 value.remove_prefix(it->first.size());
269 } else {
270
1/2
✓ Branch 2 taken 1201350 times.
✗ Branch 3 not taken.
1201350 result += value.front();
271 1201350 value.remove_prefix(1);
272 }
273 }
274 136975 return result;
275 ✗ }
276
277 226331 inline void write_xml_tag_open(const XMLElement& e,
278 std::ostream& s,
279 std::string_view close_char) {
280 226331 s << "<" << e.name();
281
2/4
✓ Branch 1 taken 226331 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 226331 times.
✗ Branch 5 not taken.
226331 std::ranges::for_each(attributes(e),
282 756609 [&] (const std::string& attr_name) {
283 s << " " << attr_name
284 << "=\""
285
2/4
✓ Branch 1 taken 756609 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 756609 times.
✗ Branch 6 not taken.
1513218 << escaped_attribute_value(e.get_attribute(attr_name))
286
2/4
✓ Branch 5 taken 756609 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 756609 times.
✗ Branch 9 not taken.
1513218 << "\"";
287 756609 });
288 226331 s << close_char;
289 226331 }
290
291 90763 inline void write_xml_tag_open(const XMLElement& e,
292 std::ostream& s) {
293
1/2
✓ Branch 2 taken 90763 times.
✗ Branch 3 not taken.
90763 write_xml_tag_open(e, s, ">");
294 90763 }
295
296 33238 inline void write_empty_xml_tag(const XMLElement& e,
297 std::ostream& s) {
298
1/2
✓ Branch 2 taken 33238 times.
✗ Branch 3 not taken.
33238 write_xml_tag_open(e, s, "/>");
299 33238 }
300
301 90763 inline void write_xml_tag_close(const XMLElement& e,
302 std::ostream& s) {
303 90763 s << "</" << e.name() << ">";
304 90763 }
305
306 76494 inline void write_xml_element(const XMLElement& e,
307 std::ostream& s,
308 Indentation& ind) {
309
6/6
✓ Branch 1 taken 52864 times.
✓ Branch 2 taken 23630 times.
✓ Branch 4 taken 33238 times.
✓ Branch 5 taken 19626 times.
✓ Branch 6 taken 33238 times.
✓ Branch 7 taken 43256 times.
76494 if (!e.has_content() && e.number_of_children() == 0) {
310 33238 s << ind;
311 33238 write_empty_xml_tag(e, s);
312 } else {
313 43256 s << ind;
314 43256 write_xml_tag_open(e, s);
315 43256 s << "\n";
316
317
2/2
✓ Branch 1 taken 23630 times.
✓ Branch 2 taken 19626 times.
43256 if (e.has_content()) {
318 23630 e.stream_content(s);
319 23630 s << "\n";
320 }
321
322 43256 ++ind;
323
2/2
✓ Branch 6 taken 72642 times.
✓ Branch 7 taken 43256 times.
115898 for (const auto& c : children(e)) {
324
1/2
✓ Branch 1 taken 72642 times.
✗ Branch 2 not taken.
72642 write_xml_element(c, s, ind);
325
1/2
✓ Branch 1 taken 72642 times.
✗ Branch 2 not taken.
72642 s << "\n";
326 }
327 43256 --ind;
328
329 43256 s << ind;
330 43256 write_xml_tag_close(e, s);
331 }
332 76494 }
333
334 } // end namespace XML::Detail
335 #endif // DOXYGEN
336
337 3852 inline void write_xml(const XMLElement& e,
338 std::ostream& s,
339 Indentation ind = {}) {
340 3852 XML::Detail::write_xml_element(e, s, ind);
341 3852 }
342
343 3846 inline void write_xml_with_version_header(const XMLElement& e,
344 std::ostream& s,
345 Indentation ind = {}) {
346 3846 s << "<?xml version=\"1.0\"?>\n";
347
2/4
✓ Branch 1 taken 3846 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3846 times.
✗ Branch 5 not taken.
3846 write_xml(e, s, ind);
348 3846 }
349
350 } // end namespace GridFormat
351
352 #endif // GRIDFORMAT_XML_ELEMENT_HPP_
353