Skip to main content

serde_xpath/
xpath.rs

1use roxmltree::Node;
2
3#[derive(Debug, Clone)]
4pub enum XPathStep {
5    Element(String),
6    Attribute(String),
7}
8
9#[derive(Debug, Clone)]
10pub struct XPath {
11    pub steps: Vec<XPathStep>,
12}
13
14impl XPath {
15    pub fn parse(path: &str) -> Result<Self, String> {
16        let mut steps = Vec::new();
17
18        if path.is_empty() {
19            return Ok(XPath { steps });
20        }
21
22        let path = path.strip_prefix('/').unwrap_or(path);
23
24        if path.is_empty() {
25            return Ok(XPath { steps });
26        }
27
28        for part in path.split('/') {
29            if part.is_empty() {
30                continue;
31            }
32
33            if let Some(attr_name) = part.strip_prefix('@') {
34                steps.push(XPathStep::Attribute(attr_name.to_string()));
35            } else {
36                steps.push(XPathStep::Element(part.to_string()));
37            }
38        }
39
40        Ok(XPath { steps })
41    }
42
43    pub fn evaluate_single<'a, 'input>(
44        &self,
45        node: Node<'a, 'input>,
46    ) -> Option<XPathResult<'a, 'input>> {
47        let mut current = node;
48
49        for (i, step) in self.steps.iter().enumerate() {
50            match step {
51                XPathStep::Element(name) => {
52                    // For the first step, check if the current node matches
53                    // (handles absolute paths like /root/child where node is root)
54                    if i == 0 && current.is_element() && current.tag_name().name() == name {
55                        // Current node matches, continue to next step
56                        continue;
57                    }
58                    current = current
59                        .children()
60                        .find(|n| n.is_element() && n.tag_name().name() == name)?;
61                }
62                XPathStep::Attribute(name) => {
63                    let value = current.attribute(name.as_str())?;
64                    return Some(XPathResult::Attribute(value));
65                }
66            }
67        }
68
69        Some(XPathResult::Node(current))
70    }
71
72    pub fn evaluate_all<'a, 'input>(
73        &self,
74        node: Node<'a, 'input>,
75    ) -> Vec<XPathResult<'a, 'input>> {
76        if self.steps.is_empty() {
77            return vec![XPathResult::Node(node)];
78        }
79
80        let mut current_nodes = vec![node];
81
82        for (i, step) in self.steps.iter().enumerate() {
83            match step {
84                XPathStep::Element(name) => {
85                    // For the first step, check if the current node matches
86                    if i == 0 && current_nodes.len() == 1 {
87                        let n = current_nodes[0];
88                        if n.is_element() && n.tag_name().name() == name {
89                            continue;
90                        }
91                    }
92
93                    let is_last = i == self.steps.len() - 1;
94                    if is_last {
95                        // For the last element step, collect all matching children
96                        let mut all_matches = Vec::new();
97                        for n in &current_nodes {
98                            for child in n.children() {
99                                if child.is_element() && child.tag_name().name() == name {
100                                    all_matches.push(child);
101                                }
102                            }
103                        }
104                        return all_matches.into_iter().map(XPathResult::Node).collect();
105                    } else {
106                        // For intermediate steps, find first matching child
107                        let mut next_nodes = Vec::new();
108                        for n in &current_nodes {
109                            if let Some(child) = n.children().find(|c| c.is_element() && c.tag_name().name() == name) {
110                                next_nodes.push(child);
111                            }
112                        }
113                        current_nodes = next_nodes;
114                        if current_nodes.is_empty() {
115                            return Vec::new();
116                        }
117                    }
118                }
119                XPathStep::Attribute(name) => {
120                    let mut results = Vec::new();
121                    for n in &current_nodes {
122                        if let Some(value) = n.attribute(name.as_str()) {
123                            results.push(XPathResult::Attribute(value));
124                        }
125                    }
126                    return results;
127                }
128            }
129        }
130
131        current_nodes.into_iter().map(XPathResult::Node).collect()
132    }
133}
134
135#[derive(Debug, Clone)]
136pub enum XPathResult<'a, 'input> {
137    Node(Node<'a, 'input>),
138    Attribute(&'a str),
139}
140
141impl<'a, 'input> XPathResult<'a, 'input> {
142    pub fn as_node(&self) -> Option<Node<'a, 'input>> {
143        match self {
144            XPathResult::Node(n) => Some(*n),
145            _ => None,
146        }
147    }
148
149    pub fn as_str(&self) -> Option<&'a str> {
150        match self {
151            XPathResult::Attribute(s) => Some(s),
152            _ => None,
153        }
154    }
155
156    pub fn text(&self) -> Option<&'a str> {
157        match self {
158            XPathResult::Node(n) => {
159                n.children()
160                    .find(|c| c.is_text())
161                    .and_then(|c| c.text())
162            }
163            XPathResult::Attribute(s) => Some(s),
164        }
165    }
166}