diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bf6948b8..0b460b7e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: matrix: os: [ubuntu, macos] compiler: [g++, clang++] - defines: [standard, PUGIXML_WCHAR_MODE, PUGIXML_COMPACT, PUGIXML_NO_EXCEPTIONS] + defines: [standard, PUGIXML_WCHAR_MODE, PUGIXML_COMPACT, PUGIXML_NO_EXCEPTIONS, PUGIXML_STRING_VIEW] exclude: - os: macos compiler: g++ @@ -25,6 +25,7 @@ jobs: export CXX=${{matrix.compiler}} make test cxxstd=c++11 defines=${{matrix.defines}} config=release -j2 make test cxxstd=c++98 defines=${{matrix.defines}} config=debug -j2 + make test cxxstd=c++17 defines=${{matrix.defines}} config=debug -j2 make test defines=${{matrix.defines}} config=sanitize -j2 - name: make coverage if: ${{!(matrix.os == 'ubuntu' && matrix.compiler == 'clang++')}} # linux/clang produces coverage info gcov can't parse @@ -38,7 +39,7 @@ jobs: strategy: matrix: arch: [Win32, x64] - defines: [standard, PUGIXML_WCHAR_MODE, PUGIXML_COMPACT, PUGIXML_NO_EXCEPTIONS] + defines: [standard, PUGIXML_WCHAR_MODE, PUGIXML_COMPACT, PUGIXML_NO_EXCEPTIONS, PUGIXML_STRING_VIEW] steps: - uses: actions/checkout@v1 - name: cmake configure @@ -50,3 +51,10 @@ jobs: Debug/pugixml-check.exe cmake --build . -- -property:Configuration=Release -verbosity:minimal Release/pugixml-check.exe + - name: cmake configure (c++17) + run: cmake . -DPUGIXML_BUILD_TESTS=ON -DCMAKE_CXX_STANDARD=17 -D${{matrix.defines}}=ON -A ${{matrix.arch}} + - name: cmake test (c++17) + shell: bash # necessary for fail-fast + run: | + cmake --build . -- -property:Configuration=Debug -verbosity:minimal + Debug/pugixml-check.exe diff --git a/CMakeLists.txt b/CMakeLists.txt index c04fa8af..53a3b1fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,14 +48,17 @@ option(PUGIXML_INSTALL "Enable installation rules" ON) option(PUGIXML_NO_XPATH "Disable XPath" OFF) option(PUGIXML_NO_STL "Disable STL" OFF) option(PUGIXML_NO_EXCEPTIONS "Disable Exceptions" OFF) -mark_as_advanced(PUGIXML_NO_XPATH PUGIXML_NO_STL PUGIXML_NO_EXCEPTIONS) +option(PUGIXML_STRING_VIEW "Enable std::string_view overloads" OFF) # requires C++17 and for PUGI_NO_STL to be OFF +mark_as_advanced(PUGIXML_NO_XPATH PUGIXML_NO_STL PUGIXML_NO_EXCEPTIONS PUGIXML_STRING_VIEW) set(PUGIXML_PUBLIC_DEFINITIONS $<$:PUGIXML_WCHAR_MODE> $<$:PUGIXML_COMPACT> $<$:PUGIXML_NO_XPATH> $<$:PUGIXML_NO_STL> - $<$:PUGIXML_NO_EXCEPTIONS>) + $<$:PUGIXML_NO_EXCEPTIONS> + $<$:PUGIXML_STRING_VIEW> +) # This is used to backport a CMake 3.15 feature, but is also forwards compatible if (NOT DEFINED CMAKE_MSVC_RUNTIME_LIBRARY) diff --git a/src/pugiconfig.hpp b/src/pugiconfig.hpp index a112de30..cc138ba0 100644 --- a/src/pugiconfig.hpp +++ b/src/pugiconfig.hpp @@ -49,6 +49,11 @@ // Uncomment this to enable long long support // #define PUGIXML_HAS_LONG_LONG +// Uncomment this to enable support for std::string_view (requires c++17 and for PUGIXML_NO_STL to not be set) +// Note: In a future version of pugixml this macro will become obsolete. +// Support will then be enabled automatically if the used C++ standard supports it. +// #define PUGIXML_STRING_VIEW + #endif /** diff --git a/src/pugixml.cpp b/src/pugixml.cpp index 652d1e20..812e8ea3 100644 --- a/src/pugixml.cpp +++ b/src/pugixml.cpp @@ -5441,6 +5441,15 @@ namespace pugi return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs, size); } +#ifdef PUGIXML_HAS_STRING_VIEW + PUGI_IMPL_FN bool xml_attribute::set_name(string_view_t rhs) + { + if (!_attr) return false; + + return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs.data(), rhs.size()); + } +#endif + PUGI_IMPL_FN bool xml_attribute::set_value(const char_t* rhs) { if (!_attr) return false; @@ -5455,6 +5464,15 @@ namespace pugi return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, size); } +#ifdef PUGIXML_HAS_STRING_VIEW + PUGI_IMPL_FN bool xml_attribute::set_value(string_view_t rhs) + { + if (!_attr) return false; + + return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs.data(), rhs.size()); + } +#endif + PUGI_IMPL_FN bool xml_attribute::set_value(int rhs) { if (!_attr) return false; @@ -5848,6 +5866,18 @@ namespace pugi return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs, size); } +#ifdef PUGIXML_HAS_STRING_VIEW + PUGI_IMPL_FN bool xml_node::set_name(string_view_t rhs) + { + xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null; + + if (type_ != node_element && type_ != node_pi && type_ != node_declaration) + return false; + + return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs.data(), rhs.size()); + } +#endif + PUGI_IMPL_FN bool xml_node::set_value(const char_t* rhs) { xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null; @@ -5868,6 +5898,18 @@ namespace pugi return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs, size); } +#ifdef PUGIXML_HAS_STRING_VIEW + PUGI_IMPL_FN bool xml_node::set_value(string_view_t rhs) + { + xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null; + + if (type_ != node_pcdata && type_ != node_cdata && type_ != node_comment && type_ != node_pi && type_ != node_doctype) + return false; + + return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs.data(), rhs.size()); + } +#endif + PUGI_IMPL_FN xml_attribute xml_node::append_attribute(const char_t* name_) { if (!impl::allow_insert_attribute(type())) return xml_attribute(); @@ -6757,6 +6799,15 @@ namespace pugi return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, size) : false; } +#ifdef PUGIXML_HAS_STRING_VIEW + PUGI_IMPL_FN bool xml_text::set(string_view_t rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs.data(), rhs.size()) : false; + } +#endif + PUGI_IMPL_FN bool xml_text::set(int rhs) { xml_node_struct* dn = _data_new(); diff --git a/src/pugixml.hpp b/src/pugixml.hpp index 9af50df6..a74e7e12 100644 --- a/src/pugixml.hpp +++ b/src/pugixml.hpp @@ -38,6 +38,20 @@ # include #endif +// Check if std::string_view is both requested and available +#if defined(PUGIXML_STRING_VIEW) && !defined(PUGIXML_NO_STL) +# if __cplusplus >= 201703L +# define PUGIXML_HAS_STRING_VIEW +# elif defined(_MSVC_LANG) && _MSVC_LANG >= 201703L +# define PUGIXML_HAS_STRING_VIEW +# endif +#endif + +// Include string_view if appropriate +#ifdef PUGIXML_HAS_STRING_VIEW +# include +#endif + // Macro for deprecated features #ifndef PUGIXML_DEPRECATED # if defined(__GNUC__) @@ -140,6 +154,11 @@ namespace pugi // String type used for operations that work with STL string; depends on PUGIXML_WCHAR_MODE typedef std::basic_string string_t; #endif + +#ifdef PUGIXML_HAS_STRING_VIEW + // String view type used for operations that can work with a length delimited string; depends on PUGIXML_WCHAR_MODE + typedef std::basic_string_view string_view_t; +#endif } // The PugiXML namespace @@ -423,8 +442,14 @@ namespace pugi // Set attribute name/value (returns false if attribute is empty or there is not enough memory) bool set_name(const char_t* rhs); bool set_name(const char_t* rhs, size_t size); + #ifdef PUGIXML_HAS_STRING_VIEW + bool set_name(string_view_t rhs); + #endif bool set_value(const char_t* rhs); bool set_value(const char_t* rhs, size_t size); + #ifdef PUGIXML_HAS_STRING_VIEW + bool set_value(string_view_t rhs); + #endif // Set attribute value with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") bool set_value(int rhs); @@ -559,8 +584,14 @@ namespace pugi // Set node name/value (returns false if node is empty, there is not enough memory, or node can not have name/value) bool set_name(const char_t* rhs); bool set_name(const char_t* rhs, size_t size); + #ifdef PUGIXML_HAS_STRING_VIEW + bool set_name(string_view_t rhs); + #endif bool set_value(const char_t* rhs); bool set_value(const char_t* rhs, size_t size); + #ifdef PUGIXML_HAS_STRING_VIEW + bool set_value(string_view_t rhs); + #endif // Add attribute with specified name. Returns added attribute, or empty attribute on errors. xml_attribute append_attribute(const char_t* name); @@ -790,6 +821,9 @@ namespace pugi // Set text (returns false if object is empty or there is not enough memory) bool set(const char_t* rhs); bool set(const char_t* rhs, size_t size); + #ifdef PUGIXML_HAS_STRING_VIEW + bool set(string_view_t rhs); + #endif // Set text with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") bool set(int rhs); diff --git a/tests/test_dom_modify.cpp b/tests/test_dom_modify.cpp index be916096..71dc5540 100644 --- a/tests/test_dom_modify.cpp +++ b/tests/test_dom_modify.cpp @@ -56,6 +56,23 @@ TEST_XML(dom_attr_set_name_with_size, "") CHECK_NODE(doc, STR("")); } +#ifdef PUGIXML_HAS_STRING_VIEW +TEST_XML(dom_attr_set_name_with_string_view, "") +{ + xml_attribute attr = doc.child(STR("node")).attribute(STR("attr")); + + CHECK(attr.set_name(string_view_t())); + CHECK(!xml_attribute().set_name(string_view_t())); + + CHECK_NODE(doc, STR("")); + + CHECK(attr.set_name(string_view_t(STR("n1234"), 1))); + CHECK(!xml_attribute().set_name(string_view_t(STR("nfail"), 1))); + + CHECK_NODE(doc, STR("")); +} +#endif + TEST_XML(dom_attr_set_value, "") { xml_node node = doc.child(STR("node")); @@ -225,6 +242,25 @@ TEST_XML(dom_node_set_name_with_size, "text") CHECK_NODE(doc, STR("text")); } +#ifdef PUGIXML_HAS_STRING_VIEW +TEST_XML(dom_node_set_name_with_string_view, "text") +{ + xml_node node = doc.child(STR("node")); + + CHECK(node.set_name(string_view_t())); + CHECK(!node.first_child().set_name(string_view_t(STR("n42"), 1))); + CHECK(!xml_node().set_name(string_view_t(STR("nanothername"), 1))); + + CHECK_NODE(doc, STR("<:anonymous>text")); + + CHECK(node.set_name(string_view_t(STR("nlongname"), 1))); + CHECK(!doc.child(STR("node")).first_child().set_name(string_view_t(STR("n42"), 1))); + CHECK(!xml_node().set_name(string_view_t(STR("nanothername"), 1))); + + CHECK_NODE(doc, STR("text")); +} +#endif + TEST_XML(dom_node_set_value, "text") { CHECK(doc.child(STR("node")).first_child().set_value(STR("no text"))); @@ -252,6 +288,26 @@ TEST_XML(dom_node_set_value_with_size, "text") CHECK_NODE(doc, STR("no text")); } +#ifdef PUGIXML_HAS_STRING_VIEW +TEST_XML(dom_node_set_value_partially_with_string_view, "text") +{ + CHECK(doc.child(STR("node")).first_child().set_value(string_view_t(STR("no text"), 2))); + CHECK(!doc.child(STR("node")).set_value(string_view_t(STR("no text"), 2))); + CHECK(!xml_node().set_value(string_view_t(STR("no text"), 2))); + + CHECK_NODE(doc, STR("no")); +} + +TEST_XML(dom_node_set_value_with_string_view, "text") +{ + CHECK(doc.child(STR("node")).first_child().set_value(string_view_t(STR("no text"), 7))); + CHECK(!doc.child(STR("node")).set_value(string_view_t(STR("no text"), 7))); + CHECK(!xml_node().set_value(string_view_t(STR("no text"), 7))); + + CHECK_NODE(doc, STR("no text")); +} +#endif + TEST_XML(dom_node_set_value_allocated, "text") { CHECK(doc.child(STR("node")).first_child().set_value(STR("no text"))); diff --git a/tests/test_dom_text.cpp b/tests/test_dom_text.cpp index b275146e..d14284d2 100644 --- a/tests/test_dom_text.cpp +++ b/tests/test_dom_text.cpp @@ -301,6 +301,69 @@ TEST_XML(dom_text_set_partially_with_size, "") CHECK_NODE(node, STR("")); } +#ifdef PUGIXML_HAS_STRING_VIEW +TEST_XML(dom_text_set_with_string_view, "") +{ + xml_node node = doc.child(STR("node")); + xml_text t = node.text(); + + t.set(string_view_t(STR(""))); + CHECK(node.first_child().type() == node_pcdata); + CHECK_NODE(node, STR("")); + + t.set(string_view_t(STR("boo"))); + CHECK(node.first_child().type() == node_pcdata); + CHECK(node.first_child() == node.last_child()); + CHECK_NODE(node, STR("boo")); + + t.set(string_view_t(STR("foobarfoobar"))); + CHECK(node.first_child().type() == node_pcdata); + CHECK(node.first_child() == node.last_child()); + CHECK_NODE(node, STR("foobarfoobar")); + + t.set(string_view_t(STR(""))); + CHECK(node.first_child().type() == node_pcdata); + CHECK_NODE(node, STR("")); + + t.set(string_view_t(STR("something"))); + CHECK(node.first_child().type() == node_pcdata); + CHECK_NODE(node, STR("something")); + + // empty string view (null data pointer) + t.set(string_view_t()); + CHECK(node.first_child().type() == node_pcdata); + CHECK_NODE(node, STR("")); + + t.set(string_view_t(STR("afternulldata"))); + CHECK(node.first_child().type() == node_pcdata); + CHECK_NODE(node, STR("afternulldata")); +} + +TEST_XML(dom_text_set_partially_with_string_view, "") +{ + xml_node node = doc.child(STR("node")); + xml_text t = node.text(); + + t.set(string_view_t(STR("foo"), 0)); + CHECK(node.first_child().type() == node_pcdata); + CHECK_NODE(node, STR("")); + + t.set(string_view_t(STR("boofoo"), 3)); + CHECK(node.first_child().type() == node_pcdata); + CHECK(node.first_child() == node.last_child()); + CHECK_NODE(node, STR("boo")); + + t.set(string_view_t(STR("foobarfoobar"), 3)); + CHECK(node.first_child().type() == node_pcdata); + CHECK(node.first_child() == node.last_child()); + CHECK_NODE(node, STR("foo")); + + t.set(string_view_t(STR("foo"), 0)); + CHECK(node.first_child().type() == node_pcdata); + CHECK_NODE(node, STR("")); +} +#endif + TEST_XML(dom_text_assign, "") { xml_node node = doc.child(STR("node")); @@ -334,6 +397,10 @@ TEST_XML(dom_text_set_value, "") CHECK(node.append_child(STR("text1")).text().set(STR("v1"))); CHECK(!xml_text().set(STR("v1"))); + CHECK(!xml_text().set(STR("v1"), 2)); +#ifdef PUGIXML_HAS_STRING_VIEW + CHECK(!xml_text().set(string_view_t(STR("v1")))); +#endif CHECK(node.append_child(STR("text2")).text().set(-2147483647)); CHECK(node.append_child(STR("text3")).text().set(-2147483647 - 1)); @@ -470,6 +537,29 @@ TEST_XML(dom_text_middle, "notthisonetext") CHECK(t.data() == node.last_child()); } +#ifdef PUGIXML_HAS_STRING_VIEW +TEST_XML(dom_text_middle_string_view, "notthisonetext") +{ + xml_node node = doc.child(STR("node")); + xml_text t = node.text(); + + CHECK_STRING(t.get(), STR("text")); + t.set(string_view_t(STR("notext"))); + + CHECK_NODE(node, STR("notthisonenotext")); + CHECK(node.remove_child(t.data())); + + CHECK(!t); + CHECK_NODE(node, STR("notthisone")); + + t.set(string_view_t(STR("yestext"))); + + CHECK(t); + CHECK_NODE(node, STR("notthisoneyestext")); + CHECK(t.data() == node.last_child()); +} +#endif + TEST_XML_FLAGS(dom_text_data, "foo", parse_default | parse_pi) { xml_node node = doc.child(STR("node"));