diff --git a/docs/manual.adoc b/docs/manual.adoc index 4484978e8..4776595c9 100644 --- a/docs/manual.adoc +++ b/docs/manual.adoc @@ -1377,7 +1377,7 @@ include::samples/modify_base.cpp[tags=attr] [[modify.add]] === Adding nodes/attributes -[[xml_node::prepend_attribute]][[xml_node::append_attribute]][[xml_node::insert_attribute_after]][[xml_node::insert_attribute_before]][[xml_node::prepend_child]][[xml_node::append_child]][[xml_node::insert_child_after]][[xml_node::insert_child_before]] +[[xml_node::prepend_attribute]][[xml_node::append_attribute]][[xml_node::insert_attribute_after]][[xml_node::insert_attribute_before]][[xml_node::ensure_attribute]][[xml_node::prepend_child]][[xml_node::append_child]][[xml_node::insert_child_after]][[xml_node::insert_child_before]][[xml_node::ensure_child]] Nodes and attributes do not exist without a document tree, so you can't create them without adding them to some document. A node or attribute can be created at the end of node/attribute list or before/after some other node: [source] @@ -1404,9 +1404,14 @@ xml_node xml_node::insert_child_after(const char_t* name, const xml_node& node); xml_node xml_node::insert_child_after(string_view_t name, const xml_node& node); xml_node xml_node::insert_child_before(const char_t* name, const xml_node& node); xml_node xml_node::insert_child_before(string_view_t name, const xml_node& node); + +xml_attribute xml_node::ensure_attribute(const char_t* name); +xml_attribute xml_node::ensure_attribute(string_view_t name); +xml_node xml_node::ensure_child(const char_t* name); +xml_node xml_node::ensure_child(string_view_t name); ---- -`append_attribute` and `append_child` create a new node/attribute at the end of the corresponding list of the node the method is called on; `prepend_attribute` and `prepend_child` create a new node/attribute at the beginning of the list; `insert_attribute_after`, `insert_attribute_before`, `insert_child_after` and `insert_child_before` add the node/attribute before or after the specified node/attribute. +`append_attribute` and `append_child` create a new node/attribute at the end of the corresponding list of the node the method is called on; `prepend_attribute` and `prepend_child` create a new node/attribute at the beginning of the list; `insert_attribute_after`, `insert_attribute_before`, `insert_child_after` and `insert_child_before` add the node/attribute before or after the specified node/attribute. `ensure_attribute` and `ensure_child` return the existing attribute/child with the specified name, appending a new one only if no such attribute/child exists; this makes it convenient to write code like `node.ensure_attribute("id") = 123;`. Attribute functions create an attribute with the specified name; you can specify the empty name and change the name later if you want to. Node functions with the `type` argument create the node with the specified type; since node type can't be changed, you have to know the desired type beforehand. Also note that not all types can be added as children; see below for clarification. Node functions with the `name` argument create the element node (<>) with the specified name. @@ -1422,7 +1427,7 @@ All functions return the handle to the created object on success, and null handl Even if the operation fails, the document remains in consistent state, but the requested node/attribute is not added. -CAUTION: `attribute()` and `child()` functions do not add attributes or nodes to the tree, so code like `node.attribute("id") = 123;` will not do anything if `node` does not have an attribute with name `"id"`. Make sure you're operating with existing attributes/nodes by adding them if necessary. +CAUTION: `attribute()` and `child()` functions do not add attributes or nodes to the tree, so code like `node.attribute("id") = 123;` will not do anything if `node` does not have an attribute with name `"id"`. Make sure you're operating with existing attributes/nodes by adding them if necessary, or use `ensure_attribute`/`ensure_child`. This is an example of adding new attributes/nodes to the document (link:samples/modify_add.cpp[]): @@ -2208,6 +2213,7 @@ Anniversary release (pugixml turns 20 this year!). Changes: * Improvements: . `PUGIXML_CHARCONV_FLOAT` option can be enabled to switch floating point conversions to ``; this requires C{plus}{plus}17, makes the conversions locale-independent and can improve performance + . Add `xml_node::ensure_child` and `xml_node::ensure_attribute` that return the child/attribute with the specified name, adding one if it does not exist . Improve performance of searching for nodes and attributes by name . Loading a document from an empty buffer no longer performs memory allocations @@ -3079,6 +3085,11 @@ const unsigned int +++parse_wnorm_attribute xml_node +++insert_child_before+++(const char_t* name, const xml_node& node); xml_node +++insert_child_before+++(string_view_t name, const xml_node& node); + xml_attribute +++ensure_attribute+++(const char_t* name); + xml_attribute +++ensure_attribute+++(string_view_t name); + xml_node +++ensure_child+++(const char_t* name); + xml_node +++ensure_child+++(string_view_t name); + xml_attribute +++append_copy+++(const xml_attribute& proto); xml_attribute +++prepend_copy+++(const xml_attribute& proto); xml_attribute +++insert_copy_after+++(const xml_attribute& proto, const xml_attribute& attr); diff --git a/docs/manual.html b/docs/manual.html index fd4c112c0..db0198dbb 100644 --- a/docs/manual.html +++ b/docs/manual.html @@ -2807,7 +2807,7 @@

