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:
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 ©) { *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 ©) { *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