1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! XPath evaluation API for EditableNode.
use crate::namespace::NamespaceResolver;
use crate::xpath::evaluator::{XPathEvaluator, XPathResult};
use super::super::error::TransformResult;
use super::EditableNode;
use super::reference::EditableNodeRef;
impl EditableNode {
/// Evaluates an XPath expression against this node's subtree.
///
/// This uses the internal `XmlDocument` directly, avoiding the need to
/// serialize to XML string and re-parse for XPath evaluation.
///
/// # Example
///
/// ```rust
/// # use fastxml::transform::EditableNodeBuilder;
/// let mut builder = EditableNodeBuilder::new();
/// builder.start_element("root", None, None, vec![], vec![], vec![]);
/// builder.start_element("child", None, None, vec![("id", "1")], vec![], vec![]);
/// builder.text("Hello");
/// builder.end_element();
/// builder.end_element();
/// let node = builder.build().unwrap();
///
/// let result = node.evaluate_xpath("//child[@id='1']").unwrap();
/// assert_eq!(result.into_nodes().len(), 1);
/// ```
pub fn evaluate_xpath(&self, xpath: &str) -> TransformResult<XPathResult> {
let resolver = self.build_namespace_resolver();
let evaluator = XPathEvaluator::with_resolver(&self.doc, resolver);
let root = self.root_node();
Ok(evaluator.evaluate_from(xpath, &root)?)
}
/// Finds all nodes matching an XPath expression and returns them as `EditableNodeRef`s.
///
/// This is a convenience wrapper around `evaluate_xpath()` that filters
/// to element nodes only.
///
/// # Example
///
/// ```rust
/// # use fastxml::transform::EditableNodeBuilder;
/// let mut builder = EditableNodeBuilder::new();
/// builder.start_element("root", None, None, vec![], vec![], vec![]);
/// builder.start_element("item", None, None, vec![("id", "1")], vec![], vec![]);
/// builder.text("A");
/// builder.end_element();
/// builder.start_element("item", None, None, vec![("id", "2")], vec![], vec![]);
/// builder.text("B");
/// builder.end_element();
/// builder.end_element();
/// let node = builder.build().unwrap();
///
/// let items = node.find_by_xpath("//item").unwrap();
/// assert_eq!(items.len(), 2);
/// assert_eq!(items[0].get_attribute("id"), Some("1".to_string()));
/// ```
pub fn find_by_xpath(&self, xpath: &str) -> TransformResult<Vec<EditableNodeRef<'_>>> {
let result = self.evaluate_xpath(xpath)?;
Ok(result
.into_nodes()
.into_iter()
.map(|node| EditableNodeRef {
node,
doc: &self.doc,
})
.collect())
}
/// Gets an attribute value by namespace URI and local name.
///
/// # Example
///
/// ```rust
/// # use fastxml::transform::EditableNodeBuilder;
/// # use fastxml::namespace::Namespace;
/// let mut builder = EditableNodeBuilder::new();
/// let ns = Namespace::new("gml", "http://www.opengis.net/gml");
/// builder.start_element(
/// "Feature", None, None,
/// vec![("id", "f1")],
/// vec![("id", "gml", "http://www.opengis.net/gml")],
/// vec![ns],
/// );
/// builder.end_element();
/// let node = builder.build().unwrap();
///
/// assert_eq!(
/// node.get_attribute_ns("http://www.opengis.net/gml", "id"),
/// Some("f1".to_string()),
/// );
/// ```
pub fn get_attribute_ns(&self, namespace_uri: &str, local_name: &str) -> Option<String> {
let root = self.root_node();
if let Some((_prefix, uri)) = root.get_attribute_ns_info(local_name) {
if uri == namespace_uri {
return root.get_attribute(local_name);
}
}
None
}
/// Builds a namespace resolver that combines the document's own namespaces
/// with the externally registered namespaces (from StreamTransformer).
pub(crate) fn build_namespace_resolver(&self) -> NamespaceResolver {
let mut resolver = self.doc.namespace_resolver().read().clone();
for (prefix, uri) in &self.namespaces {
resolver.register(prefix, uri);
}
resolver
}
}