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 if i == 0 && current.is_element() && current.tag_name().name() == name {
55 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 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 let mut all_matches = Vec::new();
97 for n in ¤t_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 let mut next_nodes = Vec::new();
108 for n in ¤t_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 ¤t_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}