//! DOM tree, nodes, and events. //! //! Arena-based DOM tree with Document, Element, Text, and Comment node types. //! Each node is stored in a flat `Vec` and referenced by `NodeId`. use std::fmt; /// A handle to a node in the DOM tree. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct NodeId(usize); impl NodeId { /// Returns the underlying index. pub fn index(self) -> usize { self.0 } } /// An HTML/XML attribute (name-value pair). #[derive(Debug, Clone, PartialEq, Eq)] pub struct Attribute { pub name: String, pub value: String, } /// The data specific to each node type. #[derive(Debug, Clone, PartialEq, Eq)] pub enum NodeData { /// The root document node. Document, /// An element node with tag name, attributes, and optional namespace. Element { tag_name: String, attributes: Vec, namespace: Option, }, /// A text node containing character data. Text { data: String }, /// A comment node. Comment { data: String }, } /// A node in the DOM tree, with links to parent, children, and siblings. #[derive(Debug)] struct Node { data: NodeData, parent: Option, first_child: Option, last_child: Option, next_sibling: Option, prev_sibling: Option, } /// The DOM document: an arena of nodes with a root document node. pub struct Document { nodes: Vec, root: NodeId, } impl fmt::Debug for Document { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Document") .field("node_count", &self.nodes.len()) .field("root", &self.root) .finish() } } impl Document { /// Create a new document with a root Document node. pub fn new() -> Self { let root_node = Node { data: NodeData::Document, parent: None, first_child: None, last_child: None, next_sibling: None, prev_sibling: None, }; Document { nodes: vec![root_node], root: NodeId(0), } } /// Returns the root document node ID. pub fn root(&self) -> NodeId { self.root } /// Create an element node. Returns its `NodeId` (not yet attached to the tree). pub fn create_element(&mut self, tag_name: &str) -> NodeId { self.create_element_ns(tag_name, None) } /// Create an element node with an optional namespace. pub fn create_element_ns(&mut self, tag_name: &str, namespace: Option<&str>) -> NodeId { self.push_node(NodeData::Element { tag_name: tag_name.to_string(), attributes: Vec::new(), namespace: namespace.map(|s| s.to_string()), }) } /// Create a text node. Returns its `NodeId` (not yet attached to the tree). pub fn create_text(&mut self, data: &str) -> NodeId { self.push_node(NodeData::Text { data: data.to_string(), }) } /// Create a comment node. Returns its `NodeId` (not yet attached to the tree). pub fn create_comment(&mut self, data: &str) -> NodeId { self.push_node(NodeData::Comment { data: data.to_string(), }) } /// Append `child` as the last child of `parent`. /// /// If `child` is already attached elsewhere, it is first removed from its /// current position. pub fn append_child(&mut self, parent: NodeId, child: NodeId) { self.detach(child); let old_last = self.nodes[parent.0].last_child; self.nodes[child.0].parent = Some(parent); self.nodes[child.0].prev_sibling = old_last; self.nodes[child.0].next_sibling = None; if let Some(old_last_id) = old_last { self.nodes[old_last_id.0].next_sibling = Some(child); } else { self.nodes[parent.0].first_child = Some(child); } self.nodes[parent.0].last_child = Some(child); } /// Insert `new_child` before `reference` under `parent`. /// /// If `new_child` is already attached elsewhere, it is first removed. /// Panics if `reference` is not a child of `parent`. pub fn insert_before(&mut self, parent: NodeId, new_child: NodeId, reference: NodeId) { assert_eq!( self.nodes[reference.0].parent, Some(parent), "reference node is not a child of parent" ); self.detach(new_child); let prev = self.nodes[reference.0].prev_sibling; self.nodes[new_child.0].parent = Some(parent); self.nodes[new_child.0].next_sibling = Some(reference); self.nodes[new_child.0].prev_sibling = prev; self.nodes[reference.0].prev_sibling = Some(new_child); if let Some(prev_id) = prev { self.nodes[prev_id.0].next_sibling = Some(new_child); } else { self.nodes[parent.0].first_child = Some(new_child); } } /// Remove `child` from `parent`. /// /// Panics if `child` is not a child of `parent`. pub fn remove_child(&mut self, parent: NodeId, child: NodeId) { assert_eq!( self.nodes[child.0].parent, Some(parent), "node is not a child of parent" ); self.detach(child); } /// Returns the parent of `node`, or `None` for the root. pub fn parent(&self, node: NodeId) -> Option { self.nodes[node.0].parent } /// Returns an iterator over the direct children of `node`. pub fn children(&self, node: NodeId) -> Children<'_> { Children { doc: self, next: self.nodes[node.0].first_child, } } /// Returns the first child of `node`, if any. pub fn first_child(&self, node: NodeId) -> Option { self.nodes[node.0].first_child } /// Returns the last child of `node`, if any. pub fn last_child(&self, node: NodeId) -> Option { self.nodes[node.0].last_child } /// Returns the next sibling of `node`, if any. pub fn next_sibling(&self, node: NodeId) -> Option { self.nodes[node.0].next_sibling } /// Returns the previous sibling of `node`, if any. pub fn prev_sibling(&self, node: NodeId) -> Option { self.nodes[node.0].prev_sibling } /// Returns a reference to the node's data. pub fn node_data(&self, node: NodeId) -> &NodeData { &self.nodes[node.0].data } /// Returns the tag name if `node` is an Element, or `None`. pub fn tag_name(&self, node: NodeId) -> Option<&str> { match &self.nodes[node.0].data { NodeData::Element { tag_name, .. } => Some(tag_name), _ => None, } } /// Get an attribute value by name. Returns `None` if the node is not /// an element or the attribute is not present. pub fn get_attribute(&self, node: NodeId, name: &str) -> Option<&str> { match &self.nodes[node.0].data { NodeData::Element { attributes, .. } => attributes .iter() .find(|a| a.name == name) .map(|a| a.value.as_str()), _ => None, } } /// Set an attribute on an element node. If the attribute already exists, /// its value is replaced. Does nothing if `node` is not an element. pub fn set_attribute(&mut self, node: NodeId, name: &str, value: &str) { if let NodeData::Element { attributes, .. } = &mut self.nodes[node.0].data { if let Some(attr) = attributes.iter_mut().find(|a| a.name == name) { attr.value = value.to_string(); } else { attributes.push(Attribute { name: name.to_string(), value: value.to_string(), }); } } } /// Remove an attribute from an element node. Returns `true` if the /// attribute was present. pub fn remove_attribute(&mut self, node: NodeId, name: &str) -> bool { if let NodeData::Element { attributes, .. } = &mut self.nodes[node.0].data { let len_before = attributes.len(); attributes.retain(|a| a.name != name); attributes.len() < len_before } else { false } } /// Returns the text content of a Text or Comment node, or `None` for /// other node types. pub fn text_content(&self, node: NodeId) -> Option<&str> { match &self.nodes[node.0].data { NodeData::Text { data } | NodeData::Comment { data } => Some(data), _ => None, } } /// Set the text content of a Text or Comment node. /// Does nothing for other node types. pub fn set_text_content(&mut self, node: NodeId, new_data: &str) { match &mut self.nodes[node.0].data { NodeData::Text { data } | NodeData::Comment { data } => { *data = new_data.to_string(); } _ => {} } } /// Returns the total number of nodes in the arena (including detached nodes). pub fn len(&self) -> usize { self.nodes.len() } /// Returns true if the document has no nodes besides the root. pub fn is_empty(&self) -> bool { self.nodes.len() == 1 } // --- private helpers --- fn push_node(&mut self, data: NodeData) -> NodeId { let id = NodeId(self.nodes.len()); self.nodes.push(Node { data, parent: None, first_child: None, last_child: None, next_sibling: None, prev_sibling: None, }); id } /// Detach a node from its current parent (if any), updating sibling links. fn detach(&mut self, node: NodeId) { let parent = match self.nodes[node.0].parent { Some(p) => p, None => return, }; let prev = self.nodes[node.0].prev_sibling; let next = self.nodes[node.0].next_sibling; if let Some(prev_id) = prev { self.nodes[prev_id.0].next_sibling = next; } else { self.nodes[parent.0].first_child = next; } if let Some(next_id) = next { self.nodes[next_id.0].prev_sibling = prev; } else { self.nodes[parent.0].last_child = prev; } self.nodes[node.0].parent = None; self.nodes[node.0].prev_sibling = None; self.nodes[node.0].next_sibling = None; } } impl Default for Document { fn default() -> Self { Self::new() } } /// Iterator over the direct children of a node. pub struct Children<'a> { doc: &'a Document, next: Option, } impl<'a> Iterator for Children<'a> { type Item = NodeId; fn next(&mut self) -> Option { let current = self.next?; self.next = self.doc.nodes[current.0].next_sibling; Some(current) } } #[cfg(test)] mod tests { use super::*; #[test] fn new_document_has_root() { let doc = Document::new(); assert_eq!(doc.root(), NodeId(0)); assert_eq!(*doc.node_data(doc.root()), NodeData::Document); assert!(doc.children(doc.root()).next().is_none()); } #[test] fn create_element() { let mut doc = Document::new(); let div = doc.create_element("div"); assert_eq!(doc.tag_name(div), Some("div")); assert!(doc.parent(div).is_none()); } #[test] fn create_element_with_namespace() { let mut doc = Document::new(); let svg = doc.create_element_ns("svg", Some("http://www.w3.org/2000/svg")); match doc.node_data(svg) { NodeData::Element { namespace, .. } => { assert_eq!(namespace.as_deref(), Some("http://www.w3.org/2000/svg")); } _ => panic!("expected element"), } } #[test] fn create_text() { let mut doc = Document::new(); let t = doc.create_text("hello"); assert_eq!(doc.text_content(t), Some("hello")); assert_eq!(doc.tag_name(t), None); } #[test] fn create_comment() { let mut doc = Document::new(); let c = doc.create_comment("a comment"); assert_eq!(doc.text_content(c), Some("a comment")); match doc.node_data(c) { NodeData::Comment { data } => assert_eq!(data, "a comment"), _ => panic!("expected comment"), } } #[test] fn append_child_single() { let mut doc = Document::new(); let root = doc.root(); let child = doc.create_element("div"); doc.append_child(root, child); assert_eq!(doc.parent(child), Some(root)); assert_eq!(doc.first_child(root), Some(child)); assert_eq!(doc.last_child(root), Some(child)); assert!(doc.next_sibling(child).is_none()); assert!(doc.prev_sibling(child).is_none()); } #[test] fn append_child_multiple() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); let b = doc.create_element("b"); let c = doc.create_element("c"); doc.append_child(root, a); doc.append_child(root, b); doc.append_child(root, c); assert_eq!(doc.first_child(root), Some(a)); assert_eq!(doc.last_child(root), Some(c)); assert_eq!(doc.next_sibling(a), Some(b)); assert_eq!(doc.next_sibling(b), Some(c)); assert!(doc.next_sibling(c).is_none()); assert!(doc.prev_sibling(a).is_none()); assert_eq!(doc.prev_sibling(b), Some(a)); assert_eq!(doc.prev_sibling(c), Some(b)); } #[test] fn children_iterator() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); let b = doc.create_element("b"); let c = doc.create_element("c"); doc.append_child(root, a); doc.append_child(root, b); doc.append_child(root, c); let children: Vec = doc.children(root).collect(); assert_eq!(children, vec![a, b, c]); } #[test] fn children_iterator_empty() { let doc = Document::new(); let children: Vec = doc.children(doc.root()).collect(); assert!(children.is_empty()); } #[test] fn insert_before_first() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); let b = doc.create_element("b"); doc.append_child(root, b); doc.insert_before(root, a, b); let children: Vec = doc.children(root).collect(); assert_eq!(children, vec![a, b]); assert_eq!(doc.first_child(root), Some(a)); assert_eq!(doc.prev_sibling(b), Some(a)); assert_eq!(doc.next_sibling(a), Some(b)); } #[test] fn insert_before_middle() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); let b = doc.create_element("b"); let c = doc.create_element("c"); doc.append_child(root, a); doc.append_child(root, c); doc.insert_before(root, b, c); let children: Vec = doc.children(root).collect(); assert_eq!(children, vec![a, b, c]); } #[test] fn remove_child_only() { let mut doc = Document::new(); let root = doc.root(); let child = doc.create_element("div"); doc.append_child(root, child); doc.remove_child(root, child); assert!(doc.parent(child).is_none()); assert!(doc.first_child(root).is_none()); assert!(doc.last_child(root).is_none()); } #[test] fn remove_child_first() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); let b = doc.create_element("b"); doc.append_child(root, a); doc.append_child(root, b); doc.remove_child(root, a); assert_eq!(doc.first_child(root), Some(b)); assert_eq!(doc.last_child(root), Some(b)); assert!(doc.prev_sibling(b).is_none()); } #[test] fn remove_child_last() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); let b = doc.create_element("b"); doc.append_child(root, a); doc.append_child(root, b); doc.remove_child(root, b); assert_eq!(doc.first_child(root), Some(a)); assert_eq!(doc.last_child(root), Some(a)); assert!(doc.next_sibling(a).is_none()); } #[test] fn remove_child_middle() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); let b = doc.create_element("b"); let c = doc.create_element("c"); doc.append_child(root, a); doc.append_child(root, b); doc.append_child(root, c); doc.remove_child(root, b); let children: Vec = doc.children(root).collect(); assert_eq!(children, vec![a, c]); assert_eq!(doc.next_sibling(a), Some(c)); assert_eq!(doc.prev_sibling(c), Some(a)); } #[test] fn append_child_moves_from_old_parent() { let mut doc = Document::new(); let root = doc.root(); let parent1 = doc.create_element("div"); let parent2 = doc.create_element("span"); let child = doc.create_element("p"); doc.append_child(root, parent1); doc.append_child(root, parent2); doc.append_child(parent1, child); assert_eq!(doc.parent(child), Some(parent1)); // Move child from parent1 to parent2. doc.append_child(parent2, child); assert_eq!(doc.parent(child), Some(parent2)); assert!(doc.first_child(parent1).is_none()); assert_eq!(doc.first_child(parent2), Some(child)); } #[test] fn set_and_get_attribute() { let mut doc = Document::new(); let div = doc.create_element("div"); assert!(doc.get_attribute(div, "class").is_none()); doc.set_attribute(div, "class", "container"); assert_eq!(doc.get_attribute(div, "class"), Some("container")); doc.set_attribute(div, "id", "main"); assert_eq!(doc.get_attribute(div, "id"), Some("main")); assert_eq!(doc.get_attribute(div, "class"), Some("container")); } #[test] fn set_attribute_replaces() { let mut doc = Document::new(); let div = doc.create_element("div"); doc.set_attribute(div, "class", "old"); doc.set_attribute(div, "class", "new"); assert_eq!(doc.get_attribute(div, "class"), Some("new")); } #[test] fn remove_attribute() { let mut doc = Document::new(); let div = doc.create_element("div"); doc.set_attribute(div, "class", "x"); assert!(doc.remove_attribute(div, "class")); assert!(doc.get_attribute(div, "class").is_none()); assert!(!doc.remove_attribute(div, "class")); } #[test] fn attribute_on_non_element_is_noop() { let mut doc = Document::new(); let text = doc.create_text("hello"); doc.set_attribute(text, "class", "x"); assert!(doc.get_attribute(text, "class").is_none()); assert!(!doc.remove_attribute(text, "class")); } #[test] fn text_content_set() { let mut doc = Document::new(); let t = doc.create_text("hello"); doc.set_text_content(t, "world"); assert_eq!(doc.text_content(t), Some("world")); } #[test] fn text_content_on_element_is_none() { let mut doc = Document::new(); let div = doc.create_element("div"); assert!(doc.text_content(div).is_none()); } #[test] fn build_simple_html_tree() { // Build: Test

