GCC Code Coverage Report


Directory: gridformat/
File: gridformat/xml/element.hpp
Date: 2024-11-10 16:24:00
Exec Total Coverage
Lines: 124 128 96.9%
Functions: 48 140 34.3%
Branches: 60 114 52.6%

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
21 #include <gridformat/common/path.hpp>
22 #include <gridformat/common/concepts.hpp>
23 #include <gridformat/common/indentation.hpp>
24 #include <gridformat/common/exceptions.hpp>
25 #include <gridformat/common/optional_reference.hpp>
26 #include <gridformat/xml/tag.hpp>
27
28 namespace GridFormat {
29
30 #ifndef DOXYGEN
31 namespace Detail {
32
33 template<typename Child>
34 1016796 auto _child_find_lambda(std::string_view name) {
35 2639489 return [name] (const Child& child) { return child.name() == name; };
36 }
37
38 } // end namespace Detail
39 #endif // DOXYGEN
40
41 /*!
42 * \ingroup XML
43 * \brief Class to represent an XML element, i.e. an XML tag with a data body.
44 */
45 class XMLElement : public XMLTag {
46 using ChildStorage = std::list<XMLElement>;
47
48 struct Content {
49 56050 virtual ~Content() {}
50
51 28025 Content() = default;
52 Content(const Content&) = delete;
53 Content& operator=(const Content&) = delete;
54
55 virtual void stream(std::ostream& os) const = 0;
56 virtual std::any get_as_any() const = 0;
57 };
58
59 template<Concepts::StreamableWith<std::ostream> C>
60 struct ContentImpl : public Content {
61 56050 explicit ContentImpl(C&& c)
62 56050 : _c(std::forward<C>(c))
63 56050 {}
64
65 56048 void stream(std::ostream& os) const override {
66 56048 os << _c;
67 56048 }
68
69 4 std::any get_as_any() const override {
70 if constexpr (std::is_constructible_v<std::any, C>)
71 4 return {_c};
72 else
73 throw InvalidState("Cannot parse content");
74 }
75
76 private:
77 std::remove_cvref_t<C> _c;
78 };
79
80 public:
81 260110 explicit XMLElement(std::string name)
82 260110 : XMLTag(std::move(name))
83 260110 {}
84
85 261182 XMLElement(XMLElement&&) = default;
86 XMLElement(const XMLElement&) = delete;
87 XMLElement() = delete;
88
89 1 XMLElement& parent() {
90 1 _check_parent();
91 1 return *_parent;
92 }
93
94 const XMLElement& parent() const {
95 _check_parent();
96 return *_parent;
97 }
98
99 bool has_parent() const {
100 return _parent;
101 }
102
103 247833 XMLElement& add_child(std::string name) {
104 247833 XMLElement child(std::move(name));
105 247833 child._parent = this;
106
1/2
✓ Branch 2 taken 247833 times.
✗ Branch 3 not taken.
247833 _children.emplace_back(std::move(child));
107 495666 return _children.back();
108 247833 }
109
110 1 bool remove_child(std::string_view child_name) {
111 1 return std::erase_if(
112 1 _children,
113 Detail::_child_find_lambda<XMLElement>(child_name)
114 1 );
115 }
116
117 426777 bool has_child(std::string_view child_name) const {
118 426777 return std::ranges::any_of(
119 426777 _children,
120 Detail::_child_find_lambda<XMLElement>(child_name)
121 426777 );
122 }
123
124 427658 XMLElement& get_child(std::string_view child_name) {
125 427658 auto it = std::ranges::find_if(
126
1/2
✓ Branch 1 taken 427658 times.
✗ Branch 2 not taken.
427658 _children,
127 Detail::_child_find_lambda<XMLElement>(child_name)
128 );
129
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 427658 times.
427658 if (it == _children.end())
130 throw ValueError("XMLElement has no child '" + std::string{child_name} + "'");
131 855316 return *it;
132 }
133
134 162360 const XMLElement& get_child(std::string_view child_name) const {
135 162360 auto it = std::ranges::find_if(
136
1/2
✓ Branch 1 taken 162360 times.
✗ Branch 2 not taken.
162360 _children,
137 Detail::_child_find_lambda<XMLElement>(child_name)
138 );
139
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 162360 times.
162360 if (it == _children.end())
140 throw ValueError("XMLElement has no child '" + std::string{child_name} + "'");
141 324720 return *it;
142 }
143
144 189906 std::size_t number_of_children() const {
145 189906 return _children.size();
146 }
147
148 template<Concepts::StreamableWith<std::ostream> C>
149 requires(!std::is_lvalue_reference_v<C>)
150 56050 void set_content(C&& content) {
151
1/2
✓ Branch 2 taken 28025 times.
✗ Branch 3 not taken.
56050 _content = std::make_unique<ContentImpl<C>>(std::forward<C>(content));
152 56050 }
153
154 28024 void stream_content(std::ostream& s) const {
155 28024 _content->stream(s);
156 28024 }
157
158 306031 bool has_content() const {
159 306031 return static_cast<bool>(_content);
160 }
161
162 template<typename T>
163 4 T get_content() const {
164
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
4 if (!has_content())
165 throw ValueError("XMLElement has no content");
166
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());
167 }
168
169 4 friend ChildStorage& children(XMLElement& e) {
170 4 return e._children;
171 }
172
173 102953 friend const ChildStorage& children(const XMLElement& e) {
174 102953 return e._children;
175 }
176
177 private:
178 1 void _check_parent() const {
179
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!_parent)
180 throw InvalidState("This xml element has no parent");
181 1 }
182
183 XMLElement* _parent{nullptr};
184 ChildStorage _children;
185 std::unique_ptr<Content> _content{nullptr};
186 };
187
188
189 /*!
190 * \ingroup XML
191 * \brief Return a reference to the element resulting from successively
192 * accessing the child elements as given by the provided path.
193 * \param path The relative path starting from the given element
194 * \param element The element at which to start traversing
195 * \param delimiter The delimiter used to separate path entries.
196 * \note If an element has multiple children with the same (matching) name,
197 * the first one will be selected.
198 */
199 template<typename Element> requires(std::same_as<XMLElement, std::remove_cvref_t<Element>>)
200 237946 OptionalReference<Element> access_at(std::string_view path, Element& element, char delimiter = '/') {
201
2/2
✓ Branch 2 taken 47023 times.
✓ Branch 3 taken 123360 times.
237946 if (path == "")
202 94046 return OptionalReference<Element>{element};
203 143900 Element* result = &element;
204
11/16
✓ Branch 1 taken 123360 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 123360 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 123360 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 198176 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 144570 times.
✓ Branch 14 taken 53606 times.
✓ Branch 16 taken 144570 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 198876 times.
✓ Branch 20 taken 69054 times.
✓ Branch 21 taken 2100 times.
✓ Branch 22 taken 700 times.
902562 for (const auto& name : Path::elements_of(path, delimiter)) {
205
3/4
✓ Branch 2 taken 198176 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 53606 times.
✓ Branch 5 taken 144570 times.
242122 if (!result->has_child(name))
206 55802 return {};
207
1/2
✓ Branch 2 taken 144570 times.
✗ Branch 3 not taken.
186320 result = &result->get_child(name);
208 }
209 88098 return OptionalReference<Element>{*result};
210 }
211
212 /*!
213 * \ingroup XML
214 * \brief Return a reference to the element resulting from successively
215 * accessing or creating the child elements as given by the provided path.
216 * \param path The relative path starting from the given element
217 * \param element The element at which to start traversing
218 * \param delimiter The delimiter used to separate path entries.
219 * \note If an element has multiple children with the same (matching) name,
220 * the first one will be selected.
221 */
222 120821 XMLElement& access_or_create_at(std::string_view path, XMLElement& element, char delimiter = '/') {
223
2/2
✓ Branch 2 taken 14777 times.
✓ Branch 3 taken 106044 times.
120821 if (path == "")
224 14777 return element;
225 106044 XMLElement* result = &element;
226
2/4
✓ Branch 1 taken 106044 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 106044 times.
✗ Branch 5 not taken.
106044 std::ranges::for_each(Path::elements_of(path, delimiter), [&] (const auto& name) {
227
3/4
✓ Branch 2 taken 172965 times.
✓ Branch 3 taken 29456 times.
✓ Branch 6 taken 172965 times.
✗ Branch 7 not taken.
202421 result = result->has_child(name) ? &result->get_child(name)
228
4/8
✓ Branch 1 taken 29456 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 29456 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 29456 times.
✓ Branch 7 taken 172965 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
202421 : &result->add_child(name);
229 202421 });
230 106044 return *result;
231 }
232
233
234 #ifndef DOXYGEN
235 namespace XML::Detail {
236
237 217563 void write_xml_tag_open(const XMLElement& e,
238 std::ostream& s,
239 std::string_view close_char) {
240 217563 s << "<" << e.name();
241
2/4
✓ Branch 1 taken 217563 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 217563 times.
✗ Branch 5 not taken.
217563 std::ranges::for_each(attributes(e),
242 720787 [&] (const std::string& attr_name) {
243 s << " " << attr_name
244 << "=\""
245
1/2
✓ Branch 1 taken 720787 times.
✗ Branch 2 not taken.
1441574 << e.get_attribute(attr_name)
246
2/4
✓ Branch 5 taken 720787 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 720787 times.
✗ Branch 9 not taken.
1441574 << "\"";
247 720787 });
248 217563 s << close_char;
249 217563 }
250
251 88466 void write_xml_tag_open(const XMLElement& e,
252 std::ostream& s) {
253
1/2
✓ Branch 2 taken 88466 times.
✗ Branch 3 not taken.
88466 write_xml_tag_open(e, s, ">");
254 88466 }
255
256 33410 void write_empty_xml_tag(const XMLElement& e,
257 std::ostream& s) {
258
1/2
✓ Branch 2 taken 33410 times.
✗ Branch 3 not taken.
33410 write_xml_tag_open(e, s, "/>");
259 33410 }
260
261 88466 void write_xml_tag_close(const XMLElement& e,
262 std::ostream& s) {
263 88466 s << "</" << e.name() << ">";
264 88466 }
265
266 74872 void write_xml_element(const XMLElement& e,
267 std::ostream& s,
268 Indentation& ind) {
269
6/6
✓ Branch 1 taken 52924 times.
✓ Branch 2 taken 21948 times.
✓ Branch 4 taken 33410 times.
✓ Branch 5 taken 19514 times.
✓ Branch 6 taken 33410 times.
✓ Branch 7 taken 41462 times.
74872 if (!e.has_content() && e.number_of_children() == 0) {
270 33410 s << ind;
271 33410 write_empty_xml_tag(e, s);
272 } else {
273 41462 s << ind;
274 41462 write_xml_tag_open(e, s);
275 41462 s << "\n";
276
277
2/2
✓ Branch 1 taken 21948 times.
✓ Branch 2 taken 19514 times.
41462 if (e.has_content()) {
278 21948 e.stream_content(s);
279 21948 s << "\n";
280 }
281
282 41462 ++ind;
283
2/2
✓ Branch 6 taken 71042 times.
✓ Branch 7 taken 41462 times.
112504 for (const auto& c : children(e)) {
284
1/2
✓ Branch 1 taken 71042 times.
✗ Branch 2 not taken.
71042 write_xml_element(c, s, ind);
285
1/2
✓ Branch 1 taken 71042 times.
✗ Branch 2 not taken.
71042 s << "\n";
286 }
287 41462 --ind;
288
289 41462 s << ind;
290 41462 write_xml_tag_close(e, s);
291 }
292 74872 }
293
294 } // end namespace XML::Detail
295 #endif // DOXYGEN
296
297 3830 inline void write_xml(const XMLElement& e,
298 std::ostream& s,
299 Indentation ind = {}) {
300 3830 XML::Detail::write_xml_element(e, s, ind);
301 3830 }
302
303 3826 inline void write_xml_with_version_header(const XMLElement& e,
304 std::ostream& s,
305 Indentation ind = {}) {
306 3826 s << "<?xml version=\"1.0\"?>\n";
307
2/4
✓ Branch 1 taken 3826 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3826 times.
✗ Branch 5 not taken.
3826 write_xml(e, s, ind);
308 3826 }
309
310 } // end namespace GridFormat
311
312 #endif // GRIDFORMAT_XML_ELEMENT_HPP_
313