diff --git a/CHANGELOG.md b/CHANGELOG.md index d8bca49827..79b75f7096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Change Log -## [0.3.2] (in development) +## [0.3.3] (in development) + +## [0.3.2] 2023-07-05 + +### Added + +* XPath: `Context::findvalues`, with optional node-bound evaluation, obtaining `String` values. + +* `Node::findvalues` method for direct XPath search obtaining `String` values, without first explicitly instantiating a `Context`. Reusing a `Context` remains more efficient. ## [0.3.1] 2022-26-03 diff --git a/src/tree/node.rs b/src/tree/node.rs index 1ffa9e4636..b71b33dcc9 100644 --- a/src/tree/node.rs +++ b/src/tree/node.rs @@ -929,6 +929,12 @@ impl Node { context.findnodes(xpath, Some(self)) } + /// find String values via xpath, at a specified node or the document root + pub fn findvalues(&self, xpath: &str) -> Result, ()> { + let mut context = Context::from_node(self)?; + context.findvalues(xpath, Some(self)) + } + /// replace a `self`'s `old` child node with a `new` node in the same position /// borrowed from Perl's XML::LibXML pub fn replace_child_node( diff --git a/src/xpath.rs b/src/xpath.rs index d9da290372..cb9af40737 100644 --- a/src/xpath.rs +++ b/src/xpath.rs @@ -162,6 +162,16 @@ impl Context { Ok(evaluated.get_nodes_as_vec()) } + /// find literal values via xpath, at a specified node or the document root + pub fn findvalues(&mut self, xpath: &str, node_opt: Option<&Node>) -> Result, ()> { + let evaluated = if let Some(node) = node_opt { + self.node_evaluate(xpath, node)? + } else { + self.evaluate(xpath)? + }; + Ok(evaluated.get_nodes_as_str()) + } + /// find a literal value via xpath, at a specified node or the document root pub fn findvalue(&mut self, xpath: &str, node_opt: Option<&Node>) -> Result { let evaluated = if let Some(node) = node_opt { @@ -199,7 +209,7 @@ impl Object { v as usize } - /// returns the result set as a vector of node references + /// returns the result set as a vector of `Node` objects pub fn get_nodes_as_vec(&self) -> Vec { let n = self.get_number_of_nodes(); let mut vec: Vec = Vec::with_capacity(n); @@ -218,7 +228,7 @@ impl Object { vec } - /// returns the result set as a vector of node references + /// returns the result set as a vector of `RoNode` objects pub fn get_readonly_nodes_as_vec(&self) -> Vec { let n = self.get_number_of_nodes(); let mut vec: Vec = Vec::with_capacity(n); @@ -235,6 +245,31 @@ impl Object { } vec } + + /// returns the result set as a vector of Strings + pub fn get_nodes_as_str(&self) -> Vec { + let n = self.get_number_of_nodes(); + let mut vec: Vec = Vec::with_capacity(n); + let slice = if n > 0 { + xmlXPathObjectGetNodes(self.ptr, n as size_t) + } else { + Vec::new() + }; + for ptr in slice { + if ptr.is_null() { + panic!("rust-libxml: xpath: found null pointer result set"); + } + let value_ptr = unsafe { xmlXPathCastNodeToString(ptr) }; + let c_value_string = unsafe { CStr::from_ptr(value_ptr as *const c_char) }; + let ready_str = c_value_string.to_string_lossy().into_owned(); + unsafe { + libc::free(value_ptr as *mut c_void); + } + vec.push(ready_str); + } + vec + } + } impl fmt::Display for Object { diff --git a/tests/resources/ids.xml b/tests/resources/ids.xml new file mode 100644 index 0000000000..a250d5c5ac --- /dev/null +++ b/tests/resources/ids.xml @@ -0,0 +1,17 @@ + + + + + + +

Hello

+ +

+
+ + +

World!

+
+
+
+
\ No newline at end of file diff --git a/tests/xpath_tests.rs b/tests/xpath_tests.rs index d2b33abd26..8f09cfa55e 100644 --- a/tests/xpath_tests.rs +++ b/tests/xpath_tests.rs @@ -193,6 +193,29 @@ fn cleanup_safely_unlinked_xpath_nodes() { assert!(true, "Drops went OK."); } +#[test] +fn xpath_find_string_values() { + let parser = Parser::default(); + let doc_result = parser.parse_file("tests/resources/ids.xml"); + assert!(doc_result.is_ok()); + let doc = doc_result.unwrap(); + let mut xpath = libxml::xpath::Context::new(&doc).unwrap(); + if let Some(root) = doc.get_root_element() { + let tests = root.get_child_elements(); + let empty_test = &tests[0]; + let ids_test = &tests[1]; + let empty_values = xpath.findvalues(".//@xml:id", Some(empty_test)); + assert_eq!(empty_values, Ok(Vec::new())); + let ids_values = xpath.findvalues(".//@xml:id", Some(ids_test)); + let expected_ids = Ok(vec![String::from("start"),String::from("mid"),String::from("end")]); + assert_eq!(ids_values, expected_ids); + let node_ids_values = ids_test.findvalues(".//@xml:id"); + assert_eq!(node_ids_values, expected_ids); + } else { + panic!("Document fails to obtain root!"); + } +} + /// Tests for checking xpath well-formedness mod compile_tests { use libxml::xpath::is_well_formed_xpath;