xsd_schema/xpath/node_ops.rs
1//! Node operations for XPath evaluation.
2//!
3//! This module implements XPath 2.0 node comparison and navigation operations:
4//! - `is` (node identity)
5//! - `<<` (precedes in document order)
6//! - `>>` (follows in document order)
7//! - Root navigation
8
9use super::error::XPathError;
10use super::{DomNavigator, XmlNodeOrder};
11
12/// Check if two nodes are identical (same node).
13///
14/// This implements the XPath `is` operator.
15///
16/// # Arguments
17///
18/// * `a` - First node
19/// * `b` - Second node
20///
21/// # Returns
22///
23/// `true` if the nodes are the same node, `false` otherwise.
24pub fn same_node<N: DomNavigator>(a: &N, b: &N) -> bool {
25 match a.compare_position(b) {
26 XmlNodeOrder::Same => true,
27 XmlNodeOrder::Unknown => b.compare_position(a) == XmlNodeOrder::Same,
28 _ => false,
29 }
30}
31
32/// Check if node `a` precedes node `b` in document order.
33///
34/// This implements the XPath `<<` operator.
35///
36/// # Arguments
37///
38/// * `a` - First node
39/// * `b` - Second node
40///
41/// # Returns
42///
43/// `true` if `a` precedes `b` in document order.
44pub fn preceding_node<N: DomNavigator>(a: &N, b: &N) -> bool {
45 a.compare_position(b) == XmlNodeOrder::Before
46}
47
48/// Check if node `a` follows node `b` in document order.
49///
50/// This implements the XPath `>>` operator.
51///
52/// # Arguments
53///
54/// * `a` - First node
55/// * `b` - Second node
56///
57/// # Returns
58///
59/// `true` if `a` follows `b` in document order.
60pub fn following_node<N: DomNavigator>(a: &N, b: &N) -> bool {
61 a.compare_position(b) == XmlNodeOrder::After
62}
63
64/// Get the root node from a given node.
65///
66/// Navigates to the document root.
67///
68/// # Arguments
69///
70/// * `node` - The starting node
71///
72/// # Returns
73///
74/// A clone of the navigator positioned at the root.
75pub fn get_root<N: DomNavigator>(node: &N) -> N {
76 let mut nav = node.clone();
77 nav.move_to_root();
78 nav
79}
80
81/// Get the context node from an optional context.
82///
83/// # Arguments
84///
85/// * `context` - Optional context node
86///
87/// # Returns
88///
89/// * `Ok(N)` - The context node
90/// * `Err(XPathError)` - XPDY0002 if context is undefined
91pub fn context_node<N: DomNavigator>(context: Option<&N>) -> Result<N, XPathError> {
92 context.cloned().ok_or_else(XPathError::context_undefined)
93}
94
95/// Compare two nodes by document order.
96///
97/// # Returns
98///
99/// * `std::cmp::Ordering::Less` if `a` precedes `b`
100/// * `std::cmp::Ordering::Equal` if they are the same node
101/// * `std::cmp::Ordering::Greater` if `a` follows `b`
102pub fn compare_document_order<N: DomNavigator>(a: &N, b: &N) -> std::cmp::Ordering {
103 match a.compare_position(b) {
104 XmlNodeOrder::Before => std::cmp::Ordering::Less,
105 XmlNodeOrder::Same => std::cmp::Ordering::Equal,
106 XmlNodeOrder::After => std::cmp::Ordering::Greater,
107 XmlNodeOrder::Unknown => {
108 // Try reverse comparison
109 match b.compare_position(a) {
110 XmlNodeOrder::Before => std::cmp::Ordering::Greater,
111 XmlNodeOrder::After => std::cmp::Ordering::Less,
112 _ => std::cmp::Ordering::Equal,
113 }
114 }
115 }
116}
117
118/// Check if a node is the document root.
119pub fn is_root<N: DomNavigator>(node: &N) -> bool {
120 let root = get_root(node);
121 same_node(node, &root)
122}
123
124/// Check if node `ancestor` is an ancestor of node `descendant`.
125pub fn is_ancestor<N: DomNavigator>(ancestor: &N, descendant: &N) -> bool {
126 let mut current = descendant.clone();
127 while current.move_to_parent() {
128 if same_node(¤t, ancestor) {
129 return true;
130 }
131 }
132 false
133}
134
135/// Check if node `descendant` is a descendant of node `ancestor`.
136pub fn is_descendant<N: DomNavigator>(descendant: &N, ancestor: &N) -> bool {
137 is_ancestor(ancestor, descendant)
138}
139
140/// Check if two nodes are siblings (same parent).
141pub fn are_siblings<N: DomNavigator>(a: &N, b: &N) -> bool {
142 let mut a_parent = a.clone();
143 let mut b_parent = b.clone();
144
145 if !a_parent.move_to_parent() || !b_parent.move_to_parent() {
146 return false;
147 }
148
149 same_node(&a_parent, &b_parent)
150}
151
152#[cfg(test)]
153mod tests {
154 // Tests would require a DomNavigator implementation
155 // The roxmltree adapter provides this for integration testing
156}