Hello

let mut doc = Document::new(); let root = doc.root(); let html = doc.create_element("html"); let head = doc.create_element("head"); let title = doc.create_element("title"); let title_text = doc.create_text("Test"); let body = doc.create_element("body"); let p = doc.create_element("p"); let p_text = doc.create_text("Hello"); doc.append_child(root, html); doc.append_child(html, head); doc.append_child(head, title); doc.append_child(title, title_text); doc.append_child(html, body); doc.append_child(body, p); doc.append_child(p, p_text); // Verify structure. assert_eq!(doc.tag_name(html), Some("html")); assert_eq!(doc.parent(html), Some(root)); let html_children: Vec = doc.children(html).collect(); assert_eq!(html_children, vec![head, body]); let head_children: Vec = doc.children(head).collect(); assert_eq!(head_children, vec![title]); let title_children: Vec = doc.children(title).collect(); assert_eq!(title_children, vec![title_text]); assert_eq!(doc.text_content(title_text), Some("Test")); let body_children: Vec = doc.children(body).collect(); assert_eq!(body_children, vec![p]); let p_children: Vec = doc.children(p).collect(); assert_eq!(p_children, vec![p_text]); assert_eq!(doc.text_content(p_text), Some("Hello")); } #[test] fn build_tree_with_attributes() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); doc.set_attribute(a, "href", "https://example.com"); doc.set_attribute(a, "class", "link"); doc.append_child(root, a); let text = doc.create_text("Click here"); doc.append_child(a, text); assert_eq!(doc.get_attribute(a, "href"), Some("https://example.com")); assert_eq!(doc.get_attribute(a, "class"), Some("link")); assert_eq!(doc.text_content(text), Some("Click here")); } #[test] fn nested_children_traversal() { //
deep
let mut doc = Document::new(); let root = doc.root(); let div = doc.create_element("div"); let span = doc.create_element("span"); let em = doc.create_element("em"); let text = doc.create_text("deep"); doc.append_child(root, div); doc.append_child(div, span); doc.append_child(span, em); doc.append_child(em, text); // Walk down. let mut current = doc.first_child(root).unwrap(); assert_eq!(doc.tag_name(current), Some("div")); current = doc.first_child(current).unwrap(); assert_eq!(doc.tag_name(current), Some("span")); current = doc.first_child(current).unwrap(); assert_eq!(doc.tag_name(current), Some("em")); let leaf = doc.first_child(current).unwrap(); assert_eq!(doc.text_content(leaf), Some("deep")); // Walk back up. assert_eq!(doc.parent(leaf).map(|n| doc.tag_name(n)), Some(Some("em"))); } #[test] fn document_len_and_is_empty() { let doc = Document::new(); assert_eq!(doc.len(), 1); // root only assert!(doc.is_empty()); // no children besides root let mut doc2 = Document::new(); let _ = doc2.create_element("div"); assert_eq!(doc2.len(), 2); assert!(!doc2.is_empty()); } #[test] fn default_document() { let doc = Document::default(); assert_eq!(doc.root(), NodeId(0)); } #[test] fn node_id_equality() { let id1 = NodeId(5); let id2 = NodeId(5); let id3 = NodeId(6); assert_eq!(id1, id2); assert_ne!(id1, id3); } #[test] #[should_panic(expected = "reference node is not a child of parent")] fn insert_before_wrong_parent_panics() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); let b = doc.create_element("b"); let c = doc.create_element("c"); doc.append_child(root, a); // b is not a child of root, so this should panic. doc.insert_before(root, c, b); } #[test] #[should_panic(expected = "node is not a child of parent")] fn remove_child_wrong_parent_panics() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); // a is not attached to root. doc.remove_child(root, a); } #[test] fn insert_before_moves_from_old_parent() { let mut doc = Document::new(); let root = doc.root(); let parent1 = doc.create_element("div"); let parent2 = doc.create_element("span"); let child = doc.create_element("p"); let reference = doc.create_element("em"); doc.append_child(root, parent1); doc.append_child(root, parent2); doc.append_child(parent1, child); doc.append_child(parent2, reference); // Move child from parent1 to parent2 before reference. doc.insert_before(parent2, child, reference); assert_eq!(doc.parent(child), Some(parent2)); assert!(doc.first_child(parent1).is_none()); let children: Vec = doc.children(parent2).collect(); assert_eq!(children, vec![child, reference]); } #[test] fn comment_in_tree() { let mut doc = Document::new(); let root = doc.root(); let comment = doc.create_comment("TODO: add content"); let div = doc.create_element("div"); doc.append_child(root, comment); doc.append_child(root, div); let children: Vec = doc.children(root).collect(); assert_eq!(children, vec![comment, div]); assert_eq!(doc.text_content(comment), Some("TODO: add content")); } }