commit 0b85cf9c2797fccc595f06e93519ed5e37915fb9
parent c08b87a67553129b078ccd3122183ad20bb32f0f
Author: cfillion <cfillion@users.noreply.github.com>
Date: Thu, 2 Jan 2020 12:55:37 -0500
migrate from WDL's old copy of tinyxml to libxml2
Diffstat:
13 files changed, 501 insertions(+), 157 deletions(-)
diff --git a/.appveyor.yml b/.appveyor.yml
@@ -25,7 +25,7 @@ for:
install-libs() {
local arch="$1"; shift
local packages=(
- libboost-dev libcurl4-openssl-dev libsqlite3-dev libssl-dev zlib1g-dev
+ libboost-dev libcurl4-openssl-dev libsqlite3-dev libssl-dev libxml2-dev zlib1g-dev
)
sudo dpkg --add-architecture $arch && sudo apt-get update -qq &&
diff --git a/README.md b/README.md
@@ -33,6 +33,7 @@ them separately):
- [Boost](https://www.boost.org/) (1.56 or later)
- [Catch2](https://github.com/catchorg/Catch2)
- [libcurl](https://curl.haxx.se/libcurl/)
+- [libxml2](http://www.xmlsoft.org/)
- [OpenSSL](https://www.openssl.org/) or compatible
- [SQLite](https://www.sqlite.org/)
- [zlib](https://www.zlib.net/)
diff --git a/cmake/FindTinyXML.cmake b/cmake/FindTinyXML.cmake
@@ -1,25 +0,0 @@
-if(TinyXML_FOUND)
- return()
-endif()
-
-find_package(WDL REQUIRED)
-
-find_path(TinyXML_INCLUDE_DIR
- NAMES tinyxml.h
- PATHS ${WDL_DIR}
- PATH_SUFFIXES tinyxml
- NO_DEFAULT_PATH
-)
-mark_as_advanced(TinyXML_INCLUDE_DIR)
-
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(TinyXML REQUIRED_VARS TinyXML_INCLUDE_DIR)
-
-add_library(tinyxml
- ${TinyXML_INCLUDE_DIR}/tinyxml.cpp ${TinyXML_INCLUDE_DIR}/tinystr.cpp
- ${TinyXML_INCLUDE_DIR}/tinyxmlparser.cpp ${TinyXML_INCLUDE_DIR}/tinyxmlerror.cpp
-)
-
-target_include_directories(tinyxml INTERFACE ${TinyXML_INCLUDE_DIR})
-
-add_library(TinyXML::TinyXML ALIAS tinyxml)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
@@ -1,8 +1,8 @@
find_package(Boost 1.56 REQUIRED)
find_package(CURL REQUIRED)
+find_package(LibXML2 REQUIRED)
find_package(MiniZip REQUIRED)
find_package(Threads REQUIRED)
-find_package(TinyXML REQUIRED)
find_package(WDL REQUIRED)
if(NOT APPLE AND NOT WIN32)
@@ -14,6 +14,15 @@ if(NOT WIN32)
endif()
if(VCPKG_TOOLCHAIN)
+ # vcpkg does not setup a usable target for libxml2
+ set_target_properties(LibXml2::LibXml2 PROPERTIES
+ IMPORTED_LOCATION "${LIBXML2_LIBRARY}"
+ MAP_IMPORTED_CONFIG_RELWITHDEBINFO ""
+ MAP_IMPORTED_CONFIG_MINSIZEREL ""
+ INTERFACE_INCLUDE_DIRECTORIES "${LIBXML2_INCLUDE_DIR}"
+ )
+ target_link_libraries(LibXml2::LibXml2 INTERFACE ${LIBXML2_LIBRARIES})
+
# required for selecting the right debug or release version
find_package(SQLite3 CONFIG REQUIRED)
add_library(SQLite::SQLite3 INTERFACE IMPORTED)
@@ -76,6 +85,7 @@ add_library(reapack OBJECT
transaction.cpp
version.cpp
win32.cpp
+ xml.cpp
)
target_compile_features(reapack PUBLIC cxx_std_17)
@@ -96,8 +106,8 @@ else()
endif()
target_link_libraries(reapack
- ${CMAKE_DL_LIBS} Boost::headers CURL::libcurl MiniZip::MiniZip
- SQLite::SQLite3 Threads::Threads TinyXML::TinyXML WDL::WDL
+ ${CMAKE_DL_LIBS} Boost::headers CURL::libcurl LibXml2::LibXml2
+ MiniZip::MiniZip SQLite::SQLite3 Threads::Threads WDL::WDL
)
if(OPENSSL_FOUND)
diff --git a/src/index.cpp b/src/index.cpp
@@ -21,8 +21,10 @@
#include "filesystem.hpp"
#include "path.hpp"
#include "remote.hpp"
+#include "xml.hpp"
-#include <tinyxml.h>
+#include <cstring>
+#include <fstream>
Path Index::pathFor(const std::string &name)
{
@@ -31,31 +33,28 @@ Path Index::pathFor(const std::string &name)
IndexPtr Index::load(const std::string &name, const char *data)
{
- TiXmlDocument doc;
+ std::unique_ptr<std::istream> stream;
if(data)
- doc.Parse(data);
+ stream = std::make_unique<std::istringstream>(data);
else {
- FILE *file = FS::open(pathFor(name));
-
- if(!file)
+ stream = std::make_unique<std::ifstream>();
+ if(!FS::open(*dynamic_cast<std::ifstream *>(stream.get()), pathFor(name)))
throw reapack_error(FS::lastError());
-
- doc.LoadFile(file);
- fclose(file);
}
- if(doc.ErrorId())
- throw reapack_error(doc.ErrorDesc());
+ XmlDocument doc(*stream);
+
+ if(!doc)
+ throw reapack_error(doc.error());
- TiXmlHandle docHandle(&doc);
- TiXmlElement *root = doc.RootElement();
+ const XmlNode &root = doc.root();
- if(!root || strcmp(root->Value(), "index"))
+ if(!root || strcmp(root.name(), "index"))
throw reapack_error("invalid index");
int version = 0;
- root->Attribute("version", &version);
+ root.attribute("version", &version);
if(!version)
throw reapack_error("index version not found");
diff --git a/src/index.hpp b/src/index.hpp
@@ -31,7 +31,7 @@
class Index;
class Path;
class Remote;
-class TiXmlElement;
+class XmlNode;
struct NetworkOpts;
typedef std::shared_ptr<const Index> IndexPtr;
@@ -59,7 +59,7 @@ public:
const std::vector<const Package *> &packages() const { return m_packages; }
private:
- static void loadV1(TiXmlElement *, Index *);
+ static void loadV1(XmlNode, Index *);
std::string m_name;
Metadata m_metadata;
diff --git a/src/index_v1.cpp b/src/index_v1.cpp
@@ -18,181 +18,140 @@
#include "index.hpp"
#include "errors.hpp"
+#include "xml.hpp"
#include <sstream>
-#include <tinyxml.h>
-static void LoadMetadataV1(TiXmlElement *, Metadata *);
-static void LoadCategoryV1(TiXmlElement *, Index *);
-static void LoadPackageV1(TiXmlElement *, Category *);
-static void LoadVersionV1(TiXmlElement *, Package *);
-static void LoadSourceV1(TiXmlElement *, Version *);
+static void LoadMetadataV1(XmlNode , Metadata *);
+static void LoadCategoryV1(XmlNode , Index *);
+static void LoadPackageV1(XmlNode , Category *);
+static void LoadVersionV1(XmlNode , Package *);
+static void LoadSourceV1(XmlNode , Version *);
-void Index::loadV1(TiXmlElement *root, Index *ri)
+void Index::loadV1(XmlNode root, Index *ri)
{
if(ri->name().empty()) {
- if(const char *name = root->Attribute("name"))
- ri->setName(name);
+ if(const XmlString &name = root.attribute("name"))
+ ri->setName(*name);
}
- TiXmlElement *node = root->FirstChildElement("category");
-
- while(node) {
+ for(XmlNode node = root.firstChild("category");
+ node; node = node.nextSibling("category"))
LoadCategoryV1(node, ri);
- node = node->NextSiblingElement("category");
- }
-
- node = root->FirstChildElement("metadata");
-
- if(node)
+ if(XmlNode node = root.firstChild("metadata"))
LoadMetadataV1(node, ri->metadata());
}
-void LoadMetadataV1(TiXmlElement *meta, Metadata *md)
+void LoadMetadataV1(XmlNode meta, Metadata *md)
{
- TiXmlElement *node = meta->FirstChildElement("description");
+ XmlNode node = meta.firstChild("description");
if(node) {
- if(const char *rtf = node->GetText())
- md->setAbout(rtf);
+ if(const XmlString &rtf = node.text())
+ md->setAbout(*rtf);
}
- node = meta->FirstChildElement("link");
-
- while(node) {
- const char *rel = node->Attribute("rel");
- const char *url = node->Attribute("href");
- const char *name = node->GetText();
+ node = meta.firstChild("link");
- if(!rel) rel = "";
- if(!name) {
- if(!url) url = "";
- name = url;
- }
- else if(!url) url = name;
+ for(node = meta.firstChild("link"); node; node = node.nextSibling("link")) {
+ const XmlString &rel = node.attribute("rel"),
+ &url = node.attribute("href"),
+ &name = node.text();
- md->addLink(Metadata::getLinkType(rel), {name, url});
+ std::string effectiveName{name ? *name : url.value_or("")};
- node = node->NextSiblingElement("link");
+ md->addLink(Metadata::getLinkType(rel.value_or("")),
+ {effectiveName, url.value_or(effectiveName.c_str())});
}
}
-void LoadCategoryV1(TiXmlElement *catNode, Index *ri)
+void LoadCategoryV1(XmlNode catNode, Index *ri)
{
- const char *name = catNode->Attribute("name");
- if(!name) name = "";
+ const XmlString &name = catNode.attribute("name");
- Category *cat = new Category(name, ri);
+ Category *cat = new Category(name.value_or(""), ri);
std::unique_ptr<Category> ptr(cat);
- TiXmlElement *packNode = catNode->FirstChildElement("reapack");
-
- while(packNode) {
+ for(XmlNode packNode = catNode.firstChild("reapack");
+ packNode; packNode = packNode.nextSibling("reapack"))
LoadPackageV1(packNode, cat);
- packNode = packNode->NextSiblingElement("reapack");
- }
-
if(ri->addCategory(cat))
ptr.release();
}
-void LoadPackageV1(TiXmlElement *packNode, Category *cat)
+void LoadPackageV1(XmlNode packNode, Category *cat)
{
- const char *type = packNode->Attribute("type");
- if(!type) type = "";
-
- const char *name = packNode->Attribute("name");
- if(!name) name = "";
-
- const char *desc = packNode->Attribute("desc");
- if(!desc) desc = "";
+ const XmlString &type = packNode.attribute("type"),
+ &name = packNode.attribute("name");
- Package *pack = new Package(Package::getType(type), name, cat);
+ Package *pack = new Package(Package::getType(type.value_or("")), name.value_or(""), cat);
std::unique_ptr<Package> ptr(pack);
- pack->setDescription(desc);
+ if(const XmlString &desc = packNode.attribute("desc"))
+ pack->setDescription(*desc);
- TiXmlElement *node = packNode->FirstChildElement("version");
-
- while(node) {
+ for(XmlNode node = packNode.firstChild("version");
+ node; node = node.nextSibling("version"))
LoadVersionV1(node, pack);
- node = node->NextSiblingElement("version");
- }
-
- node = packNode->FirstChildElement("metadata");
-
- if(node)
+ if(XmlNode node = packNode.firstChild("metadata"))
LoadMetadataV1(node, pack->metadata());
if(cat->addPackage(pack))
ptr.release();
}
-void LoadVersionV1(TiXmlElement *verNode, Package *pkg)
+void LoadVersionV1(XmlNode verNode, Package *pkg)
{
- const char *name = verNode->Attribute("name");
- if(!name) name = "";
-
- Version *ver = new Version(name, pkg);
+ const XmlString &name = verNode.attribute("name");
+ Version *ver = new Version(name.value_or(""), pkg);
std::unique_ptr<Version> ptr(ver);
- const char *author = verNode->Attribute("author");
- if(author) ver->setAuthor(author);
+ if(const XmlString &author = verNode.attribute("author"))
+ ver->setAuthor(*author);
- const char *time = verNode->Attribute("time");
- if(time) ver->setTime(time);
+ if(const XmlString &time = verNode.attribute("time"))
+ ver->setTime(*time);
- TiXmlElement *node = verNode->FirstChildElement("source");
+ XmlNode node = verNode.firstChild("source");
while(node) {
LoadSourceV1(node, ver);
- node = node->NextSiblingElement("source");
+ node = node.nextSibling("source");
}
- node = verNode->FirstChildElement("changelog");
+ node = verNode.firstChild("changelog");
if(node) {
- if(const char *changelog = node->GetText())
- ver->setChangelog(changelog);
+ if(const XmlString &changelog = node.text())
+ ver->setChangelog(*changelog);
}
if(pkg->addVersion(ver))
ptr.release();
}
-void LoadSourceV1(TiXmlElement *node, Version *ver)
+void LoadSourceV1(XmlNode node, Version *ver)
{
- const char *platform = node->Attribute("platform");
- if(!platform) platform = "all";
-
- const char *type = node->Attribute("type");
- if(!type) type = "";
-
- const char *file = node->Attribute("file");
- if(!file) file = "";
-
- const char *checksum = node->Attribute("hash");
- if(!checksum) checksum = "";
-
- const char *main = node->Attribute("main");
- if(!main) main = "";
-
- const char *url = node->GetText();
- if(!url) url = "";
-
- Source *src = new Source(file, url, ver);
+ const XmlString &platform = node.attribute("platform"),
+ &type = node.attribute("type"),
+ &file = node.attribute("file"),
+ &checksum = node.attribute("hash"),
+ &main = node.attribute("main"),
+ &url = node.text();
+
+ Source *src = new Source(file.value_or(""), url.value_or(""), ver);
std::unique_ptr<Source> ptr(src);
- src->setChecksum(checksum);
- src->setPlatform(platform);
- src->setTypeOverride(Package::getType(type));
+ src->setChecksum(checksum.value_or(""));
+ src->setPlatform(platform.value_or("all"));
+ src->setTypeOverride(Package::getType(type.value_or("")));
int sections = 0;
std::string section;
- std::istringstream mainStream(main);
+ std::istringstream mainStream(main.value_or(""));
while(std::getline(mainStream, section, '\x20'))
sections |= Source::getSection(section.c_str());
src->setSections(sections);
diff --git a/src/xml.cpp b/src/xml.cpp
@@ -0,0 +1,157 @@
+/* ReaPack: Package manager for REAPER
+ * Copyright (C) 2015-2020 Christian Fillion
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "xml.hpp"
+
+#include <cstdio>
+#include <cstring>
+#include <fstream>
+
+#include <libxml/parser.h>
+
+constexpr int LIBXML2_OPTIONS =
+ XML_PARSE_NOBLANKS | XML_PARSE_NOERROR | XML_PARSE_NOWARNING;
+
+static int readCallback(void *context, char *buffer, const int size)
+{
+ auto stream = static_cast<std::istream *>(context);
+ const std::streamsize bytes = stream->read(buffer, size).gcount();
+ return static_cast<int>(bytes);
+}
+
+XmlDocument::XmlDocument(std::istream &stream)
+{
+ xmlResetLastError();
+
+ m_doc =
+ xmlReadIO(&readCallback, nullptr, static_cast<void *>(&stream),
+ nullptr, nullptr, LIBXML2_OPTIONS);
+}
+
+XmlDocument::XmlDocument(XmlDocument &&) = default;
+
+XmlDocument::~XmlDocument()
+{
+ if(m_doc)
+ xmlFreeDoc(m_doc);
+}
+
+XmlDocument::operator bool() const
+{
+ return m_doc && xmlGetLastError() == nullptr;
+}
+
+const char *XmlDocument::error() const
+{
+ if(xmlError *error = xmlGetLastError()) {
+ const size_t length = strlen(error->message);
+ if(length > 1) {
+ // remove trailing newline
+ char &tail = error->message[length - 1];
+ if(tail == '\n')
+ tail = 0;
+ }
+
+ return error->message;
+ }
+
+ return nullptr;
+}
+
+XmlNode XmlDocument::root() const
+{
+ return xmlDocGetRootElement(m_doc);
+}
+
+XmlNode::XmlNode(xmlNode *node) : m_node(node) {}
+XmlNode::XmlNode(const XmlNode &) = default;
+XmlNode::~XmlNode() = default;
+
+XmlNode::operator bool() const
+{
+ return m_node != nullptr;
+}
+
+XmlNode &XmlNode::operator=(const XmlNode &) = default;
+
+const char *XmlNode::name() const
+{
+ return reinterpret_cast<const char *>(m_node->name);
+}
+
+XmlString XmlNode::attribute(const char *name) const
+{
+ return xmlGetProp(m_node, reinterpret_cast<const xmlChar *>(name));
+}
+
+bool XmlNode::attribute(const char *name, int *output) const
+{
+ if(const XmlString &value = attribute(name)) {
+ return sscanf(*value, "%d", output) == 1;
+ }
+
+ return false;
+}
+
+XmlString XmlNode::text() const
+{
+ return m_node->children ? xmlNodeGetContent(m_node) : nullptr;
+}
+
+XmlNode XmlNode::firstChild(const char *element) const
+{
+ for(xmlNode *node = m_node->children; node; node = node->next) {
+ if(node->type == XML_ELEMENT_NODE && (!element ||
+ !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>(element))))
+ return node;
+ }
+
+ return nullptr;
+}
+
+XmlNode XmlNode::nextSibling(const char *element) const
+{
+ for(xmlNode *node = m_node->next; node; node = node->next) {
+ if(node->type == XML_ELEMENT_NODE && (!element ||
+ !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>(element))))
+ return node;
+ }
+
+ return nullptr;
+}
+
+XmlString::XmlString(xmlChar *str) : m_str(str) {}
+
+XmlString::~XmlString()
+{
+ xmlFree(m_str);
+}
+
+XmlString::operator bool() const
+{
+ return m_str != nullptr;
+}
+
+const char *XmlString::operator *() const
+{
+ return reinterpret_cast<const char *>(m_str);
+}
+
+const char *XmlString::value_or(const char *fallback) const
+{
+ return m_str ? **this : fallback;
+}
diff --git a/src/xml.hpp b/src/xml.hpp
@@ -0,0 +1,84 @@
+/* ReaPack: Package manager for REAPER
+ * Copyright (C) 2015-2020 Christian Fillion
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef REAPACK_XML_HPP
+#define REAPACK_XML_HPP
+
+#include <istream>
+
+class XmlNode;
+class XmlString;
+
+struct _xmlDoc;
+using xmlDoc = _xmlDoc;
+struct _xmlNode;
+using xmlNode = _xmlNode;
+using xmlChar = unsigned char;
+
+class XmlDocument {
+public:
+ XmlDocument(std::istream &);
+ XmlDocument(const XmlDocument &) = delete;
+ XmlDocument(XmlDocument &&);
+ ~XmlDocument();
+
+ operator bool() const;
+ const char *error() const;
+
+ XmlNode root() const;
+
+private:
+ xmlDoc *m_doc;
+};
+
+class XmlNode {
+public:
+ XmlNode(xmlNode *);
+ XmlNode(const XmlNode &);
+ ~XmlNode();
+
+ operator bool() const;
+ XmlNode &operator=(const XmlNode &);
+
+ const char *name() const;
+ XmlString attribute(const char *name) const;
+ bool attribute(const char *name, int *value) const;
+ XmlString text() const;
+
+ XmlNode firstChild(const char *element = nullptr) const;
+ XmlNode nextSibling(const char *element = nullptr) const;
+
+private:
+ xmlNode *m_node;
+};
+
+class XmlString {
+public:
+ XmlString(xmlChar *);
+ XmlString(const XmlString &) = delete;
+ XmlString(XmlString &&) = default;
+ ~XmlString();
+
+ operator bool() const;
+ const char *operator *() const;
+ const char *value_or(const char *fallback) const;
+
+private:
+ xmlChar *m_str;
+};
+
+#endif
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
@@ -27,6 +27,7 @@ add_executable(tests EXCLUDE_FROM_ALL
time.cpp
version.cpp
win32.cpp
+ xml.cpp
)
# std::uncaught_exceptions is unavailable prior to macOS 10.12
diff --git a/test/index.cpp b/test/index.cpp
@@ -28,9 +28,7 @@ TEST_CASE("load index from raw data", M) {
Index::load({}, "<index>\n");
FAIL();
}
- catch(const reapack_error &e) {
- REQUIRE(std::string{e.what()} == "Error reading end tag.");
- }
+ catch(const reapack_error &) {}
}
}
@@ -49,9 +47,7 @@ TEST_CASE("broken index", M) {
IndexPtr ri = Index::load("broken");
FAIL();
}
- catch(const reapack_error &e) {
- REQUIRE(std::string{e.what()} == "Error reading end tag.");
- }
+ catch(const reapack_error &) {}
}
TEST_CASE("wrong root tag name", M) {
diff --git a/test/xml.cpp b/test/xml.cpp
@@ -0,0 +1,162 @@
+#include "helper.hpp"
+
+#include <xml.hpp>
+
+#include <cstring>
+#include <sstream>
+
+static const char *M = "[xml]";
+
+using namespace std::string_literals;
+
+TEST_CASE("read empty document", M) {
+ std::stringstream s;
+ XmlDocument doc(s);
+ REQUIRE((!doc || !doc.root()));
+}
+
+TEST_CASE("read invalid document", M) {
+ std::stringstream s("<unclosed_tag>");
+ XmlDocument doc(s);
+ REQUIRE(!doc);
+ REQUIRE(!doc.root());
+ REQUIRE(strlen(doc.error()) > 0);
+}
+
+TEST_CASE("valid document has no errors", M) {
+ std::stringstream s("<root></root>");
+ XmlDocument doc(s);
+ REQUIRE(doc);
+ REQUIRE(doc.root());
+ REQUIRE(doc.error() == nullptr);
+}
+
+static XmlDocument parse(const char *data)
+{
+ std::stringstream s(data);
+
+ XmlDocument doc(s);
+ CHECK(doc);
+ CHECK(doc.root());
+
+ if(doc.error())
+ FAIL(doc.error());
+
+ return doc;
+}
+
+TEST_CASE("read element name", M) {
+ const XmlDocument &doc = parse("<foobar></foobar>");
+ REQUIRE(doc.root().name() == "foobar"s);
+}
+
+TEST_CASE("read string attribute", M) {
+ const XmlDocument &doc = parse(R"(<root foo="bar" bar=""></root>)");
+
+ SECTION("exists") {
+ REQUIRE(doc.root().attribute("foo"));
+ REQUIRE(*doc.root().attribute("foo") == "bar"s);
+ }
+
+ SECTION("no value") {
+ REQUIRE(doc.root().attribute("bar"));
+ REQUIRE(*doc.root().attribute("bar") == ""s);
+ }
+
+ SECTION("missing") {
+ REQUIRE(!doc.root().attribute("baz"));
+ REQUIRE(*doc.root().attribute("baz") == nullptr);
+ }
+}
+
+TEST_CASE("read integer attribute", M) {
+ const XmlDocument &doc = parse(R"(<root foo="123" bar="baz"></root>)");
+ int output = 42'42'42;
+
+ SECTION("numeric value") {
+ REQUIRE(doc.root().attribute("foo", &output));
+ REQUIRE(output == 123);
+ }
+
+ SECTION("failed conversion") {
+ REQUIRE(!doc.root().attribute("bar", &output));
+ REQUIRE(output == 42'42'42);
+ }
+
+ SECTION("missing attribute") {
+ REQUIRE(!doc.root().attribute("baz", &output));
+ REQUIRE(output == 42'42'42);
+ }
+}
+
+TEST_CASE("no element contents", M) {
+ const XmlDocument &doc = parse("<root></root>");
+
+ REQUIRE(!doc.root().text());
+ REQUIRE(*doc.root().text() == nullptr);
+}
+
+TEST_CASE("read element contents", M) {
+ const XmlDocument &doc = parse("<root>Hello\nWorld!</root>");
+
+ REQUIRE(doc.root().text());
+ REQUIRE(*doc.root().text() == "Hello\nWorld!"s);
+}
+
+TEST_CASE("read element CDATA", M) {
+ const XmlDocument &doc = parse("<root><![CDATA[Hello\nWorld!]]>\n\x20\x20</root>");
+
+ REQUIRE(doc.root().text());
+ REQUIRE(*doc.root().text() == "Hello\nWorld!"s);
+}
+
+TEST_CASE("get first child element", M) {
+ const XmlDocument &doc = parse(R"(
+<root>
+ <foo>
+ <chunky></chunky>
+ </foo>
+ <bar>
+ <bacon></bacon>
+ </bar>
+</root>)");
+
+ SECTION("first child") {
+ REQUIRE(doc.root().firstChild());
+ REQUIRE(doc.root().firstChild().name() == "foo"s);
+ }
+
+ SECTION("find by name") {
+ REQUIRE(doc.root().firstChild("bar"));
+ REQUIRE(doc.root().firstChild("bar").name() == "bar"s);
+ }
+}
+
+TEST_CASE("get next sibling element", M) {
+ const XmlDocument &doc = parse(R"(
+<root>
+ <foo>
+ <chunky></chunky>
+ <bacon></bacon>
+ </foo>
+ <bar>
+ <hello></hello>
+ <world></world>
+ </bar>
+ <baz>
+ <coffee></coffee>
+ </baz>
+</root>)");
+
+ const XmlNode &foo = doc.root().firstChild();
+
+ SECTION("first sibling") {
+ REQUIRE(foo.nextSibling());
+ REQUIRE(foo.nextSibling().name() == "bar"s);
+ }
+
+ SECTION("find by name") {
+ REQUIRE(foo.nextSibling("baz"));
+ REQUIRE(foo.nextSibling("baz").name() == "baz"s);
+ }
+}
diff --git a/vendor/vcpkg-deps.txt b/vendor/vcpkg-deps.txt
@@ -1 +1 @@
-boost-algorithm boost-core boost-lexical-cast boost-logic boost-math boost-preprocessor boost-range catch2 curl[non-http] sqlite3
+boost-algorithm boost-core boost-lexical-cast boost-logic boost-math boost-preprocessor boost-range catch2 curl[non-http] libxml2 sqlite3