GCC Code Coverage Report


Directory: gridformat/
File: gridformat/xml/element.hpp
Date: 2026-10-12 17:50:57
Exec Total Coverage
Lines: 147 153 96.1%
Functions: 51 143 35.7%
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 1030054 auto _child_find_lambda(std::string_view name) {
37 2669113 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 59564 virtual ~Content() {}
52
53 29782 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 59564 explicit ContentImpl(C&& c)
64 59564 : _c(std::forward<C>(c))
65 59564 {}
66
67 59562 void stream(std::ostream& os) const override {
68 59562 os << _c;
69 59562 }
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 269128 explicit XMLElement(std::string name)
84 269128 : XMLTag(std::move(name))
85 269128 {}
86
87 270210 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 256722 XMLElement& add_child(std::string name) {
106 256722 XMLElement child(std::move(name));
107 256722 child._parent = this;
108
1/2
✓ Branch 2 taken 256722 times.
✗ Branch 3 not taken.
256722 _children.emplace_back(std::move(child));
109 513444 return _children.back();
110 256722 }
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 433480 bool has_child(std::string_view child_name) const {
120 433480 return std::ranges::any_of(
121 433480 _children,
122 Detail::_child_find_lambda<XMLElement>(child_name)
123 433480 );
124 }
125
126 423418 XMLElement& get_child(std::string_view child_name) {
127 423418 auto it = std::ranges::find_if(
128
1/2
✓ Branch 1 taken 423418 times.
✗ Branch 2 not taken.
423418 _children,
129 Detail::_child_find_lambda<XMLElement>(child_name)
130 );
131
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 423418 times.
423418 if (it == _children.end())
132 ✗ throw ValueError("XMLElement has no child '" + std::string{child_name} + "'");
133 846836 return *it;
134 }
135
136 173155 const XMLElement& get_child(std::string_view child_name) const {
137 173155 auto it = std::ranges::find_if(
138
1/2
✓ Branch 1 taken 173155 times.
✗ Branch 2 not taken.
173155 _children,
139 Detail::_child_find_lambda<XMLElement>(child_name)
140 );
141
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 173155 times.
173155 if (it == _children.end())
142 ✗ throw ValueError("XMLElement has no child '" + std::string{child_name} + "'");
143 346310 return *it;
144 }
145
146 196874 std::size_t number_of_children() const {
147 196874 return _children.size();
148 }
149
150 template<Concepts::StreamableWith<std::ostream> C>
151 requires(!std::is_lvalue_reference_v<C>)
152 59564 void set_content(C&& content) {
153
1/2
✓ Branch 2 taken 29782 times.
✗ Branch 3 not taken.
59564 _content = std::make_unique<ContentImpl<C>>(std::forward<C>(content));
154 59564 }
155
156 29781 void stream_content(std::ostream& s) const {
157 29781 _content->stream(s);
158 29781 }
159
160 317014 bool has_content() const {
161 317014 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 105666 friend const ChildStorage& children(const XMLElement& e) {
176 105666 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 248434 OptionalReference<Element> access_at(std::string_view path, Element& element, char delimiter = '/') {
203
2/2
✓ Branch 2 taken 51149 times.
✓ Branch 3 taken 124922 times.
248434 if (path == "")
204 102298 return OptionalReference<Element>{element};
205 146136 Element* result = &element;
206
9/14
✓ Branch 1 taken 124922 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 124922 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 124922 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 200892 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 146791 times.
✓ Branch 14 taken 54101 times.
✓ Branch 16 taken 146791 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 200892 times.
✓ Branch 20 taken 70821 times.
918242 for (const auto& name : Path::elements_of(path, delimiter)) {
207
3/4
✓ Branch 2 taken 200892 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 54101 times.
✓ Branch 5 taken 146791 times.
246222 if (!result->has_child(name))
208 56348 return {};
209
1/2
✓ Branch 2 taken 146791 times.
✗ Branch 3 not taken.
189874 result = &result->get_child(name);
210 }
211 89788 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 122473 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 107256 times.
122473 if (path == "")
226 15217 return element;
227 107256 XMLElement* result = &element;
228
2/4
✓ Branch 1 taken 107256 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 107256 times.
✗ Branch 5 not taken.
107256 std::ranges::for_each(Path::elements_of(path, delimiter), [&] (const auto& name) {
229
3/4
✓ Branch 2 taken 174992 times.
✓ Branch 3 taken 29727 times.
✓ Branch 6 taken 174992 times.
✗ Branch 7 not taken.
204719 result = result->has_child(name) ? &result->get_child(name)
230
4/8
✓ Branch 1 taken 29727 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 29727 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 29727 times.
✓ Branch 7 taken 174992 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
204719 : &result->add_child(name);
231 204719 });
232 107256 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 756496 inline std::string escaped_attribute_value(std::string_view value) {
241 756496 std::string result;
242
1/2
✓ Branch 2 taken 756496 times.
✗ Branch 3 not taken.
756496 result.reserve(value.size());
243
2/2
✓ Branch 2 taken 6101850 times.
✓ Branch 3 taken 756496 times.
6858346 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 6101817 times.
6101850 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 6101817 times.
✗ Branch 2 not taken.
6101817 default: result += c;
250 }
251 }
252 756496 return result;
253 ✗ }
254
255 //! Replace the predefined xml entity references by the characters they represent
256 136755 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 136755 std::string result;
261
1/2
✓ Branch 2 taken 136755 times.
✗ Branch 3 not taken.
136755 result.reserve(value.size());
262
2/2
✓ Branch 1 taken 1199868 times.
✓ Branch 2 taken 136755 times.
1336623 while (!value.empty()) {
263
1/2
✓ Branch 1 taken 1199868 times.
✗ Branch 2 not taken.
1199868 const auto it = std::ranges::find_if(entities, [&] (const auto& entity) {
264 5999252 return value.starts_with(entity.first);
265 });
266
2/2
✓ Branch 1 taken 35 times.
✓ Branch 2 taken 1199833 times.
1199868 if (it != entities.end()) {
267
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 result += it->second;
268 35 value.remove_prefix(it->first.size());
269 } else {
270
1/2
✓ Branch 2 taken 1199833 times.
✗ Branch 3 not taken.
1199833 result += value.front();
271 1199833 value.remove_prefix(1);
272 }
273 }
274 136755 return result;
275 ✗ }
276
277 226285 inline void write_xml_tag_open(const XMLElement& e,
278 std::ostream& s,
279 std::string_view close_char) {
280 226285 s << "<" << e.name();
281
2/4
✓ Branch 1 taken 226285 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 226285 times.
✗ Branch 5 not taken.
226285 std::ranges::for_each(attributes(e),
282 756496 [&] (const std::string& attr_name) {
283 s << " " << attr_name
284 << "=\""
285
2/4
✓ Branch 1 taken 756496 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 756496 times.
✗ Branch 6 not taken.
1512992 << escaped_attribute_value(e.get_attribute(attr_name))
286
2/4
✓ Branch 5 taken 756496 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 756496 times.
✗ Branch 9 not taken.
1512992 << "\"";
287 756496 });
288 226285 s << close_char;
289 226285 }
290
291 90727 inline void write_xml_tag_open(const XMLElement& e,
292 std::ostream& s) {
293
1/2
✓ Branch 2 taken 90727 times.
✗ Branch 3 not taken.
90727 write_xml_tag_open(e, s, ">");
294 90727 }
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 90727 inline void write_xml_tag_close(const XMLElement& e,
302 std::ostream& s) {
303 90727 s << "</" << e.name() << ">";
304 90727 }
305
306 76472 inline void write_xml_element(const XMLElement& e,
307 std::ostream& s,
308 Indentation& ind) {
309
6/6
✓ Branch 1 taken 52852 times.
✓ Branch 2 taken 23620 times.
✓ Branch 4 taken 33238 times.
✓ Branch 5 taken 19614 times.
✓ Branch 6 taken 33238 times.
✓ Branch 7 taken 43234 times.
76472 if (!e.has_content() && e.number_of_children() == 0) {
310 33238 s << ind;
311 33238 write_empty_xml_tag(e, s);
312 } else {
313 43234 s << ind;
314 43234 write_xml_tag_open(e, s);
315 43234 s << "\n";
316
317
2/2
✓ Branch 1 taken 23620 times.
✓ Branch 2 taken 19614 times.
43234 if (e.has_content()) {
318 23620 e.stream_content(s);
319 23620 s << "\n";
320 }
321
322 43234 ++ind;
323
2/2
✓ Branch 6 taken 72622 times.
✓ Branch 7 taken 43234 times.
115856 for (const auto& c : children(e)) {
324
1/2
✓ Branch 1 taken 72622 times.
✗ Branch 2 not taken.
72622 write_xml_element(c, s, ind);
325
1/2
✓ Branch 1 taken 72622 times.
✗ Branch 2 not taken.
72622 s << "\n";
326 }
327 43234 --ind;
328
329 43234 s << ind;
330 43234 write_xml_tag_close(e, s);
331 }
332 76472 }
333
334 } // end namespace XML::Detail
335 #endif // DOXYGEN
336
337 3850 inline void write_xml(const XMLElement& e,
338 std::ostream& s,
339 Indentation ind = {}) {
340 3850 XML::Detail::write_xml_element(e, s, ind);
341 3850 }
342
343 3844 inline void write_xml_with_version_header(const XMLElement& e,
344 std::ostream& s,
345 Indentation ind = {}) {
346 3844 s << "<?xml version=\"1.0\"?>\n";
347
2/4
✓ Branch 1 taken 3844 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3844 times.
✗ Branch 5 not taken.
3844 write_xml(e, s, ind);
348 3844 }
349
350 } // end namespace GridFormat
351
352 #endif // GRIDFORMAT_XML_ELEMENT_HPP_
353