Skip to content

Commit

Permalink
add Context::findvalues, Node::findvalues
Browse files Browse the repository at this point in the history
  • Loading branch information
dginev committed Mar 7, 2023
1 parent d2ac9e0 commit 0eb7135
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 3 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
6 changes: 6 additions & 0 deletions src/tree/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<String>, ()> {
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(
Expand Down
39 changes: 37 additions & 2 deletions src/xpath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<String>, ()> {
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<String, ()> {
let evaluated = if let Some(node) = node_opt {
Expand Down Expand Up @@ -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<Node> {
let n = self.get_number_of_nodes();
let mut vec: Vec<Node> = Vec::with_capacity(n);
Expand All @@ -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<RoNode> {
let n = self.get_number_of_nodes();
let mut vec: Vec<RoNode> = Vec::with_capacity(n);
Expand All @@ -235,6 +245,31 @@ impl Object {
}
vec
}

/// returns the result set as a vector of Strings
pub fn get_nodes_as_str(&self) -> Vec<String> {
let n = self.get_number_of_nodes();
let mut vec: Vec<String> = 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 {
Expand Down
17 changes: 17 additions & 0 deletions tests/resources/ids.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<document>
<test>
<empty/>
</test>
<test>
<p xml:id="start">Hello</p>
<deeper>
<p xml:id="mid"> </p>
</deeper>
<deeper>
<still>
<p xml:id="end">World!</p>
</still>
</deeper>
</test>
</document>
23 changes: 23 additions & 0 deletions tests/xpath_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 0eb7135

Please sign in to comment.