19#include "libxml/encoding.h"
20#include "libxml/parser.h"
21#include "libxml/valid.h"
22#include "libxml/xmlIO.h"
23#include "libxml/xmlerror.h"
24#include "libxml/xmlschemas.h"
29 void libxmlGenericErrorCallback(
void* ctx,
const char* msg, ...)
31 auto* err_str =
static_cast<std::string*
>(ctx);
32 if ((err_str ==
nullptr) || (msg ==
nullptr))
40 vsnprintf(buf,
sizeof(buf), msg, args);
47 std::string formatError(
const std::string_view title,
const std::string& rawErrors,
const std::string_view hint)
49 std::string errors = rawErrors;
52 while (!errors.empty() && (std::isspace(
static_cast<unsigned char>(errors.back())) != 0))
59 while ((pos = errors.find(
'\n', pos)) != std::string::npos)
61 errors.replace(pos, 1,
"\n│ ");
65 return std::format(
"\n"
72 "└────────────────────────────────────────────────────────────────────────────",
73 title, errors.empty() ?
"Unknown validation error." : errors, hint);
77 std::string getXmlLastErrorFormatted()
79 const xmlError* err = xmlGetLastError();
80 if ((err !=
nullptr) && (err->message !=
nullptr))
82 std::string msg = err->message;
83 while (!msg.empty() && (std::isspace(
static_cast<unsigned char>(msg.back())) != 0))
89 return std::format(
"Line {}: {}", err->line, msg);
93 return "Syntax error or malformed XML.";
100 xmlIOParseDTD(
nullptr,
101 xmlParserInputBufferCreateMem(
reinterpret_cast<const char*
>(dtdData.data()),
102 static_cast<int>(dtdData.size()), XML_CHAR_ENCODING_UTF8),
103 XML_CHAR_ENCODING_UTF8);
109 const std::unique_ptr<xmlValidCtxt,
decltype(&xmlFreeValidCtxt)> validation_ctxt(xmlNewValidCtxt(),
111 if (!validation_ctxt)
114 throw XmlException(
"Failed to create validation context.");
118 std::string dtdErrors;
119 validation_ctxt->userData = &dtdErrors;
120 validation_ctxt->error = libxmlGenericErrorCallback;
121 validation_ctxt->warning = libxmlGenericErrorCallback;
123 const bool is_valid = xmlValidateDtd(validation_ctxt.get(), _doc.get(), dtd) != 0;
128 std::string fancyError = formatError(
"XML DTD Validation Failed", dtdErrors,
129 "Check your scenario XML tags and attributes against 'fers-xml.dtd'.");
139 const std::unique_ptr<xmlSchemaParserCtxt,
decltype(&xmlSchemaFreeParserCtxt)> schema_parser_ctxt(
140 xmlSchemaNewMemParserCtxt(
reinterpret_cast<const char*
>(xsdData.data()),
static_cast<int>(xsdData.size())),
141 xmlSchemaFreeParserCtxt);
142 if (!schema_parser_ctxt)
144 throw XmlException(
"Failed to create schema parser context.");
148 std::string xsdParseErrors;
149 xmlSchemaSetParserErrors(schema_parser_ctxt.get(), libxmlGenericErrorCallback, libxmlGenericErrorCallback,
152 const std::unique_ptr<xmlSchema,
decltype(&xmlSchemaFree)> schema(xmlSchemaParse(schema_parser_ctxt.get()),
156 std::string fancyError =
157 formatError(
"XSD Schema Parse Failed", xsdParseErrors,
"The internal XSD schema is invalid.");
159 throw XmlException(
"Failed to parse schema from memory.");
162 const std::unique_ptr<xmlSchemaValidCtxt,
decltype(&xmlSchemaFreeValidCtxt)> schema_valid_ctxt(
163 xmlSchemaNewValidCtxt(schema.get()), xmlSchemaFreeValidCtxt);
164 if (!schema_valid_ctxt)
166 throw XmlException(
"Failed to create schema validation context.");
170 std::string xsdErrors;
171 xmlSchemaSetValidErrors(schema_valid_ctxt.get(), libxmlGenericErrorCallback, libxmlGenericErrorCallback,
174 if (
const bool is_valid = xmlSchemaValidateDoc(schema_valid_ctxt.get(), _doc.get()) == 0; !is_valid)
176 std::string fancyError = formatError(
"XML XSD Validation Failed", xsdErrors,
177 "Check your scenario XML tags and attributes against 'fers-xml.xsd'.");
190 for (xmlNodePtr child = included_root.
getNode()->children; child !=
nullptr; child = child->next)
192 if (child->type == XML_ELEMENT_NODE)
194 xmlNodePtr new_node = xmlCopyNode(child, 1);
195 xmlAddChild(main_root.
getNode(), new_node);
208 xmlUnlinkNode(include_element.getNode());
209 xmlFreeNode(include_element.getNode());
222 _doc.reset(xmlReadFile(filename.data(),
nullptr, XML_PARSE_NOERROR | XML_PARSE_NOWARNING));
225 std::string fancyError =
226 formatError(
"XML Parsing Failed", getXmlLastErrorFormatted(),
"Ensure the XML file is well-formed.");
236 _doc.reset(xmlReadMemory(content.c_str(),
static_cast<int>(content.length()),
"in_memory.xml",
nullptr,
237 XML_PARSE_NOERROR | XML_PARSE_NOWARNING));
240 std::string fancyError =
241 formatError(
"XML Parsing Failed", getXmlLastErrorFormatted(),
"Ensure the XML string is well-formed.");
255 xmlChar* buffer =
nullptr;
257 xmlDocDumpFormatMemory(_doc.get(), &buffer, &size, 1);
258 if (buffer ==
nullptr)
263 const std::string result(
reinterpret_cast<const char*
>(buffer),
static_cast<size_t>(size));
Class for managing XML documents.
XmlElement getRootElement() const
Get the root element of the document.
bool loadFile(std::string_view filename)
Load an XML file into the document.
bool validateWithDtd(std::span< const unsigned char > dtdData) const
Validate the document using a DTD.
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.
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.
xmlNodePtr getNode() const noexcept
Get the underlying XML node pointer.
Exception class for handling XML-related errors.
void mergeXmlDocuments(const XmlDocument &mainDoc, const XmlDocument &includedDoc)
Merge two XML documents.
void removeIncludeElements(const XmlDocument &doc)
Remove "include" elements from the XML document.
Wrapper for managing XML documents and elements using libxml2.
@ FATAL
Fatal level for severe error events.
@ ERROR
Error level for error events.