1use std::collections::{BTreeMap, BTreeSet};
9
10use crate::NodeId;
11
12#[derive(Debug, Clone)]
14#[non_exhaustive]
15pub enum NodeData<Ext = ()> {
16 Element {
17 tag: String,
18 attrs: BTreeMap<String, String>,
21 classes: BTreeSet<String>,
23 ext: Ext,
25 },
26 Text {
27 data: String,
28 },
29 Comment {
30 data: String,
31 },
32 Fragment,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40#[repr(u8)]
41#[non_exhaustive]
42pub enum NodeType {
43 Element = 1,
44 Text = 3,
45 Comment = 8,
46 Fragment = 11,
47}
48
49impl NodeType {
50 pub fn canonical_name(self) -> &'static str {
52 match self {
53 NodeType::Element => "", NodeType::Text => "#text",
55 NodeType::Comment => "#comment",
56 NodeType::Fragment => "#document-fragment",
57 }
58 }
59}
60
61#[derive(Debug, Clone)]
66pub(crate) struct Node<Ext = ()> {
67 pub(crate) parent: Option<NodeId>,
68 pub(crate) first_child: Option<NodeId>,
69 pub(crate) last_child: Option<NodeId>,
70 pub(crate) prev_sibling: Option<NodeId>,
71 pub(crate) next_sibling: Option<NodeId>,
72 pub(crate) data: NodeData<Ext>,
73}
74
75impl<Ext> Node<Ext> {
76 pub(crate) fn new(data: NodeData<Ext>) -> Self {
77 Self {
78 parent: None,
79 first_child: None,
80 last_child: None,
81 prev_sibling: None,
82 next_sibling: None,
83 data,
84 }
85 }
86
87 pub(crate) fn tag_name(&self) -> Option<&str> {
89 match &self.data {
90 NodeData::Element { tag, .. } => Some(tag),
91 _ => None,
92 }
93 }
94
95 pub(crate) fn node_type(&self) -> NodeType {
96 match &self.data {
97 NodeData::Element { .. } => NodeType::Element,
98 NodeData::Text { .. } => NodeType::Text,
99 NodeData::Comment { .. } => NodeType::Comment,
100 NodeData::Fragment => NodeType::Fragment,
101 }
102 }
103
104 #[allow(dead_code)] pub(crate) fn unlink(&mut self) {
107 self.parent = None;
108 self.first_child = None;
109 self.last_child = None;
110 self.prev_sibling = None;
111 self.next_sibling = None;
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn spec_numeric_values_match_mdn() {
121 assert_eq!(NodeType::Element as u8, 1);
122 assert_eq!(NodeType::Text as u8, 3);
123 assert_eq!(NodeType::Comment as u8, 8);
124 assert_eq!(NodeType::Fragment as u8, 11);
125 }
126
127 #[test]
128 fn canonical_names_match_browser() {
129 assert_eq!(NodeType::Text.canonical_name(), "#text");
130 assert_eq!(NodeType::Comment.canonical_name(), "#comment");
131 assert_eq!(NodeType::Fragment.canonical_name(), "#document-fragment");
132 }
133
134 #[test]
135 fn new_element_node_starts_unlinked() {
136 let n: Node<()> = Node::new(NodeData::Element {
137 tag: "div".into(),
138 attrs: BTreeMap::new(),
139 classes: BTreeSet::new(),
140 ext: (),
141 });
142 assert!(n.parent.is_none());
143 assert!(n.first_child.is_none());
144 assert!(n.last_child.is_none());
145 assert!(n.prev_sibling.is_none());
146 assert!(n.next_sibling.is_none());
147 assert_eq!(n.node_type(), NodeType::Element);
148 assert_eq!(n.tag_name(), Some("div"));
149 }
150
151 #[test]
152 fn non_element_tag_name_is_none() {
153 let n: Node<()> = Node::new(NodeData::Text { data: "hi".into() });
154 assert_eq!(n.tag_name(), None);
155 assert_eq!(n.node_type(), NodeType::Text);
156 }
157}