6.3. Adding nodes/attributes

-

+

Nodes and attributes do not exist without a document tree, so you can’t create them without adding them to some document. A node or attribute can be created at the end of node/attribute list or before/after some other node:

-

append_attribute and append_child create a new node/attribute at the end of the corresponding list of the node the method is called on; prepend_attribute and prepend_child create a new node/attribute at the beginning of the list; insert_attribute_after, insert_attribute_before, insert_child_after and insert_child_before add the node/attribute before or after the specified node/attribute.

+

append_attribute and append_child create a new node/attribute at the end of the corresponding list of the node the method is called on; prepend_attribute and prepend_child create a new node/attribute at the beginning of the list; insert_attribute_after, insert_attribute_before, insert_child_after and insert_child_before add the node/attribute before or after the specified node/attribute. ensure_attribute and ensure_child return the existing attribute/child with the specified name, appending a new one only if no such attribute/child exists; this makes it convenient to write code like node.ensure_attribute("id") = 123;.

-attribute() and child() functions do not add attributes or nodes to the tree, so code like node.attribute("id") = 123; will not do anything if node does not have an attribute with name "id". Make sure you’re operating with existing attributes/nodes by adding them if necessary. +attribute() and child() functions do not add attributes or nodes to the tree, so code like node.attribute("id") = 123; will not do anything if node does not have an attribute with name "id". Make sure you’re operating with existing attributes/nodes by adding them if necessary, or use ensure_attribute/ensure_child. @@ -4153,6 +4158,9 @@

