Skip to content

Commit 0eb7135

Browse files
committed
add Context::findvalues, Node::findvalues
1 parent d2ac9e0 commit 0eb7135

File tree

5 files changed

+92
-3
lines changed

5 files changed

+92
-3
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
# Change Log
22

3-
## [0.3.2] (in development)
3+
## [0.3.3] (in development)
4+
5+
## [0.3.2] 2023-07-05
6+
7+
### Added
8+
9+
* XPath: `Context::findvalues`, with optional node-bound evaluation, obtaining `String` values.
10+
11+
* `Node::findvalues` method for direct XPath search obtaining `String` values, without first explicitly instantiating a `Context`. Reusing a `Context` remains more efficient.
412

513
## [0.3.1] 2022-26-03
614

src/tree/node.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,12 @@ impl Node {
929929
context.findnodes(xpath, Some(self))
930930
}
931931

932+
/// find String values via xpath, at a specified node or the document root
933+
pub fn findvalues(&self, xpath: &str) -> Result<Vec<String>, ()> {
934+
let mut context = Context::from_node(self)?;
935+
context.findvalues(xpath, Some(self))
936+
}
937+
932938
/// replace a `self`'s `old` child node with a `new` node in the same position
933939
/// borrowed from Perl's XML::LibXML
934940
pub fn replace_child_node(

src/xpath.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,16 @@ impl Context {
162162
Ok(evaluated.get_nodes_as_vec())
163163
}
164164

165+
/// find literal values via xpath, at a specified node or the document root
166+
pub fn findvalues(&mut self, xpath: &str, node_opt: Option<&Node>) -> Result<Vec<String>, ()> {
167+
let evaluated = if let Some(node) = node_opt {
168+
self.node_evaluate(xpath, node)?
169+
} else {
170+
self.evaluate(xpath)?
171+
};
172+
Ok(evaluated.get_nodes_as_str())
173+
}
174+
165175
/// find a literal value via xpath, at a specified node or the document root
166176
pub fn findvalue(&mut self, xpath: &str, node_opt: Option<&Node>) -> Result<String, ()> {
167177
let evaluated = if let Some(node) = node_opt {
@@ -199,7 +209,7 @@ impl Object {
199209
v as usize
200210
}
201211

202-
/// returns the result set as a vector of node references
212+
/// returns the result set as a vector of `Node` objects
203213
pub fn get_nodes_as_vec(&self) -> Vec<Node> {
204214
let n = self.get_number_of_nodes();
205215
let mut vec: Vec<Node> = Vec::with_capacity(n);
@@ -218,7 +228,7 @@ impl Object {
218228
vec
219229
}
220230

221-
/// returns the result set as a vector of node references
231+
/// returns the result set as a vector of `RoNode` objects
222232
pub fn get_readonly_nodes_as_vec(&self) -> Vec<RoNode> {
223233
let n = self.get_number_of_nodes();
224234
let mut vec: Vec<RoNode> = Vec::with_capacity(n);
@@ -235,6 +245,31 @@ impl Object {
235245
}
236246
vec
237247
}
248+
249+
/// returns the result set as a vector of Strings
250+
pub fn get_nodes_as_str(&self) -> Vec<String> {
251+
let n = self.get_number_of_nodes();
252+
let mut vec: Vec<String> = Vec::with_capacity(n);
253+
let slice = if n > 0 {
254+
xmlXPathObjectGetNodes(self.ptr, n as size_t)
255+
} else {
256+
Vec::new()
257+
};
258+
for ptr in slice {
259+
if ptr.is_null() {
260+
panic!("rust-libxml: xpath: found null pointer result set");
261+
}
262+
let value_ptr = unsafe { xmlXPathCastNodeToString(ptr) };
263+
let c_value_string = unsafe { CStr::from_ptr(value_ptr as *const c_char) };
264+
let ready_str = c_value_string.to_string_lossy().into_owned();
265+
unsafe {
266+
libc::free(value_ptr as *mut c_void);
267+
}
268+
vec.push(ready_str);
269+
}
270+
vec
271+
}
272+
238273
}
239274

240275
impl fmt::Display for Object {

tests/resources/ids.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0"?>
2+
<document>
3+
<test>
4+
<empty/>
5+
</test>
6+
<test>
7+
<p xml:id="start">Hello</p>
8+
<deeper>
9+
<p xml:id="mid"> </p>
10+
</deeper>
11+
<deeper>
12+
<still>
13+
<p xml:id="end">World!</p>
14+
</still>
15+
</deeper>
16+
</test>
17+
</document>

tests/xpath_tests.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,29 @@ fn cleanup_safely_unlinked_xpath_nodes() {
193193
assert!(true, "Drops went OK.");
194194
}
195195

196+
#[test]
197+
fn xpath_find_string_values() {
198+
let parser = Parser::default();
199+
let doc_result = parser.parse_file("tests/resources/ids.xml");
200+
assert!(doc_result.is_ok());
201+
let doc = doc_result.unwrap();
202+
let mut xpath = libxml::xpath::Context::new(&doc).unwrap();
203+
if let Some(root) = doc.get_root_element() {
204+
let tests = root.get_child_elements();
205+
let empty_test = &tests[0];
206+
let ids_test = &tests[1];
207+
let empty_values = xpath.findvalues(".//@xml:id", Some(empty_test));
208+
assert_eq!(empty_values, Ok(Vec::new()));
209+
let ids_values = xpath.findvalues(".//@xml:id", Some(ids_test));
210+
let expected_ids = Ok(vec![String::from("start"),String::from("mid"),String::from("end")]);
211+
assert_eq!(ids_values, expected_ids);
212+
let node_ids_values = ids_test.findvalues(".//@xml:id");
213+
assert_eq!(node_ids_values, expected_ids);
214+
} else {
215+
panic!("Document fails to obtain root!");
216+
}
217+
}
218+
196219
/// Tests for checking xpath well-formedness
197220
mod compile_tests {
198221
use libxml::xpath::is_well_formed_xpath;

0 commit comments

Comments
 (0)