Skip to main content

bt_dom/
lib.rs

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}