1use blitz_dom::{BaseDocument, LocalName, Namespace, NodeId, QualName};
6
7use crate::Result;
8use crate::error::DomError;
9
10pub(crate) fn qual_name(local: &str) -> QualName {
12 QualName::new(None, markup5ever::ns!(html), LocalName::from(local))
13}
14
15pub(crate) fn qual_name_ns(local: &str, ns: &str) -> QualName {
17 QualName::new(None, Namespace::from(ns), LocalName::from(local))
18}
19
20pub fn create_element(doc: &mut BaseDocument, tag: &str) -> Result<NodeId> {
29 let tag = tag.to_ascii_lowercase();
30 Ok(doc.mutate().create_element(qual_name(&tag), Vec::new()))
31}
32
33pub fn create_element_ns(doc: &mut BaseDocument, ns: &str, tag: &str) -> Result<NodeId> {
38 Ok(doc
39 .mutate()
40 .create_element(qual_name_ns(tag, ns), Vec::new()))
41}
42
43pub fn create_text_node(doc: &mut BaseDocument, text: &str) -> Result<NodeId> {
45 Ok(doc.mutate().create_text_node(text))
46}
47
48pub fn create_comment(doc: &mut BaseDocument, text: &str) -> Result<NodeId> {
50 Ok(doc.mutate().create_comment_node(text))
51}
52
53pub fn import_node(doc: &mut BaseDocument, node: NodeId, deep: bool) -> Result<NodeId> {
59 crate::node::clone_node(doc, node, deep)
60}
61
62pub fn document_element(doc: &BaseDocument) -> Result<Option<NodeId>> {
66 Ok(doc.try_root_element().map(|root| root.id))
67}
68
69pub fn body(doc: &BaseDocument) -> Result<Option<NodeId>> {
71 Ok(find_tag(doc, markup5ever::local_name!("body")))
72}
73
74pub fn head(doc: &BaseDocument) -> Result<Option<NodeId>> {
76 Ok(find_tag(doc, markup5ever::local_name!("head")))
77}
78
79fn find_tag(doc: &BaseDocument, tag: LocalName) -> Option<NodeId> {
86 let root = doc.try_root_element()?;
87 if root.data.is_element_with_tag_name(&tag) {
88 return Some(root.id);
89 }
90 root.children
91 .iter()
92 .copied()
93 .find(|child_id| {
94 doc.get_node(*child_id)
95 .is_some_and(|child| child.data.is_element_with_tag_name(&tag))
96 })
97 .or_else(|| {
98 let mut stack = vec![doc.root_node().id];
99 while let Some(node_id) = stack.pop() {
100 let node = doc.get_node(node_id)?;
101 if node.data.is_element_with_tag_name(&tag) {
102 return Some(node_id);
103 }
104 stack.extend(node.children.iter().rev().copied());
105 }
106 None
107 })
108}
109
110pub fn get_element_by_id(doc: &BaseDocument, id: &str) -> Result<Option<NodeId>> {
112 Ok(doc.get_element_by_id(id))
113}
114
115pub fn query_selector(doc: &BaseDocument, selector: &str) -> Result<Option<NodeId>> {
121 doc.query_selector(selector)
122 .map_err(|_| DomError::InvalidSelector(selector.to_owned()))
123}
124
125pub fn query_selector_all(doc: &BaseDocument, selector: &str) -> Result<Vec<NodeId>> {
130 doc.query_selector_all(selector)
131 .map(|matches| matches.into_iter().collect())
132 .map_err(|_| DomError::InvalidSelector(selector.to_owned()))
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use crate::element;
139 use crate::node;
140 use crate::test_support::{bare, skeleton};
141
142 #[test]
143 fn create_element_lowercases_the_tag() {
144 let mut doc = bare();
145 let id = create_element(&mut doc, "DIV").unwrap();
146 assert_eq!(element::tag_name(&doc, id).unwrap(), "DIV");
147 assert_eq!(
148 doc.get_node(id)
149 .and_then(|n| n.element_data())
150 .map(|e| e.name.local.to_string()),
151 Some("div".to_string())
152 );
153 }
154
155 #[test]
156 fn create_element_ns_keeps_the_namespace_and_the_case() {
157 let mut doc = bare();
158 let id =
159 create_element_ns(&mut doc, "http://www.w3.org/2000/svg", "linearGradient").unwrap();
160 let element = doc.get_node(id).unwrap().element_data().unwrap();
161 assert_eq!(element.name.local.to_string(), "linearGradient");
162 assert_eq!(element.name.ns.to_string(), "http://www.w3.org/2000/svg");
163 }
164
165 #[test]
166 fn create_text_node_carries_its_text() {
167 let mut doc = bare();
168 let id = create_text_node(&mut doc, "hello").unwrap();
169 assert_eq!(node::text_content(&doc, id).unwrap(), "hello");
170 }
171
172 #[test]
173 fn create_comment_makes_a_comment_node() {
174 let mut doc = bare();
175 let id = create_comment(&mut doc, "note").unwrap();
176 assert!(matches!(
177 doc.get_node(id).map(|n| &n.data),
178 Some(blitz_dom::NodeData::Comment { .. })
179 ));
180 }
181
182 #[test]
183 fn import_node_copies_the_subtree_when_deep() {
184 let (mut doc, _html, _head, body) = skeleton();
185 let outer = create_element(&mut doc, "div").unwrap();
186 let inner = create_element(&mut doc, "span").unwrap();
187 node::append_child(&mut doc, outer, inner).unwrap();
188 node::append_child(&mut doc, body, outer).unwrap();
189
190 let copy = import_node(&mut doc, outer, true).unwrap();
191 assert_ne!(copy, outer);
192 assert_eq!(node::child_nodes(&doc, copy).unwrap().len(), 1);
193
194 let shallow = import_node(&mut doc, outer, false).unwrap();
195 assert!(node::child_nodes(&doc, shallow).unwrap().is_empty());
196 }
197
198 #[test]
199 fn document_element_is_the_root_element() {
200 let (doc, html, _head, _body) = skeleton();
201 assert_eq!(document_element(&doc).unwrap(), Some(html));
202 assert_eq!(document_element(&bare()).unwrap(), None);
203 }
204
205 #[test]
206 fn body_and_head_are_found_among_the_root_children() {
207 let (doc, _html, head_id, body_id) = skeleton();
208 assert_eq!(body(&doc).unwrap(), Some(body_id));
209 assert_eq!(head(&doc).unwrap(), Some(head_id));
210 }
211
212 #[test]
215 fn body_falls_back_to_a_full_tree_walk() {
216 let (mut doc, html, _head, _body) = skeleton();
217 let wrapper = create_element(&mut doc, "div").unwrap();
218 let nested = create_element(&mut doc, "body").unwrap();
219 node::append_child(&mut doc, wrapper, nested).unwrap();
220 node::append_child(&mut doc, html, wrapper).unwrap();
221
222 assert_ne!(body(&doc).unwrap(), Some(nested));
224
225 let (mut doc, html, _head, body_id) = skeleton();
227 node::remove_child(&mut doc, html, body_id).unwrap();
228 let wrapper = create_element(&mut doc, "div").unwrap();
229 let nested = create_element(&mut doc, "body").unwrap();
230 node::append_child(&mut doc, wrapper, nested).unwrap();
231 node::append_child(&mut doc, html, wrapper).unwrap();
232 assert_eq!(body(&doc).unwrap(), Some(nested));
233 }
234
235 #[test]
236 fn get_element_by_id_finds_an_attached_element() {
237 let (mut doc, _html, _head, body_id) = skeleton();
238 let target = create_element(&mut doc, "div").unwrap();
239 element::set_attribute(&mut doc, target, "id", "target").unwrap();
240 node::append_child(&mut doc, body_id, target).unwrap();
241 assert_eq!(get_element_by_id(&doc, "target").unwrap(), Some(target));
242 assert_eq!(get_element_by_id(&doc, "absent").unwrap(), None);
243 }
244
245 #[test]
246 fn query_selector_returns_the_first_match() {
247 let (mut doc, _html, _head, body_id) = skeleton();
248 let first = create_element(&mut doc, "p").unwrap();
249 let second = create_element(&mut doc, "p").unwrap();
250 node::append_child(&mut doc, body_id, first).unwrap();
251 node::append_child(&mut doc, body_id, second).unwrap();
252 assert_eq!(query_selector(&doc, "p").unwrap(), Some(first));
253 }
254
255 #[test]
256 fn query_selector_all_returns_every_match_in_tree_order() {
257 let (mut doc, _html, _head, body_id) = skeleton();
258 let first = create_element(&mut doc, "p").unwrap();
259 let second = create_element(&mut doc, "p").unwrap();
260 node::append_child(&mut doc, body_id, first).unwrap();
261 node::append_child(&mut doc, body_id, second).unwrap();
262 assert_eq!(query_selector_all(&doc, "p").unwrap(), vec![first, second]);
263 }
264
265 #[test]
266 fn an_unparseable_selector_is_an_error_not_an_empty_result() {
267 let (doc, _html, _head, _body) = skeleton();
268 assert!(matches!(
269 query_selector(&doc, "!!!"),
270 Err(DomError::InvalidSelector(_))
271 ));
272 assert!(matches!(
273 query_selector_all(&doc, "!!!"),
274 Err(DomError::InvalidSelector(_))
275 ));
276 }
277}