diff --git a/CHANGELOG.md b/CHANGELOG.md index 22f291627..32c7a121b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added -* Node methods: `get_property_no_ns` (alias: `get_attribute_no_ns`), `get_properties_ns` (alias: `get_attributes_ns`) +* Node methods: `get_property_no_ns` (alias: `get_attribute_no_ns`), `get_properties_ns` (alias: `get_attributes_ns`), `has_property_no_ns` (alias: `has_attribute_no_ns`), `remove_property_no_ns` (alias: `remove_attribute_no_ns`), `get_property_node_ns` (alias: `get_attribute_node_ns`), `get_property_node_no_ns` (alias: `get_attribute_node_no_ns`) * Added implementations of `Hash`, `PartialEq` and `Eq` traits for `Namespace` ## [0.3.3] 2023-17-07 diff --git a/src/readonly/tree.rs b/src/readonly/tree.rs index eb174c9ed..1223060e0 100644 --- a/src/readonly/tree.rs +++ b/src/readonly/tree.rs @@ -275,6 +275,22 @@ impl RoNode { } } + /// Return an attribute in a namespace `ns` as a `Node` of type AttributeNode + pub fn get_property_node_ns(self, name: &str, ns: &str) -> Option { + let c_name = CString::new(name).unwrap(); + let c_ns = CString::new(ns).unwrap(); + let attr_node = + unsafe { xmlHasNsProp(self.0, c_name.as_bytes().as_ptr(), c_ns.as_bytes().as_ptr()) }; + self.ptr_as_option(attr_node as xmlNodePtr) + } + + /// Return an attribute with no namespace as a `Node` of type AttributeNode + pub fn get_property_node_no_ns(self, name: &str) -> Option { + let c_name = CString::new(name).unwrap(); + let attr_node = unsafe { xmlHasNsProp(self.0, c_name.as_bytes().as_ptr(), ptr::null()) }; + self.ptr_as_option(attr_node as xmlNodePtr) + } + /// Alias for get_property pub fn get_attribute(self, name: &str) -> Option { self.get_property(name) @@ -295,6 +311,16 @@ impl RoNode { self.get_property_node(name) } + /// Alias for get_property_node_ns + pub fn get_attribute_node_ns(self, name: &str, ns: &str) -> Option { + self.get_property_node_ns(name, ns) + } + + /// Alias for get_property_node_no_ns + pub fn get_attribute_node_no_ns(self, name: &str) -> Option { + self.get_property_node_no_ns(name) + } + /// Get a copy of the attributes of this node pub fn get_properties(self) -> HashMap { let mut attributes = HashMap::new(); @@ -354,6 +380,7 @@ impl RoNode { let value_ptr = unsafe { xmlHasProp(self.0, c_name.as_bytes().as_ptr()) }; !value_ptr.is_null() } + /// Check if property `name` in namespace `ns` exists pub fn has_property_ns(self, name: &str, ns: &str) -> bool { let c_name = CString::new(name).unwrap(); @@ -362,15 +389,29 @@ impl RoNode { unsafe { xmlHasNsProp(self.0, c_name.as_bytes().as_ptr(), c_ns.as_bytes().as_ptr()) }; !value_ptr.is_null() } + + /// Check if property `name` with no namespace exists + pub fn has_property_no_ns(self, name: &str) -> bool { + let c_name = CString::new(name).unwrap(); + let value_ptr = unsafe { xmlHasNsProp(self.0, c_name.as_bytes().as_ptr(), ptr::null()) }; + !value_ptr.is_null() + } + /// Alias for has_property pub fn has_attribute(self, name: &str) -> bool { self.has_property(name) } + /// Alias for has_property_ns pub fn has_attribute_ns(self, name: &str, ns: &str) -> bool { self.has_property_ns(name, ns) } + /// Alias for has_property_no_ns + pub fn has_attribute_no_ns(self, name: &str) -> bool { + self.has_property_no_ns(name) + } + /// Gets the active namespace associated of this node pub fn get_namespace(self) -> Option { let ns_ptr = xmlNodeNs(self.0); diff --git a/src/tree/node.rs b/src/tree/node.rs index 93306698c..09a7b9f2a 100644 --- a/src/tree/node.rs +++ b/src/tree/node.rs @@ -481,12 +481,35 @@ impl Node { } } + /// Return an attribute in a namespace `ns` as a `Node` of type AttributeNode + pub fn get_property_node_ns(&self, name: &str, ns: &str) -> Option { + let c_name = CString::new(name).unwrap(); + let c_ns = CString::new(ns).unwrap(); + let attr_node = unsafe { + xmlHasNsProp( + self.node_ptr(), + c_name.as_bytes().as_ptr(), + c_ns.as_bytes().as_ptr(), + ) + }; + self.ptr_as_option(attr_node as xmlNodePtr) + } + + /// Return an attribute with no namespace as a `Node` of type AttributeNode + pub fn get_property_node_no_ns(&self, name: &str) -> Option { + let c_name = CString::new(name).unwrap(); + let attr_node = + unsafe { xmlHasNsProp(self.node_ptr(), c_name.as_bytes().as_ptr(), ptr::null()) }; + self.ptr_as_option(attr_node as xmlNodePtr) + } + /// Check if a property has been defined, without allocating its value pub fn has_property(&self, name: &str) -> bool { let c_name = CString::new(name).unwrap(); let value_ptr = unsafe { xmlHasProp(self.node_ptr(), c_name.as_bytes().as_ptr()) }; !value_ptr.is_null() } + /// Check if property `name` in namespace `ns` exists pub fn has_property_ns(&self, name: &str, ns: &str) -> bool { let c_name = CString::new(name).unwrap(); @@ -500,6 +523,15 @@ impl Node { }; !value_ptr.is_null() } + + /// Check if property `name` with no namespace exists + pub fn has_property_no_ns(&self, name: &str) -> bool { + let c_name = CString::new(name).unwrap(); + let value_ptr = + unsafe { xmlHasNsProp(self.node_ptr(), c_name.as_bytes().as_ptr(), ptr::null()) }; + !value_ptr.is_null() + } + /// Alias for has_property pub fn has_attribute(&self, name: &str) -> bool { self.has_property(name) @@ -509,6 +541,11 @@ impl Node { self.has_property_ns(name, ns) } + /// Alias for has_property_no_ns + pub fn has_attribute_no_ns(&self, name: &str) -> bool { + self.has_property_no_ns(name) + } + /// Sets the value of property `name` to `value` pub fn set_property( &mut self, @@ -601,6 +638,33 @@ impl Node { } } + /// Removes the property of given `name` with no namespace + pub fn remove_property_no_ns(&mut self, name: &str) -> Result<(), Box> { + let c_name = CString::new(name).unwrap(); + let attr_node = unsafe { + xmlHasNsProp( + self.node_ptr_mut()?, + c_name.as_bytes().as_ptr(), + ptr::null(), + ) + }; + if !attr_node.is_null() { + let remove_prop_status = unsafe { xmlRemoveProp(attr_node) }; + if remove_prop_status == 0 { + Ok(()) + } else { + // Propagate libxml2 failure to remove + Err(From::from(format!( + "libxml2 failed to remove property with status: {:?}", + remove_prop_status + ))) + } + } else { + // silently no-op if asked to remove a property which is not present + Ok(()) + } + } + /// Alias for get_property pub fn get_attribute(&self, name: &str) -> Option { self.get_property(name) @@ -621,6 +685,16 @@ impl Node { self.get_property_node(name) } + /// Alias for get_property_node_ns + pub fn get_attribute_node_ns(&self, name: &str, ns: &str) -> Option { + self.get_property_node_ns(name, ns) + } + + /// Alias for get_property_node_no_ns + pub fn get_attribute_node_no_ns(&self, name: &str) -> Option { + self.get_property_node_no_ns(name) + } + /// Alias for set_property pub fn set_attribute( &mut self, @@ -653,6 +727,11 @@ impl Node { self.remove_property_ns(name, ns) } + /// Alias for remove_property_no_ns + pub fn remove_attribute_no_ns(&mut self, name: &str) -> Result<(), Box> { + self.remove_property_no_ns(name) + } + /// Get a copy of the attributes of this node pub fn get_properties(&self) -> HashMap { let mut attributes = HashMap::new(); diff --git a/tests/tree_tests.rs b/tests/tree_tests.rs index 52fc03d81..59ec8c0bc 100644 --- a/tests/tree_tests.rs +++ b/tests/tree_tests.rs @@ -144,8 +144,12 @@ fn node_attributes_ns_accessor() { ); // Has - // TODO include this when `has_attribute_no_ns` is implemented - // assert!(child.has_attribute("attribute")); + assert!(child.has_attribute("attribute")); + assert!(child.has_attribute_no_ns("attribute")); + assert!(child.has_attribute_ns("attribute", "http://www.example.com/myns"),); + assert!(child.has_attribute("attr")); + assert!(!child.has_attribute_no_ns("attr")); + assert!(child.has_attribute_ns("attr", "http://www.example.com/myns")); // Get assert_eq!( @@ -162,13 +166,18 @@ fn node_attributes_ns_accessor() { ); // Get as node - // TODO include this when `get_attribute_node_ns` and - // `get_attribute_node_no_ns` are implemented - // let attr_node_opt = child.get_attribute_node("attribute"); - // assert!(attr_node_opt.is_some()); - // let attr_node = attr_node_opt.unwrap(); - // assert_eq!(attr_node.get_name(), "attribute"); - // assert_eq!(attr_node.get_type(), Some(NodeType::AttributeNode)); + let attr_node_opt = child.get_attribute_node_no_ns("attribute"); + assert!(attr_node_opt.is_some()); + let attr_node = attr_node_opt.unwrap(); + assert_eq!(attr_node.get_name(), "attribute"); + assert_eq!(attr_node.get_type(), Some(NodeType::AttributeNode)); + let attr_node_opt = child.get_attribute_node_no_ns("attr"); + assert!(attr_node_opt.is_none()); + let attr_node_opt = child.get_attribute_node_ns("attr", "http://www.example.com/myns"); + assert!(attr_node_opt.is_some()); + let attr_node = attr_node_opt.unwrap(); + assert_eq!(attr_node.get_name(), "attr"); + assert_eq!(attr_node.get_type(), Some(NodeType::AttributeNode)); // Set assert!(child.set_attribute("attribute", "setter_value").is_ok()); @@ -184,13 +193,13 @@ fn node_attributes_ns_accessor() { Some("setter_value".to_string()) ); // Remove - // TODO include this when `remove_attribute_no_ns` is implemented - // assert!(child.remove_attribute("attribute").is_ok()); - // assert_eq!(child.get_attribute("attribute"), None); - // assert_eq!(child.has_attribute("attribute"), false); + assert!(child.has_attribute_no_ns("attribute")); + assert!(child.remove_attribute_no_ns("attribute").is_ok()); + assert_eq!(child.get_attribute_no_ns("attribute"), None); + assert!(!child.has_attribute_no_ns("attribute")); // Recount - // let attributes = child.get_attributes_ns(); - // assert_eq!(attributes.len(), 2); + let attributes = child.get_attributes_ns(); + assert_eq!(attributes.len(), 2); } #[test] @@ -321,13 +330,9 @@ fn attribute_no_namespace() { assert!(foo_no_ns_attr.is_some()); assert_eq!(foo_no_ns_attr.unwrap(), "no_ns"); - // TODO: include this when `remove_attribute_no_ns` is implemented - // It's not possible use remove_attribute here as it removes the first - // attribute found with the local name regardless of the namespace; here it - // removes the attribute with the namespace - // assert!(element.remove_attribute_no_ns("foo").is_ok()); - // let foo_no_ns_attr = element.get_attribute_no_ns("foo"); - // assert!(foo_no_ns_attr.is_none()); + assert!(element.remove_attribute_no_ns("foo").is_ok()); + let foo_no_ns_attr = element.get_attribute_no_ns("foo"); + assert!(foo_no_ns_attr.is_none()); assert!(element.set_attribute("bar", "bar").is_ok()); let bar_no_ns_attr = element.get_attribute_no_ns("bar");