diff --git a/src/pugixml.cpp b/src/pugixml.cpp index 1abd80ca..b84658d2 100644 --- a/src/pugixml.cpp +++ b/src/pugixml.cpp @@ -252,6 +252,24 @@ PUGI_IMPL_NS_BEGIN #endif } +#ifdef PUGIXML_HAS_STRING_VIEW + // Check if the null-terminated dst string is equal to the entire contents of srcview + PUGI_IMPL_FN bool stringview_equal(string_view_t srcview, const char_t* dst) + { + // std::basic_string_view::compare(const char*) has the right behavior, but it performs an + // extra traversal of dst to compute its length. + assert(dst); + const char_t* src = srcview.data(); + size_t srclen = srcview.size(); + + while (srclen && *dst && *src == *dst) + { + --srclen; ++dst; ++src; + } + return srclen == 0 && *dst == 0; + } +#endif + // Compare lhs with [rhs_begin, rhs_end) PUGI_IMPL_FN bool strequalrange(const char_t* lhs, const char_t* rhs, size_t count) { @@ -5413,6 +5431,14 @@ namespace pugi return *this; } +#ifdef PUGIXML_HAS_STRING_VIEW + PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(string_view_t rhs) + { + set_value(rhs); + return *this; + } +#endif + #ifdef PUGIXML_HAS_LONG_LONG PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(long long rhs) { @@ -5736,6 +5762,64 @@ namespace pugi return xml_node(); } +#ifdef PUGIXML_HAS_STRING_VIEW + PUGI_IMPL_FN xml_node xml_node::child(string_view_t name_) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + { + const char_t* iname = i->name; + if (iname && impl::stringview_equal(name_, iname)) + return xml_node(i); + } + + return xml_node(); + } + + PUGI_IMPL_FN xml_attribute xml_node::attribute(string_view_t name_) const + { + if (!_root) return xml_attribute(); + + for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute) + { + const char_t* iname = i->name; + if (iname && impl::stringview_equal(name_, iname)) + return xml_attribute(i); + } + + return xml_attribute(); + } + + PUGI_IMPL_FN xml_node xml_node::next_sibling(string_view_t name_) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling) + { + const char_t* iname = i->name; + if (iname && impl::stringview_equal(name_, iname)) + return xml_node(i); + } + + return xml_node(); + } + + PUGI_IMPL_FN xml_node xml_node::previous_sibling(string_view_t name_) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->prev_sibling_c; i->next_sibling; i = i->prev_sibling_c) + { + const char_t* iname = i->name; + if (iname && impl::stringview_equal(name_, iname)) + return xml_node(i); + } + + return xml_node(); + } +#endif + PUGI_IMPL_FN xml_attribute xml_node::attribute(const char_t* name_, xml_attribute& hint_) const { xml_attribute_struct* hint = hint_._attr; @@ -5775,6 +5859,47 @@ namespace pugi return xml_attribute(); } +#ifdef PUGIXML_HAS_STRING_VIEW + PUGI_IMPL_FN xml_attribute xml_node::attribute(string_view_t name_, xml_attribute& hint_) const + { + xml_attribute_struct* hint = hint_._attr; + + // if hint is not an attribute of node, behavior is not defined + assert(!hint || (_root && impl::is_attribute_of(hint, _root))); + + if (!_root) return xml_attribute(); + + // optimistically search from hint up until the end + for (xml_attribute_struct* i = hint; i; i = i->next_attribute) + { + const char_t* iname = i->name; + if (iname && impl::stringview_equal(name_, iname)) + { + // update hint to maximize efficiency of searching for consecutive attributes + hint_._attr = i->next_attribute; + + return xml_attribute(i); + } + } + + // wrap around and search from the first attribute until the hint + // 'j' null pointer check is technically redundant, but it prevents a crash in case the assertion above fails + for (xml_attribute_struct* j = _root->first_attribute; j && j != hint; j = j->next_attribute) + { + const char_t* jname = j->name; + if (jname && impl::stringview_equal(name_, jname)) + { + // update hint to maximize efficiency of searching for consecutive attributes + hint_._attr = j->next_attribute; + + return xml_attribute(j); + } + } + + return xml_attribute(); + } +#endif + PUGI_IMPL_FN xml_node xml_node::previous_sibling() const { if (!_root) return xml_node(); @@ -5980,6 +6105,78 @@ namespace pugi return a; } +#ifdef PUGIXML_HAS_STRING_VIEW + PUGI_IMPL_FN xml_attribute xml_node::append_attribute(string_view_t name_) + { + if (!impl::allow_insert_attribute(type())) return xml_attribute(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(alloc)); + if (!a) return xml_attribute(); + + impl::append_attribute(a._attr, _root); + + a.set_name(name_); + + return a; + } + + PUGI_IMPL_FN xml_attribute xml_node::prepend_attribute(string_view_t name_) + { + if (!impl::allow_insert_attribute(type())) return xml_attribute(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(alloc)); + if (!a) return xml_attribute(); + + impl::prepend_attribute(a._attr, _root); + + a.set_name(name_); + + return a; + } + + PUGI_IMPL_FN xml_attribute xml_node::insert_attribute_after(string_view_t name_, const xml_attribute& attr) + { + if (!impl::allow_insert_attribute(type())) return xml_attribute(); + if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(alloc)); + if (!a) return xml_attribute(); + + impl::insert_attribute_after(a._attr, attr._attr, _root); + + a.set_name(name_); + + return a; + } + + PUGI_IMPL_FN xml_attribute xml_node::insert_attribute_before(string_view_t name_, const xml_attribute& attr) + { + if (!impl::allow_insert_attribute(type())) return xml_attribute(); + if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(alloc)); + if (!a) return xml_attribute(); + + impl::insert_attribute_before(a._attr, attr._attr, _root); + + a.set_name(name_); + + return a; + } +#endif + PUGI_IMPL_FN xml_attribute xml_node::append_copy(const xml_attribute& proto) { if (!proto) return xml_attribute(); @@ -6156,6 +6353,44 @@ namespace pugi return result; } +#ifdef PUGIXML_HAS_STRING_VIEW + PUGI_IMPL_FN xml_node xml_node::append_child(string_view_t name_) + { + xml_node result = append_child(node_element); + + result.set_name(name_); + + return result; + } + + PUGI_IMPL_FN xml_node xml_node::prepend_child(string_view_t name_) + { + xml_node result = prepend_child(node_element); + + result.set_name(name_); + + return result; + } + + PUGI_IMPL_FN xml_node xml_node::insert_child_after(string_view_t name_, const xml_node& node) + { + xml_node result = insert_child_after(node_element, node); + + result.set_name(name_); + + return result; + } + + PUGI_IMPL_FN xml_node xml_node::insert_child_before(string_view_t name_, const xml_node& node) + { + xml_node result = insert_child_before(node_element, node); + + result.set_name(name_); + + return result; + } +#endif + PUGI_IMPL_FN xml_node xml_node::append_copy(const xml_node& proto) { xml_node_type type_ = proto.type(); @@ -6299,6 +6534,13 @@ namespace pugi return remove_attribute(attribute(name_)); } +#ifdef PUGIXML_HAS_STRING_VIEW + PUGI_IMPL_FN bool xml_node::remove_attribute(string_view_t name_) + { + return remove_attribute(attribute(name_)); + } +#endif + PUGI_IMPL_FN bool xml_node::remove_attribute(const xml_attribute& a) { if (!_root || !a._attr) return false; @@ -6339,6 +6581,13 @@ namespace pugi return remove_child(child(name_)); } +#ifdef PUGIXML_HAS_STRING_VIEW + PUGI_IMPL_FN bool xml_node::remove_child(string_view_t name_) + { + return remove_child(child(name_)); + } +#endif + PUGI_IMPL_FN bool xml_node::remove_child(const xml_node& n) { if (!_root || !n._root || n._root->parent != _root) return false; @@ -6935,6 +7184,14 @@ namespace pugi return *this; } +#ifdef PUGIXML_HAS_STRING_VIEW + PUGI_IMPL_FN xml_text& xml_text::operator=(string_view_t rhs) + { + set(rhs); + return *this; + } +#endif + #ifdef PUGIXML_HAS_LONG_LONG PUGI_IMPL_FN xml_text& xml_text::operator=(long long rhs) { diff --git a/src/pugixml.hpp b/src/pugixml.hpp index 4bad982c..f2d985e0 100644 --- a/src/pugixml.hpp +++ b/src/pugixml.hpp @@ -477,6 +477,10 @@ namespace pugi xml_attribute& operator=(float rhs); xml_attribute& operator=(bool rhs); + #ifdef PUGIXML_HAS_STRING_VIEW + xml_attribute& operator=(string_view_t rhs); + #endif + #ifdef PUGIXML_HAS_LONG_LONG xml_attribute& operator=(long long rhs); xml_attribute& operator=(unsigned long long rhs); @@ -571,9 +575,18 @@ namespace pugi xml_attribute attribute(const char_t* name) const; xml_node next_sibling(const char_t* name) const; xml_node previous_sibling(const char_t* name) const; + #ifdef PUGIXML_HAS_STRING_VIEW + xml_node child(string_view_t name) const; + xml_attribute attribute(string_view_t name) const; + xml_node next_sibling(string_view_t name) const; + xml_node previous_sibling(string_view_t name) const; + #endif // Get attribute, starting the search from a hint (and updating hint so that searching for a sequence of attributes is fast) xml_attribute attribute(const char_t* name, xml_attribute& hint) const; + #ifdef PUGIXML_HAS_STRING_VIEW + xml_attribute attribute(string_view_t name, xml_attribute& hint) const; + #endif // Get child value of current node; that is, value of the first child node of type PCDATA/CDATA const char_t* child_value() const; @@ -598,6 +611,12 @@ namespace pugi xml_attribute prepend_attribute(const char_t* name); xml_attribute insert_attribute_after(const char_t* name, const xml_attribute& attr); xml_attribute insert_attribute_before(const char_t* name, const xml_attribute& attr); + #ifdef PUGIXML_HAS_STRING_VIEW + xml_attribute append_attribute(string_view_t name); + xml_attribute prepend_attribute(string_view_t name); + xml_attribute insert_attribute_after(string_view_t name, const xml_attribute& attr); + xml_attribute insert_attribute_before(string_view_t name, const xml_attribute& attr); + #endif // Add a copy of the specified attribute. Returns added attribute, or empty attribute on errors. xml_attribute append_copy(const xml_attribute& proto); @@ -616,6 +635,12 @@ namespace pugi xml_node prepend_child(const char_t* name); xml_node insert_child_after(const char_t* name, const xml_node& node); xml_node insert_child_before(const char_t* name, const xml_node& node); + #ifdef PUGIXML_HAS_STRING_VIEW + xml_node append_child(string_view_t name); + xml_node prepend_child(string_view_t name); + xml_node insert_child_after(string_view_t, const xml_node& node); + xml_node insert_child_before(string_view_t name, const xml_node& node); + #endif // Add a copy of the specified node as a child. Returns added node, or empty node on errors. xml_node append_copy(const xml_node& proto); @@ -632,6 +657,9 @@ namespace pugi // Remove specified attribute bool remove_attribute(const xml_attribute& a); bool remove_attribute(const char_t* name); + #ifdef PUGIXML_HAS_STRING_VIEW + bool remove_attribute(string_view_t name); + #endif // Remove all attributes bool remove_attributes(); @@ -639,6 +667,9 @@ namespace pugi // Remove specified child bool remove_child(const xml_node& n); bool remove_child(const char_t* name); + #ifdef PUGIXML_HAS_STRING_VIEW + bool remove_child(string_view_t name); + #endif // Remove all children bool remove_children(); @@ -851,6 +882,10 @@ namespace pugi xml_text& operator=(float rhs); xml_text& operator=(bool rhs); + #ifdef PUGIXML_HAS_STRING_VIEW + xml_text& operator=(string_view_t rhs); + #endif + #ifdef PUGIXML_HAS_LONG_LONG xml_text& operator=(long long rhs); xml_text& operator=(unsigned long long rhs); diff --git a/tests/test_dom_traverse.cpp b/tests/test_dom_traverse.cpp index 0c1e3af8..ba0da35c 100644 --- a/tests/test_dom_traverse.cpp +++ b/tests/test_dom_traverse.cpp @@ -536,6 +536,42 @@ TEST_XML(dom_node_child, "") CHECK(doc.child(STR("node")).child(STR("child2")) == doc.child(STR("node")).last_child()); } +#ifdef PUGIXML_HAS_STRING_VIEW +TEST_XML(dom_node_child_stringview, "") +{ + CHECK(xml_node().child(string_view_t(STR("n"))) == xml_node()); + CHECK(doc.child(string_view_t()) == xml_node()); + CHECK(doc.child(string_view_t(STR("n"))) == xml_node()); + + xml_node node = doc.child(string_view_t(STR("node"))); + CHECK_NAME_VALUE(node, STR("node"), STR("")); + CHECK(node.child(string_view_t(STR("child2"))) == node.last_child()); + + // verify only the characters in the view of the string view are included in the comparison + CHECK_NAME_VALUE(doc.child(string_view_t(STR("node_andextratext"), 4)), STR("node"), STR("")); + CHECK(doc.child(string_view_t(STR("node"), 2)) == xml_node()); +} + +TEST_XML(dom_node_child_interior_null, "") +{ + const char_t name[] = STR("node\0extra"); + size_t len = (sizeof(name) / sizeof(char_t)) - 1; + CHECK(len == 10); + + xml_node node = doc.child(string_view_t(name, 4)); // "node" view excluding null + CHECK_NAME_VALUE(node, STR("node"), STR("")); + CHECK(doc.child(string_view_t(name, 5)) == xml_node()); // "node\0" view including null + CHECK(doc.child(string_view_t(name, len)) == xml_node()); // "node\0extra" view + + node.set_name(string_view_t(name, len)); + CHECK_NODE(doc, STR("")); + CHECK_NAME_VALUE(node, STR("node"), STR("")); + CHECK_NAME_VALUE(doc.child(string_view_t(name, 4)), STR("node"), STR("")); // "node" view excluding null + CHECK(doc.child(string_view_t(name, 5)) == xml_node()); // "node\0" view including null + CHECK(doc.child(string_view_t(name, len)) == xml_node()); // "node\0extra" view +} +#endif + TEST_XML(dom_node_attribute, "") { CHECK(xml_node().attribute(STR("a")) == xml_attribute()); @@ -547,6 +583,46 @@ TEST_XML(dom_node_attribute, "") CHECK(node.attribute(STR("attr2")) == node.last_attribute()); } +#ifdef PUGIXML_HAS_STRING_VIEW +TEST_XML(dom_node_attribute_stringview, "") +{ + CHECK(xml_node().attribute(string_view_t(STR("a"))) == xml_attribute()); + + xml_node node = doc.child(string_view_t(STR("node"))); + + CHECK(node.attribute(string_view_t()) == xml_attribute()); + CHECK(node.attribute(string_view_t(STR("n"))) == xml_attribute()); + CHECK_NAME_VALUE(node.attribute(string_view_t(STR("attr1"))), STR("attr1"), STR("0")); + CHECK(node.attribute(string_view_t(STR("attr2"))) == node.last_attribute()); + + // verify only the characters in the view of the string view are included in the comparison + CHECK_NAME_VALUE(node.attribute(string_view_t(STR("attr1_andextratext"), 5)), STR("attr1"), STR("0")); + CHECK(node.attribute(string_view_t(STR("attr1"), 2)) == xml_attribute()); +} + +TEST_XML(dom_node_attribute_interior_null, "") +{ + xml_node node = doc.child(STR("node")); + CHECK_NAME_VALUE(node, STR("node"), STR("")); + + const char_t name[] = STR("attr2\0extra"); + size_t len = (sizeof(name) / sizeof(char_t)) - 1; + CHECK(len == 11); + CHECK_NAME_VALUE(node.attribute(string_view_t(name, 5)), STR("attr2"), STR("1")); // "attr2" view excluding null + CHECK(node.attribute(string_view_t(name, 6)) == xml_attribute()); // "attr2\0" view including null + CHECK(node.attribute(string_view_t(name, len)) == xml_attribute()); // "attr2\0extra" view + + xml_attribute attr = node.attribute(STR("attr2")); + CHECK_NAME_VALUE(attr, STR("attr2"), STR("1")); + attr.set_name(string_view_t(name, len)); + + CHECK_NODE(doc, STR("")); + CHECK_NAME_VALUE(node.attribute(string_view_t(name, 5)), STR("attr2"), STR("1")); // "attr2" view excluding null + CHECK(node.attribute(string_view_t(name, 6)) == xml_attribute()); // "attr2\0" view including null + CHECK(node.attribute(string_view_t(name, len)) == xml_attribute()); // "attr2\0extra" view +} +#endif + TEST_XML(dom_node_next_previous_sibling, "") { CHECK(xml_node().next_sibling() == xml_node()); @@ -567,9 +643,17 @@ TEST_XML(dom_node_next_previous_sibling, "value1value2value4") @@ -1302,3 +1386,35 @@ TEST(dom_node_anonymous) CHECK_STRING(doc.child_value(), STR("")); CHECK_STRING(doc.last_child().child_value(), STR("")); } + +TEST_XML(dom_node_anonymous_child, "") +{ + xml_node node = doc.child(STR("node")); + CHECK_NAME_VALUE(node, STR("node"), STR("")); + node.set_name(STR("")); + CHECK_NODE(doc, STR("<:anonymous/>")); + CHECK(doc.first_child() != xml_node()); + CHECK_NAME_VALUE(doc.first_child(), STR(""), STR("")); + + // searching for empty string does not find a node with empty name + CHECK(doc.child(STR("")) == xml_node()); +#ifdef PUGIXML_HAS_STRING_VIEW + CHECK(doc.child(string_view_t()) == xml_node()); + CHECK(doc.child(string_view_t(STR("hi"), 0)) == xml_node()); +#endif +} + +TEST_XML(dom_node_anonymous_attribute, "") +{ + xml_attribute attr = doc.first_child().attribute(STR("attr")); + CHECK(attr != xml_attribute()); + attr.set_name(STR("")); + CHECK_NODE(doc, STR("")); + CHECK_NAME_VALUE(doc.first_child().first_attribute(), STR(""), STR("0")); + + CHECK(doc.first_child().attribute(STR("")) == xml_attribute()); +#ifdef PUGIXML_HAS_STRING_VIEW + CHECK(doc.first_child().attribute(string_view_t()) == xml_attribute()); + CHECK(doc.first_child().attribute(string_view_t(STR("hi"), 0)) == xml_attribute()); +#endif +}