1use std::collections::BTreeMap;
2
3pub const HTML_NAMESPACE_URI: &str = "http://www.w3.org/1999/xhtml";
4pub const SVG_NAMESPACE_URI: &str = "http://www.w3.org/2000/svg";
5pub const MATHML_NAMESPACE_URI: &str = "http://www.w3.org/1998/Math/MathML";
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
8pub struct NodeId {
9 index: u32,
10 generation: u32,
11}
12
13impl NodeId {
14 pub const fn new(index: u32, generation: u32) -> Self {
15 Self { index, generation }
16 }
17
18 pub const fn index(self) -> u32 {
19 self.index
20 }
21
22 pub const fn generation(self) -> u32 {
23 self.generation
24 }
25}
26
27#[derive(Clone, Debug, Default, PartialEq, Eq)]
28pub struct DocumentState {
29 pub title: String,
30}
31
32#[derive(Clone, Debug, Default, PartialEq, Eq)]
33pub struct ElementData {
34 pub tag_name: String,
35 pub local_name: String,
36 pub namespace_uri: String,
37 pub attributes: BTreeMap<String, String>,
38}
39
40#[derive(Clone, Debug, Default, PartialEq, Eq)]
41pub struct TextData {
42 pub value: String,
43}
44
45#[derive(Clone, Debug, PartialEq, Eq)]
46pub enum NodeKind {
47 Document,
48 Element(ElementData),
49 Text(TextData),
50 Comment(String),
51}
52
53#[derive(Clone, Debug, PartialEq, Eq)]
54pub struct NodeRecord {
55 pub id: NodeId,
56 pub parent: Option<NodeId>,
57 pub children: Vec<NodeId>,
58 pub kind: NodeKind,
59}
60
61#[derive(Clone, Debug, Default, PartialEq, Eq)]
62pub struct FormControlState {
63 pub value: String,
64 pub checked: bool,
65 pub indeterminate: bool,
66}
67
68#[derive(Clone, Debug, Default, PartialEq, Eq)]
69pub struct SelectionState {
70 pub start: usize,
71 pub end: usize,
72}
73
74#[derive(Clone, Debug, Default, PartialEq, Eq)]
75pub struct FileInputState {
76 pub files: Vec<String>,
77}
78
79#[derive(Clone, Debug, Default, PartialEq, Eq)]
80pub struct DialogState {
81 pub open: bool,
82}
83
84#[derive(Clone, Debug, Default, PartialEq, Eq)]
85pub struct LayoutStubState {
86 pub width: u32,
87 pub height: u32,
88}
89
90#[derive(Clone, Debug, Default, PartialEq, Eq)]
91pub struct DomIndexes {
92 pub id_index: BTreeMap<String, NodeId>,
93 pub name_index: BTreeMap<String, Vec<NodeId>>,
94 pub tag_index: BTreeMap<String, Vec<NodeId>>,
95 pub class_index: BTreeMap<String, Vec<NodeId>>,
96}
97
98#[derive(Clone, Debug, Default, PartialEq, Eq)]
99pub struct DomSideTables {
100 pub form_controls: BTreeMap<NodeId, FormControlState>,
101 pub selection: BTreeMap<NodeId, SelectionState>,
102 pub file_inputs: BTreeMap<NodeId, FileInputState>,
103 pub dialogs: BTreeMap<NodeId, DialogState>,
104 pub layout_stub: BTreeMap<NodeId, LayoutStubState>,
105}
106
107#[derive(Clone, Debug, PartialEq, Eq)]
108pub struct DomStore {
109 nodes: Vec<NodeRecord>,
110 document: DocumentState,
111 document_id: NodeId,
112 indexes: DomIndexes,
113 side_tables: DomSideTables,
114 focused_node: Option<NodeId>,
115 target_fragment: Option<String>,
116 source_html: Option<String>,
117}
118
119impl Default for DomStore {
120 fn default() -> Self {
121 Self::new_empty()
122 }
123}
124
125impl DomStore {
126 pub fn new_empty() -> Self {
127 let document_id = NodeId::new(0, 0);
128 let nodes = vec![NodeRecord {
129 id: document_id,
130 parent: None,
131 children: Vec::new(),
132 kind: NodeKind::Document,
133 }];
134 Self {
135 nodes,
136 document: DocumentState::default(),
137 document_id,
138 indexes: DomIndexes::default(),
139 side_tables: DomSideTables::default(),
140 focused_node: None,
141 target_fragment: None,
142 source_html: None,
143 }
144 }
145
146 pub fn source_html(&self) -> Option<&str> {
147 self.source_html.as_deref()
148 }
149
150 pub fn document_id(&self) -> NodeId {
151 self.document_id
152 }
153
154 pub fn node_count(&self) -> usize {
155 self.nodes.len()
156 }
157
158 pub fn nodes(&self) -> &[NodeRecord] {
159 &self.nodes
160 }
161
162 pub fn indexes(&self) -> &DomIndexes {
163 &self.indexes
164 }
165
166 pub fn side_tables(&self) -> &DomSideTables {
167 &self.side_tables
168 }
169
170 pub fn focused_node(&self) -> Option<NodeId> {
171 self.focused_node
172 }
173
174 pub fn set_focused_node(&mut self, focused_node: Option<NodeId>) {
175 self.focused_node = focused_node;
176 }
177
178 pub fn target_fragment(&self) -> Option<&str> {
179 self.target_fragment.as_deref()
180 }
181
182 pub fn set_target_fragment(&mut self, target_fragment: Option<String>) {
183 self.target_fragment = target_fragment;
184 }
185
186 pub fn document_state(&self) -> &DocumentState {
187 &self.document
188 }
189}
190
191mod html_dom;
192
193#[cfg(test)]
194mod tests {
195 use super::{DomStore, NodeId, NodeKind};
196
197 #[test]
198 fn empty_store_has_document_root() {
199 let store = DomStore::new_empty();
200 assert_eq!(store.node_count(), 1);
201 assert_eq!(store.nodes()[0].kind, NodeKind::Document);
202 assert_eq!(store.source_html(), None);
203 }
204
205 #[test]
206 fn bootstrap_html_is_recorded_for_phase_zero() {
207 let mut store = DomStore::new_empty();
208 store
209 .bootstrap_html("<p>Hello</p>")
210 .expect("HTML should parse");
211 assert_eq!(store.source_html(), Some("<p>Hello</p>"));
212 assert_eq!(store.node_count(), 3);
213 assert_eq!(store.select("#missing").unwrap(), Vec::<NodeId>::new());
214 assert_eq!(store.dump_dom(), "#document\n <p>\n \"Hello\"\n </p>");
215 }
216}