FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
libxml_wrapper.h
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2//
3// Copyright (c) 2024-present FERS Contributors (see AUTHORS.md).
4//
5// See the GNU GPLv2 LICENSE file in the FERS project root for more information.
6
7/**
8 * @file libxml_wrapper.h
9 * @brief Wrapper for managing XML documents and elements using libxml2.
10 *
11 * This header file provides classes and functions to simplify handling XML documents and elements
12 * using the libxml2 library. It includes basic functionality for manipulating XML nodes, attributes,
13 * content, and validation using DTD and XSD schemas.
14 */
15
16#pragma once
17
18#include <iostream>
19#include <libxml/parser.h>
20#include <limits>
21#include <memory>
22#include <optional>
23#include <span>
24#include <stdexcept>
25#include <string>
26#include <string_view>
27#include <vector>
28
29#include "core/logging.h"
30#include "libxml/globals.h"
31#include "libxml/xmlstring.h"
32
33/**
34 * @class XmlException
35 * @brief Exception class for handling XML-related errors.
36 */
37class XmlException final : public std::runtime_error
38{
39public:
40 /**
41 * @brief Constructor for XmlException.
42 *
43 * @param message The error message associated with the exception.
44 */
45 explicit XmlException(const std::string_view message) : std::runtime_error(std::string(message)) {}
46};
47
48namespace xml_detail
49{
51 {
52 std::vector<xmlChar> _value;
53
54 public:
55 explicit XmlCharBuffer(const std::string_view value)
56 {
57 _value.reserve(value.size() + 1);
58 for (const char character : value)
59 {
60 _value.push_back(static_cast<xmlChar>(static_cast<unsigned char>(character)));
61 }
62 _value.push_back(0);
63 }
64
65 [[nodiscard]] const xmlChar* c_str() const noexcept { return _value.data(); }
66
67 [[nodiscard]] int contentLength() const
68 {
69 const auto length = _value.empty() ? static_cast<std::size_t>(0) : _value.size() - 1;
70 if (length > static_cast<std::size_t>(std::numeric_limits<int>::max()))
71 {
72 throw XmlException("XML string exceeds libxml2 length limit.");
73 }
74 return static_cast<int>(length);
75 }
76 };
77
78 [[nodiscard]] inline std::string toString(const xmlChar* value)
79 {
80 if (value == nullptr)
81 {
82 return "";
83 }
84
85 std::string result;
86 for (const xmlChar* cursor = value; *cursor != 0; ++cursor)
87 {
88 result.push_back(static_cast<char>(*cursor));
89 }
90 return result;
91 }
92
93 [[nodiscard]] inline std::string toString(const xmlChar* value, const int length)
94 {
95 if (value == nullptr || length <= 0)
96 {
97 return "";
98 }
99
100 std::string result;
101 result.reserve(static_cast<std::size_t>(length));
102 for (int index = 0; index < length; ++index)
103 {
104 result.push_back(static_cast<char>(value[index]));
105 }
106 return result;
107 }
108
109 [[nodiscard]] inline bool equals(const xmlChar* xml_value, const std::string_view value)
110 {
111 if (xml_value == nullptr)
112 {
113 return false;
114 }
115
116 for (std::size_t index = 0; index < value.size(); ++index)
117 {
118 if (xml_value[index] == 0 || static_cast<char>(xml_value[index]) != value[index])
119 {
120 return false;
121 }
122 }
123 return xml_value[value.size()] == 0;
124 }
125
126 [[nodiscard]] inline xmlNodePtr createNode(const std::string_view name)
127 {
128 const XmlCharBuffer xml_name(name);
129 xmlNodePtr node = xmlNewNode(nullptr, xml_name.c_str());
130 if (node == nullptr)
131 {
132 throw XmlException("Failed to create XML node: " + std::string(name));
133 }
134 return node;
135 }
136
138 {
139 const XmlCharBuffer version("1.0");
140 return xmlNewDoc(version.c_str());
141 }
142}
143
144/**
145 * @class XmlElement
146 * @brief Class representing a node in an XML document.
147 *
148 * This class encapsulates an XML element, allowing users to access and manipulate
149 * element names, attributes, content, and children. It uses libxml2 for all operations
150 * and provides simplified methods to interact with XML nodes.
151 */
153{
154 xmlNodePtr _node; ///< Pointer to the XML node represented by this object.
155
156public:
157 /**
158 * @brief Constructor for XmlElement.
159 *
160 * @param node The xmlNode pointer representing the XML element.
161 */
162 explicit XmlElement(xmlNodePtr node) : _node(node) {}
163
164 XmlElement(const XmlElement&) = default;
165
167
169
171
173
174 /**
175 * @brief Get the name of the XML element.
176 *
177 * @return The name of the XML element.
178 */
179 [[nodiscard]] std::string name() const { return _node == nullptr ? "" : xml_detail::toString(_node->name); }
180
181 /**
182 * @brief Create a new XML element by name.
183 *
184 * @param name The name of the XML element.
185 * @return The newly created XML element.
186 */
187 [[nodiscard]] static XmlElement create(const std::string_view name)
188 {
190 }
191
192 /**
193 * @brief Get the text content of the XML element.
194 *
195 * @return The text content as a string.
196 */
197 [[nodiscard]] std::string getText() const
198 {
199 if (_node == nullptr)
200 {
201 return "";
202 }
204 std::string result = xml_detail::toString(text);
205 xmlFree(text);
206 return result;
207 }
208
209 /**
210 * @brief Set the text content of the XML element.
211 *
212 * @param text The text to set as the content of the node.
213 */
214 void setText(const std::string_view text) const
215 {
217 xmlNodeSetContentLen(_node, xml_text.c_str(), xml_text.contentLength());
218 }
219
220 /**
221 * @brief Get the value of an attribute safely.
222 *
223 * @param element The XmlElement to retrieve the attribute from.
224 * @param name The name of the attribute.
225 * @return The value of the attribute.
226 * @throws XmlException if the attribute is not found.
227 */
228 static std::string getSafeAttribute(const XmlElement& element, const std::string_view name)
229 {
230 std::string value;
232 if (xmlChar* attr = xmlGetProp(element.getNode(), xml_name.c_str()))
233 {
234 value = xml_detail::toString(attr);
235 xmlFree(attr);
236 }
237 else
238 {
239 throw XmlException("Attribute not found: " + std::string(name));
240 }
241 return value;
242 }
243
244 /**
245 * @brief Get the value of an optional attribute.
246 *
247 * @param element The XmlElement to retrieve the attribute from.
248 * @param name The name of the attribute.
249 * @return The attribute value if present.
250 */
251 static std::optional<std::string> getOptionalAttribute(const XmlElement& element, const std::string_view name)
252 {
253 if (!element.isValid())
254 {
255 return std::nullopt;
256 }
258 if (xmlChar* attr = xmlGetProp(element.getNode(), xml_name.c_str()))
259 {
260 std::string value = xml_detail::toString(attr);
261 xmlFree(attr);
262 return value;
263 }
264 return std::nullopt;
265 }
266
267 /**
268 * @brief Set an attribute on the XML element.
269 *
270 * @param name The name of the attribute.
271 * @param value The value to set for the attribute.
272 */
273 void setAttribute(const std::string_view name, const std::string_view value) const
274 {
277 xmlSetProp(_node, xml_name.c_str(), xml_value.c_str());
278 }
279
280 /**
281 * @brief Add a child element to the current node.
282 *
283 * @param name The name of the new child element.
284 * @return The newly created XmlElement.
285 */
286 [[nodiscard]] XmlElement addChild(const std::string_view name) const
287 {
289 xmlAddChild(_node, child);
290 return XmlElement(child);
291 }
292
293 /**
294 * @brief Retrieve a child element by name and index.
295 *
296 * @param name The name of the child element (optional).
297 * @param index The index of the child to retrieve.
298 * @return The child element or an invalid XmlElement if not found.
299 */
300 [[nodiscard]] XmlElement childElement(const std::string_view name = "", const unsigned index = 0) const noexcept
301 {
302 if (_node == nullptr)
303 {
304 return XmlElement(nullptr);
305 }
306 unsigned count = 0;
307 for (auto* child = _node->children; child != nullptr; child = child->next)
308 {
309 if (child->type == XML_ELEMENT_NODE && (name.empty() || xml_detail::equals(child->name, name)))
310 {
311 if (count == index)
312 {
313 return XmlElement(child);
314 }
315 ++count;
316 }
317 }
318 return XmlElement(nullptr);
319 }
320
321 /**
322 * @brief Check if the XML element is valid.
323 *
324 * @return True if the node is valid, otherwise false.
325 */
326 [[nodiscard]] bool isValid() const noexcept { return _node != nullptr; }
327
328 /**
329 * @brief Get the underlying XML node pointer.
330 *
331 * @return The underlying XML node pointer.
332 */
333 [[nodiscard]] xmlNodePtr getNode() const noexcept { return _node; }
334};
335
336/**
337 * @class XmlDocument
338 * @brief Class for managing XML documents.
339 */
341{
342 std::unique_ptr<xmlDoc, decltype(&xmlFreeDoc)> _doc; ///< Pointer to the XML document.
343
344public:
345 /**
346 * @brief Constructor for XmlDocument.
347 *
348 * @throws std::runtime_error if the document creation fails.
349 */
350 XmlDocument() : _doc(xml_detail::createDocument(), &xmlFreeDoc)
351 {
352 if (!_doc)
353 {
354 throw XmlException("Failed to create XML document.");
355 }
356 }
357
358 ~XmlDocument() = default;
359
360 XmlDocument(const XmlDocument&) = delete;
361
363
365
367
368 /**
369 * @brief Load an XML file into the document.
370 *
371 * @param filename The name of the file to load.
372 * @return True if the file was successfully loaded, otherwise false.
373 */
374 [[nodiscard]] bool loadFile(std::string_view filename);
375
376 /**
377 * @brief Load an XML document from a string in memory.
378 *
379 * @param content The string containing the XML document.
380 * @return True if the string was successfully parsed, otherwise false.
381 */
382 [[nodiscard]] bool loadString(const std::string& content);
383
384 /**
385 * @brief Save the document to a file.
386 *
387 * @param filename The name of the file to save to.
388 * @return True if the file was successfully saved, otherwise false.
389 */
391 {
392 if (!_doc)
393 {
394 LOG(logging::Level::ERROR, "Document is null; Cannot save to file");
395 return false;
396 }
397 return xmlSaveFormatFileEnc(filename.data(), _doc.get(), "UTF-8", 1) != -1;
398 }
399
400 /**
401 * @brief Dumps the document to a string.
402 *
403 * @return A string containing the XML document.
404 */
405 [[nodiscard]] std::string dumpToString() const;
406
407 /**
408 * @brief Set the root element of the document.
409 *
410 * @param root The root element to set.
411 * @throws std::runtime_error if the document is not created.
412 */
413 void setRootElement(const XmlElement& root) const
414 {
415 if (!_doc)
416 {
417 throw std::runtime_error("Document not created");
418 }
419 xmlDocSetRootElement(_doc.get(), root.getNode());
420 }
421
422 /**
423 * @brief Get the root element of the document.
424 *
425 * @return The root element.
426 * @throws std::runtime_error if the document is not loaded or the root element is missing.
427 */
429 {
430 if (!_doc)
431 {
432 throw std::runtime_error("Document not loaded");
433 }
435 if (root == nullptr)
436 {
437 throw std::runtime_error("Root element not found");
438 }
439 return XmlElement(root);
440 }
441
442 /**
443 * @brief Validate the document using a DTD.
444 *
445 * @param dtdData The DTD data used for validation.
446 * @return True if the document is valid according to the DTD.
447 * @throws XmlException if the DTD is invalid or the validation fails.
448 */
449 [[nodiscard]] bool validateWithDtd(std::span<const unsigned char> dtdData) const;
450
451 /**
452 * @brief Validate the document using an XSD schema.
453 *
454 * @param xsdData The XSD data used for validation.
455 * @return True if the document is valid according to the XSD schema.
456 * @throws XmlException if the XSD is invalid or the validation fails.
457 */
458 [[nodiscard]] bool validateWithXsd(std::span<const unsigned char> xsdData) const;
459};
460
461/**
462 * @brief Merge two XML documents.
463 *
464 * @param mainDoc The main XML document.
465 * @param includedDoc The XML document to include.
466 */
468
469/**
470 * @brief Remove "include" elements from the XML document.
471 *
472 * @param doc The XML document from which to remove the "include" elements.
473 */
Class for managing XML documents.
XmlElement getRootElement() const
Get the root element of the document.
XmlDocument(XmlDocument &&) noexcept=default
bool loadFile(std::string_view filename)
Load an XML file into the document.
XmlDocument(const XmlDocument &)=delete
XmlDocument()
Constructor for XmlDocument.
bool validateWithDtd(std::span< const unsigned char > dtdData) const
Validate the document using a DTD.
void setRootElement(const XmlElement &root) const
Set the root element of the document.
bool validateWithXsd(std::span< const unsigned char > xsdData) const
Validate the document using an XSD schema.
bool loadString(const std::string &content)
Load an XML document from a string in memory.
bool saveFile(const std::string_view filename) const
Save the document to a file.
~XmlDocument()=default
std::string dumpToString() const
Dumps the document to a string.
Class representing a node in an XML document.
XmlElement childElement(const std::string_view name="", const unsigned index=0) const noexcept
Retrieve a child element by name and index.
XmlElement(xmlNodePtr node)
Constructor for XmlElement.
static std::optional< std::string > getOptionalAttribute(const XmlElement &element, const std::string_view name)
Get the value of an optional attribute.
XmlElement addChild(const std::string_view name) const
Add a child element to the current node.
static std::string getSafeAttribute(const XmlElement &element, const std::string_view name)
Get the value of an attribute safely.
bool isValid() const noexcept
Check if the XML element is valid.
xmlNodePtr getNode() const noexcept
Get the underlying XML node pointer.
void setAttribute(const std::string_view name, const std::string_view value) const
Set an attribute on the XML element.
XmlElement(XmlElement &&) noexcept=default
static XmlElement create(const std::string_view name)
Create a new XML element by name.
std::string name() const
Get the name of the XML element.
std::string getText() const
Get the text content of the XML element.
void setText(const std::string_view text) const
Set the text content of the XML element.
XmlElement(const XmlElement &)=default
Exception class for handling XML-related errors.
XmlException(const std::string_view message)
Constructor for XmlException.
const xmlChar * c_str() const noexcept
XmlCharBuffer(const std::string_view value)
void mergeXmlDocuments(const XmlDocument &mainDoc, const XmlDocument &includedDoc)
Merge two XML documents.
void removeIncludeElements(const XmlDocument &doc)
Remove "include" elements from the XML document.
Header file for the logging system.
#define LOG(level,...)
Definition logging.h:19
@ ERROR
Error level for error events.
bool equals(const xmlChar *xml_value, const std::string_view value)
xmlNodePtr createNode(const std::string_view name)
xmlDocPtr createDocument()
std::string toString(const xmlChar *value)
math::Vec3 max