1use std::collections::HashMap;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct DomNodeId(pub usize);
8
9#[derive(Debug, Clone)]
11pub enum DomNodeKind {
12 Document,
14 Element {
16 tag: String,
17 attributes: HashMap<String, String>,
18 namespace: String,
19 },
20 Text(String),
22 Comment(String),
24}
25
26#[derive(Debug, Clone)]
28pub struct DomNode {
29 pub id: DomNodeId,
30 pub kind: DomNodeKind,
31 pub children: Vec<DomNodeId>,
32 pub parent: Option<DomNodeId>,
33}
34
35impl DomNode {
36 pub fn tag_name(&self) -> Option<&str> {
37 match &self.kind {
38 DomNodeKind::Element { tag, .. } => Some(tag),
39 _ => None,
40 }
41 }
42
43 pub fn get_attribute(&self, name: &str) -> Option<&str> {
44 match &self.kind {
45 DomNodeKind::Element { attributes, .. } => attributes.get(name).map(|s| s.as_str()),
46 _ => None,
47 }
48 }
49
50 pub fn text_content(&self) -> Option<&str> {
51 match &self.kind {
52 DomNodeKind::Text(text) => Some(text),
53 _ => None,
54 }
55 }
56
57 pub fn is_element(&self) -> bool {
58 matches!(self.kind, DomNodeKind::Element { .. })
59 }
60}
61
62#[derive(Debug, Clone)]
64pub struct DomTree {
65 nodes: Vec<DomNode>,
66 root: DomNodeId,
67}
68
69impl DomTree {
70 pub fn new() -> Self {
71 let root_node = DomNode {
72 id: DomNodeId(0),
73 kind: DomNodeKind::Document,
74 children: Vec::new(),
75 parent: None,
76 };
77 Self {
78 nodes: vec![root_node],
79 root: DomNodeId(0),
80 }
81 }
82
83 pub fn root(&self) -> DomNodeId {
84 self.root
85 }
86
87 pub fn add_element(
88 &mut self,
89 parent: DomNodeId,
90 tag: &str,
91 attributes: HashMap<String, String>,
92 ) -> DomNodeId {
93 let id = DomNodeId(self.nodes.len());
94 self.nodes.push(DomNode {
95 id,
96 kind: DomNodeKind::Element {
97 tag: tag.to_string(),
98 attributes,
99 namespace: "http://www.w3.org/1999/xhtml".to_string(),
100 },
101 children: Vec::new(),
102 parent: Some(parent),
103 });
104 self.nodes[parent.0].children.push(id);
105 id
106 }
107
108 pub fn add_text(&mut self, parent: DomNodeId, text: &str) -> DomNodeId {
109 let id = DomNodeId(self.nodes.len());
110 self.nodes.push(DomNode {
111 id,
112 kind: DomNodeKind::Text(text.to_string()),
113 children: Vec::new(),
114 parent: Some(parent),
115 });
116 self.nodes[parent.0].children.push(id);
117 id
118 }
119
120 pub fn node(&self, id: DomNodeId) -> &DomNode {
121 &self.nodes[id.0]
122 }
123
124 pub fn children(&self, id: DomNodeId) -> &[DomNodeId] {
125 &self.nodes[id.0].children
126 }
127
128 pub fn parent(&self, id: DomNodeId) -> Option<DomNodeId> {
129 self.nodes[id.0].parent
130 }
131
132 pub fn len(&self) -> usize {
133 self.nodes.len()
134 }
135
136 pub fn is_empty(&self) -> bool {
137 self.nodes.is_empty()
138 }
139
140 pub fn find_body(&self) -> Option<DomNodeId> {
142 self.find_element_by_tag("body")
143 }
144
145 pub fn find_element_by_tag(&self, tag: &str) -> Option<DomNodeId> {
147 self.find_element_recursive(self.root, tag)
148 }
149
150 fn find_element_recursive(&self, node: DomNodeId, tag: &str) -> Option<DomNodeId> {
151 let n = &self.nodes[node.0];
152 if let Some(t) = n.tag_name() {
153 if t.eq_ignore_ascii_case(tag) {
154 return Some(node);
155 }
156 }
157 for &child in &n.children {
158 if let Some(found) = self.find_element_recursive(child, tag) {
159 return Some(found);
160 }
161 }
162 None
163 }
164
165 pub fn find_element_by_id(&self, id: &str) -> Option<DomNodeId> {
167 self.find_by_attr_recursive(self.root, "id", id)
168 }
169
170 fn find_by_attr_recursive(
171 &self,
172 node: DomNodeId,
173 attr: &str,
174 value: &str,
175 ) -> Option<DomNodeId> {
176 let n = &self.nodes[node.0];
177 if let Some(v) = n.get_attribute(attr) {
178 if v == value {
179 return Some(node);
180 }
181 }
182 for &child in &n.children {
183 if let Some(found) = self.find_by_attr_recursive(child, attr, value) {
184 return Some(found);
185 }
186 }
187 None
188 }
189
190 pub fn iter_dfs(&self) -> DfsIter<'_> {
192 DfsIter {
193 tree: self,
194 stack: vec![self.root],
195 }
196 }
197}
198
199impl Default for DomTree {
200 fn default() -> Self {
201 Self::new()
202 }
203}
204
205pub struct DfsIter<'a> {
207 tree: &'a DomTree,
208 stack: Vec<DomNodeId>,
209}
210
211impl<'a> Iterator for DfsIter<'a> {
212 type Item = DomNodeId;
213
214 fn next(&mut self) -> Option<DomNodeId> {
215 let id = self.stack.pop()?;
216 let node = self.tree.node(id);
217 for child in node.children.iter().rev() {
219 self.stack.push(*child);
220 }
221 Some(id)
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_dom_tree_build() {
231 let mut tree = DomTree::new();
232 let root = tree.root();
233 let body = tree.add_element(root, "body", HashMap::new());
234 let div = tree.add_element(body, "div", HashMap::new());
235 tree.add_text(div, "Hello");
236
237 assert_eq!(tree.len(), 4);
238 assert_eq!(tree.children(root).len(), 1);
239 assert_eq!(tree.find_body(), Some(body));
240 }
241
242 #[test]
243 fn test_find_by_id() {
244 let mut tree = DomTree::new();
245 let root = tree.root();
246 let mut attrs = HashMap::new();
247 attrs.insert("id".to_string(), "target".to_string());
248 let el = tree.add_element(root, "div", attrs);
249
250 assert_eq!(tree.find_element_by_id("target"), Some(el));
251 assert_eq!(tree.find_element_by_id("missing"), None);
252 }
253}