Skip to main content

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(&current, 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}