reapack

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

commit 70c81e1656ef2d0804bcf60ba29cb8c6288d7f17
parent 0b85cf9c2797fccc595f06e93519ed5e37915fb9
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Fri,  3 Jan 2020 06:02:53 -0500

migrate XML parsing to TinyXml2 on Windows

...to avoid adding almost 2MB to the binary from libxml2 and its dependencies.

Diffstat:
Msrc/CMakeLists.txt | 20+++++++-------------
Dsrc/xml.cpp | 157-------------------------------------------------------------------------------
Msrc/xml.hpp | 32+++++++++++++++++---------------
Asrc/xml_libxml2.cpp | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/xml_tinyxml2.cpp | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mvendor/vcpkg-deps.txt | 2+-
6 files changed, 269 insertions(+), 186 deletions(-)

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt @@ -1,6 +1,5 @@ find_package(Boost 1.56 REQUIRED) find_package(CURL REQUIRED) -find_package(LibXML2 REQUIRED) find_package(MiniZip REQUIRED) find_package(Threads REQUIRED) find_package(WDL REQUIRED) @@ -14,20 +13,14 @@ 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) target_link_libraries(SQLite::SQLite3 INTERFACE sqlite3) + + find_package(tinyxml2 CONFIG REQUIRED) else() + find_package(LibXml2 REQUIRED) find_package(SQLite3 REQUIRED) endif() @@ -85,7 +78,7 @@ add_library(reapack OBJECT transaction.cpp version.cpp win32.cpp - xml.cpp + xml_$<IF:$<BOOL:${LIBXML2_FOUND}>,libxml2,tinyxml2>.cpp ) target_compile_features(reapack PUBLIC cxx_std_17) @@ -106,8 +99,9 @@ else() endif() target_link_libraries(reapack - ${CMAKE_DL_LIBS} Boost::headers CURL::libcurl LibXml2::LibXml2 - MiniZip::MiniZip SQLite::SQLite3 Threads::Threads WDL::WDL + ${CMAKE_DL_LIBS} Boost::headers CURL::libcurl MiniZip::MiniZip + SQLite::SQLite3 Threads::Threads WDL::WDL + $<IF:$<BOOL:${LIBXML2_FOUND}>,LibXml2::LibXml2,tinyxml2::tinyxml2> ) if(OPENSSL_FOUND) diff --git a/src/xml.cpp b/src/xml.cpp @@ -1,157 +0,0 @@ -/* 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 @@ -19,17 +19,15 @@ #define REAPACK_XML_HPP #include <istream> +#include <memory> class XmlNode; class XmlString; -struct _xmlDoc; -using xmlDoc = _xmlDoc; -struct _xmlNode; -using xmlNode = _xmlNode; -using xmlChar = unsigned char; - class XmlDocument { + struct Impl; + using ImplPtr = std::unique_ptr<Impl>; + public: XmlDocument(std::istream &); XmlDocument(const XmlDocument &) = delete; @@ -42,17 +40,21 @@ public: XmlNode root() const; private: - xmlDoc *m_doc; + ImplPtr m_impl; }; class XmlNode { + friend XmlDocument; + struct Impl; + using ImplPtr = std::unique_ptr<Impl>; + public: - XmlNode(xmlNode *); + XmlNode(void *); XmlNode(const XmlNode &); ~XmlNode(); - operator bool() const; XmlNode &operator=(const XmlNode &); + operator bool() const; const char *name() const; XmlString attribute(const char *name) const; @@ -63,22 +65,22 @@ public: XmlNode nextSibling(const char *element = nullptr) const; private: - xmlNode *m_node; + ImplPtr m_impl; }; class XmlString { public: - XmlString(xmlChar *); + XmlString(const void *); XmlString(const XmlString &) = delete; XmlString(XmlString &&) = default; ~XmlString(); - operator bool() const; - const char *operator *() const; - const char *value_or(const char *fallback) const; + operator bool() const { return m_str != nullptr; } + const char *operator *() const { return reinterpret_cast<const char *>(m_str); } + const char *value_or(const char *fallback) const { return m_str ? **this : fallback; } private: - xmlChar *m_str; + const void *m_str; }; #endif diff --git a/src/xml_libxml2.cpp b/src/xml_libxml2.cpp @@ -0,0 +1,144 @@ +/* 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 <libxml/parser.h> + +constexpr int LIBXML2_OPTIONS = + XML_PARSE_NOBLANKS | XML_PARSE_NOERROR | XML_PARSE_NOWARNING; + +struct XmlDocument::Impl { xmlDoc *doc; }; +struct XmlNode::Impl { xmlNode *node; }; + +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) : m_impl{new Impl} +{ + xmlResetLastError(); + + m_impl->doc = + xmlReadIO(&readCallback, nullptr, static_cast<void *>(&stream), + nullptr, nullptr, LIBXML2_OPTIONS); +} + +XmlDocument::XmlDocument(XmlDocument &&) = default; + +XmlDocument::~XmlDocument() +{ + if(m_impl->doc) + xmlFreeDoc(m_impl->doc); +} + +XmlDocument::operator bool() const +{ + return m_impl->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_impl->doc); +} + +XmlNode::XmlNode(void *node) : m_impl(new Impl{static_cast<xmlNode *>(node)}) {} +XmlNode::XmlNode(const XmlNode &copy) { *this = copy; } +XmlNode::~XmlNode() = default; + +XmlNode &XmlNode::operator=(const XmlNode &other) +{ + m_impl.reset(new Impl(*other.m_impl)); + return *this; +} + +XmlNode::operator bool() const +{ + return m_impl->node != nullptr; +} + +const char *XmlNode::name() const +{ + return reinterpret_cast<const char *>(m_impl->node->name); +} + +XmlString XmlNode::attribute(const char *name) const +{ + return xmlGetProp(m_impl->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_impl->node->children ? xmlNodeGetContent(m_impl->node) : nullptr; +} + +XmlNode XmlNode::firstChild(const char *element) const +{ + for(xmlNode *node = m_impl->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_impl->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(const void *str) : m_str(str) {} +XmlString::~XmlString() { xmlFree(const_cast<void *>(m_str)); } diff --git a/src/xml_tinyxml2.cpp b/src/xml_tinyxml2.cpp @@ -0,0 +1,100 @@ +/* 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 <iterator> + +#include <tinyxml2.h> + +struct XmlDocument::Impl { tinyxml2::XMLDocument doc; }; +struct XmlNode::Impl { tinyxml2::XMLElement *node; }; + +XmlDocument::XmlDocument(std::istream &stream) + : m_impl{std::make_unique<Impl>()} +{ + std::string everything(std::istreambuf_iterator<char>(stream), {}); + m_impl->doc.Parse(everything.c_str(), everything.size()); +} + +XmlDocument::XmlDocument(XmlDocument &&) = default; +XmlDocument::~XmlDocument() = default; + +XmlDocument::operator bool() const +{ + return m_impl->doc.ErrorID() == tinyxml2::XML_SUCCESS; +} + +const char *XmlDocument::error() const +{ + return !*this ? m_impl->doc.ErrorStr() : nullptr; +} + +XmlNode XmlDocument::root() const +{ + return m_impl->doc.RootElement(); +} + + +XmlNode::XmlNode(void *node) + : m_impl(new Impl{static_cast<tinyxml2::XMLElement *>(node)}) {} +XmlNode::XmlNode(const XmlNode &copy) { *this = copy; } +XmlNode::~XmlNode() = default; + +XmlNode &XmlNode::operator=(const XmlNode &other) +{ + m_impl.reset(new Impl(*other.m_impl)); + return *this; +} + +XmlNode::operator bool() const +{ + return m_impl->node != nullptr; +} + +const char *XmlNode::name() const +{ + return m_impl->node->Value(); +} + +XmlString XmlNode::attribute(const char *name) const +{ + return m_impl->node->Attribute(name); +} + +bool XmlNode::attribute(const char *name, int *output) const +{ + return m_impl->node->QueryIntAttribute(name, output) == tinyxml2::XML_SUCCESS; +} + +XmlString XmlNode::text() const +{ + return m_impl->node->GetText(); +} + +XmlNode XmlNode::firstChild(const char *element) const +{ + return m_impl->node->FirstChildElement(element); +} + +XmlNode XmlNode::nextSibling(const char *element) const +{ + return m_impl->node->NextSiblingElement(element); +} + +XmlString::XmlString(const void *str) : m_str(str) {} +XmlString::~XmlString() = default; 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] libxml2 sqlite3 +boost-algorithm boost-core boost-lexical-cast boost-logic boost-math boost-preprocessor boost-range catch2 curl[non-http] sqlite3 tinyxml2