PUGIXML_CHARCONV_FLOAT option can be enabled to switch floating point conversions to <charconv>; this requires C++17, makes the conversions locale-independent and can improve performance

  • +

    Add xml_node::ensure_child and xml_node::ensure_attribute that return the child/attribute with the specified name, adding one if it does not exist

    +
  • +
  • Improve performance of searching for nodes and attributes by name

  • @@ -6128,6 +6136,11 @@

    xml_node insert_child_before(const char_t* name, const xml_node& node); xml_node insert_child_before(string_view_t name, const xml_node& node); + xml_attribute ensure_attribute(const char_t* name); + xml_attribute ensure_attribute(string_view_t name); + xml_node ensure_child(const char_t* name); + xml_node ensure_child(string_view_t name); + xml_attribute append_copy(const xml_attribute& proto); xml_attribute prepend_copy(const xml_attribute& proto); xml_attribute insert_copy_after(const xml_attribute& proto, const xml_attribute& attr); @@ -6375,7 +6388,7 @@

    diff --git a/src/pugixml.cpp b/src/pugixml.cpp index a38b5a7ef..8c286d25e 100644 --- a/src/pugixml.cpp +++ b/src/pugixml.cpp @@ -6256,6 +6256,22 @@ namespace pugi } #endif + PUGI_IMPL_FN xml_attribute xml_node::ensure_attribute(const char_t* name_) + { + xml_attribute result = attribute(name_); + + return result ? result : append_attribute(name_); + } + +#ifdef PUGIXML_HAS_STRING_VIEW + PUGI_IMPL_FN xml_attribute xml_node::ensure_attribute(string_view_t name_) + { + xml_attribute result = attribute(name_); + + return result ? result : append_attribute(name_); + } +#endif + PUGI_IMPL_FN xml_attribute xml_node::append_copy(const xml_attribute& proto) { if (!proto) return xml_attribute(); @@ -6470,6 +6486,22 @@ namespace pugi } #endif + PUGI_IMPL_FN xml_node xml_node::ensure_child(const char_t* name_) + { + xml_node result = child(name_); + + return result ? result : append_child(name_); + } + +#ifdef PUGIXML_HAS_STRING_VIEW + PUGI_IMPL_FN xml_node xml_node::ensure_child(string_view_t name_) + { + xml_node result = child(name_); + + return result ? result : append_child(name_); + } +#endif + PUGI_IMPL_FN xml_node xml_node::append_copy(const xml_node& proto) { xml_node_type type_ = proto.type(); diff --git a/src/pugixml.hpp b/src/pugixml.hpp index 8ebd32878..a6eca725d 100644 --- a/src/pugixml.hpp +++ b/src/pugixml.hpp @@ -625,6 +625,12 @@ namespace pugi xml_attribute insert_attribute_before(string_view_t name, const xml_attribute& attr); #endif + // Get attribute with specified name, adding one if it does not exist. Returns the existing or added attribute, or empty attribute on errors. + xml_attribute ensure_attribute(const char_t* name); + #ifdef PUGIXML_HAS_STRING_VIEW + xml_attribute ensure_attribute(string_view_t name); + #endif + // Add a copy of the specified attribute. Returns added attribute, or empty attribute on errors. xml_attribute append_copy(const xml_attribute& proto); xml_attribute prepend_copy(const xml_attribute& proto); @@ -649,6 +655,12 @@ namespace pugi xml_node insert_child_before(string_view_t name, const xml_node& node); #endif + // Get child with specified name, adding one if it does not exist. Returns the existing or added node, or empty node on errors. + xml_node ensure_child(const char_t* name); + #ifdef PUGIXML_HAS_STRING_VIEW + xml_node ensure_child(string_view_t name); + #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); xml_node prepend_copy(const xml_node& proto); diff --git a/tests/test_dom_modify.cpp b/tests/test_dom_modify.cpp index 2b96d9d7b..063b139af 100644 --- a/tests/test_dom_modify.cpp +++ b/tests/test_dom_modify.cpp @@ -379,6 +379,37 @@ TEST_XML(dom_node_append_attribute, "") CHECK_NODE(doc, STR("")); } +TEST_XML(dom_node_ensure_attribute, "") +{ + CHECK(xml_node().ensure_attribute(STR("a")) == xml_attribute()); + CHECK(doc.ensure_attribute(STR("a")) == xml_attribute()); + + xml_node node = doc.child(STR("node")); + + // existing attribute is returned as is + xml_attribute a1 = node.ensure_attribute(STR("a1")); + CHECK(a1 && a1 == node.attribute(STR("a1"))); + CHECK_STRING(a1.value(), STR("v1")); + + // missing attribute is appended + xml_attribute a2 = node.ensure_attribute(STR("a2")); + CHECK(a2 && a2 != a1); + a2 = STR("v2"); + + // ensuring an existing attribute does not add a duplicate + CHECK(node.ensure_attribute(STR("a2")) == a2); + +#ifdef PUGIXML_HAS_STRING_VIEW + xml_attribute a3 = node.child(STR("child")).ensure_attribute(string_view_t(STR("a3"))); +#else + xml_attribute a3 = node.child(STR("child")).ensure_attribute(STR("a3")); +#endif + CHECK(a3 && a3 != a1 && a3 != a2); + a3 = STR("v3"); + + CHECK_NODE(doc, STR("")); +} + TEST_XML(dom_node_insert_attribute_after, "") { CHECK(xml_node().insert_attribute_after(STR("a"), xml_attribute()) == xml_attribute()); @@ -838,6 +869,34 @@ TEST_XML(dom_node_append_child_name, "foo") CHECK_NODE(doc, STR("foo")); } +TEST_XML(dom_node_ensure_child, "foo") +{ + CHECK(xml_node().ensure_child(STR("")) == xml_node()); + CHECK(doc.child(STR("node")).first_child().ensure_child(STR("n")) == xml_node()); + + xml_node node = doc.child(STR("node")); + + // existing child is returned as is + xml_node child = node.ensure_child(STR("child")); + CHECK(child && child == node.child(STR("child"))); + + // missing child is appended + xml_node n1 = node.ensure_child(STR("n1")); + CHECK(n1 && n1 != child); + + // ensuring an existing child does not add a duplicate + CHECK(node.ensure_child(STR("child")) == child); + +#ifdef PUGIXML_HAS_STRING_VIEW + xml_node n2 = doc.ensure_child(string_view_t(STR("n2"))); +#else + xml_node n2 = doc.ensure_child(STR("n2")); +#endif + CHECK(n2 && n2 != child && n2 != n1); + + CHECK_NODE(doc, STR("foo")); +} + TEST_XML(dom_node_insert_child_after_name, "foo") { CHECK(xml_node().insert_child_after(STR(""), xml_node()) == xml_node());