Skip to main content

rama_http/protocols/html/selector/
dom.rs

1//! A minimal in-memory element tree that implements [`SelectorSubject`].
2//!
3//! This is a convenience for matching selectors against a materialized
4//! tree (and for testing the engine); the streaming HTML parser does not
5//! use it. Only element nodes and their attributes are modelled — text and
6//! comments are irrelevant to selector matching.
7
8use super::matcher::SelectorSubject;
9
10/// Handle to an element within a [`Dom`].
11///
12/// Returned by [`Dom::create`] / [`Dom::append`]; only valid for the
13/// `Dom` that produced it.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct NodeId(usize);
16
17#[derive(Debug)]
18struct Attribute {
19    name: String,
20    value: String,
21}
22
23#[derive(Debug)]
24struct NodeData {
25    name: String,
26    attributes: Vec<Attribute>,
27    parent: Option<usize>,
28    children: Vec<usize>,
29}
30
31/// A simple arena-backed element tree.
32#[derive(Debug, Default)]
33pub struct Dom {
34    nodes: Vec<NodeData>,
35}
36
37impl Dom {
38    /// Creates an empty tree.
39    #[must_use]
40    pub fn new() -> Self {
41        Self::default()
42    }
43
44    /// Creates a parentless (root) element with the given tag name.
45    pub fn create(&mut self, name: &str) -> NodeId {
46        self.push(name, None)
47    }
48
49    /// Appends a new child element to `parent` and returns its id.
50    pub fn append(&mut self, parent: NodeId, name: &str) -> NodeId {
51        let id = self.push(name, Some(parent.0));
52        if let Some(node) = self.nodes.get_mut(parent.0) {
53            node.children.push(id.0);
54        }
55        id
56    }
57
58    /// Sets (or replaces) an attribute on `node`.
59    pub fn set_attr(&mut self, node: NodeId, name: &str, value: &str) {
60        let Some(data) = self.nodes.get_mut(node.0) else {
61            return;
62        };
63        if let Some(existing) = data
64            .attributes
65            .iter_mut()
66            .find(|attr| attr.name.eq_ignore_ascii_case(name))
67        {
68            existing.value = value.to_owned();
69        } else {
70            data.attributes.push(Attribute {
71                name: name.to_owned(),
72                value: value.to_owned(),
73            });
74        }
75    }
76
77    /// Returns a [`SelectorSubject`] handle for `node`.
78    #[must_use]
79    pub fn element(&self, node: NodeId) -> Element<'_> {
80        Element {
81            dom: self,
82            id: node.0,
83        }
84    }
85
86    fn push(&mut self, name: &str, parent: Option<usize>) -> NodeId {
87        let id = self.nodes.len();
88        self.nodes.push(NodeData {
89            name: name.to_owned(),
90            attributes: Vec::new(),
91            parent,
92            children: Vec::new(),
93        });
94        NodeId(id)
95    }
96}
97
98/// A cheap, copyable view of an element in a [`Dom`].
99#[derive(Debug, Clone, Copy)]
100pub struct Element<'a> {
101    dom: &'a Dom,
102    id: usize,
103}
104
105impl Element<'_> {
106    fn data(&self) -> Option<&NodeData> {
107        self.dom.nodes.get(self.id)
108    }
109}
110
111impl SelectorSubject for Element<'_> {
112    fn local_name(&self) -> &str {
113        self.data().map_or("", |d| d.name.as_str())
114    }
115
116    fn attribute(&self, name: &str) -> Option<&str> {
117        self.data()?
118            .attributes
119            .iter()
120            .find(|attr| attr.name.eq_ignore_ascii_case(name))
121            .map(|attr| attr.value.as_str())
122    }
123
124    fn parent(&self) -> Option<Self> {
125        let parent = self.data()?.parent?;
126        Some(Self {
127            dom: self.dom,
128            id: parent,
129        })
130    }
131
132    fn nth_child_index(&self) -> usize {
133        let Some(data) = self.data() else {
134            return 1;
135        };
136        let Some(parent) = data.parent.and_then(|p| self.dom.nodes.get(p)) else {
137            return 1;
138        };
139        parent
140            .children
141            .iter()
142            .position(|&c| c == self.id)
143            .map_or(1, |i| i + 1)
144    }
145
146    fn nth_of_type_index(&self) -> usize {
147        let Some(data) = self.data() else {
148            return 1;
149        };
150        let Some(parent) = data.parent.and_then(|p| self.dom.nodes.get(p)) else {
151            return 1;
152        };
153        let mut index = 0;
154        for &child in &parent.children {
155            if let Some(sibling) = self.dom.nodes.get(child)
156                && sibling.name.eq_ignore_ascii_case(&data.name)
157            {
158                index += 1;
159            }
160            if child == self.id {
161                return index.max(1);
162            }
163        }
164        index.max(1)
165    }
166}