reapack

Package manager for REAPER
Log | Files | Refs | Submodules | README | LICENSE

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:
M.appveyor.yml | 2+-
MREADME.md | 1+
Dcmake/FindTinyXML.cmake | 25-------------------------
Msrc/CMakeLists.txt | 16+++++++++++++---
Msrc/index.cpp | 29++++++++++++++---------------
Msrc/index.hpp | 4++--
Msrc/index_v1.cpp | 167++++++++++++++++++++++++++++++-------------------------------------------------
Asrc/xml.cpp | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/xml.hpp | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/CMakeLists.txt | 1+
Mtest/index.cpp | 8++------
Atest/xml.cpp | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mvendor/vcpkg-deps.txt | 2+-
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