lewp_html/
node_ext.rs

1//! Trait for DOM node interactions.
2
3use {
4    crate::Node,
5    html5ever::{
6        namespace_url,
7        ns,
8        tendril::Tendril,
9        Attribute,
10        LocalName,
11        QualName,
12    },
13    rcdom::NodeData,
14    std::cell::RefCell,
15};
16
17/// Methods for easy interaction with a `DOM` [Node].
18pub trait NodeExt
19where
20    Self: Sized,
21{
22    /// The children of this node.
23    fn children(&self) -> &RefCell<Vec<Node>>;
24    /// The [NodeData].
25    fn data(&self) -> &rcdom::NodeData;
26    /// Appends the given children to the node.
27    fn append_children(&self, children: Vec<Node>);
28    /// Appends the given child to the node.
29    fn append_child(&self, child: Node);
30    /// Returns the tag name as string if available.
31    fn tag_name(&self) -> Option<String> {
32        match &self.data() {
33            NodeData::Doctype { name, .. } => Some(name.to_string()),
34            NodeData::Element { name, .. } => Some(name.local.to_string()),
35            _ => None,
36        }
37    }
38    /// Adds an Attribute with given name and value to the node.
39    ///
40    /// If the attribute is already present, it will be overridden.
41    fn attr(self, name: &str, value: &str) -> Self {
42        {
43            let attrs = match &self.data() {
44                NodeData::Element { attrs, .. } => attrs,
45                _ => return self,
46            };
47            let mut attrs = attrs.borrow_mut();
48            attrs.push(Attribute {
49                name: QualName::new(None, ns!(), LocalName::from(name)),
50                value: Tendril::from(value),
51            });
52        }
53        self
54    }
55    /// Adds an Attribute with given name and value to the node without consuming self.
56    ///
57    /// If the attribute is already present, it will be overridden.
58    fn borrow_attr(&self, name: &str, value: &str) {
59        {
60            let attrs = match &self.data() {
61                NodeData::Element { attrs, .. } => attrs,
62                _ => return,
63            };
64            let mut attrs = attrs.borrow_mut();
65            attrs.push(Attribute {
66                name: QualName::new(None, ns!(), LocalName::from(name)),
67                value: Tendril::from(value),
68            });
69        }
70    }
71    /// Adds a list of attributes to the node.
72    ///
73    /// The attributes are tuples of name and value. Attributes that are already
74    /// set are being overridden.
75    fn attrs(mut self, attributes: Vec<(&str, &str)>) -> Self {
76        for attr in attributes {
77            self = self.attr(attr.0, attr.1);
78        }
79        self
80    }
81    /// Adds a list of attributes to the node without consuming self.
82    ///
83    /// The attributes are tuples of name and value. Attributes that are already
84    /// set are being overridden.
85    fn borrow_attrs(&self, attributes: Vec<(&str, &str)>) {
86        for attr in attributes {
87            self.borrow_attr(attr.0, attr.1);
88        }
89    }
90    /// Returns the attribute index if present.
91    fn find_attribute(&self, attribute_name: &str) -> Option<usize> {
92        let attrs = match &self.data() {
93            NodeData::Element { attrs, .. } => attrs,
94            _ => return None,
95        };
96        for (idx, attr) in attrs.borrow().iter().enumerate() {
97            if attr.name
98                != QualName::new(None, ns!(), LocalName::from(attribute_name))
99            {
100                continue;
101            }
102            return Some(idx);
103        }
104        None
105    }
106    /// Checks if the given attribute matches the value.
107    fn attribute_eq(&self, attribute_name: &str, value: &str) -> bool {
108        if let Some(index) = self.find_attribute(attribute_name) {
109            let attrs = match &self.data() {
110                NodeData::Element { attrs, .. } => attrs.borrow_mut(),
111                _ => return false,
112            };
113            return &*attrs[index].value == value;
114        }
115        false
116    }
117
118    /// Adds `value` to the `class` attribute. Adds the `class` attribute if
119    /// it is not present.
120    fn add_class(&self, class_value: &str) {
121        let attribute_index = self.find_attribute("class");
122        let attrs = match self.data() {
123            NodeData::Element { attrs, .. } => attrs,
124            _ => return,
125        };
126        let mut attrs = attrs.borrow_mut();
127        match attribute_index {
128            None => {
129                attrs.push(Attribute {
130                    name: QualName::new(None, ns!(), LocalName::from("class")),
131                    value: Tendril::from(class_value),
132                });
133            }
134            Some(index) => {
135                let value = String::from(attrs[index].value.clone());
136                let mut value = value.split(' ').collect::<Vec<_>>();
137                if value.contains(&class_value) {
138                    return;
139                }
140                value.push(class_value);
141                attrs[index].value = Tendril::from(value.join(" "));
142            }
143        };
144    }
145
146    /// Removes the given `value` from the `class` attribute.
147    fn remove_class(&self, class_value: &str) {
148        let attribute_index = self.find_attribute("class");
149        let attrs = match self.data() {
150            NodeData::Element { attrs, .. } => attrs,
151            _ => return,
152        };
153        let mut attrs = attrs.borrow_mut();
154        if let Some(index) = attribute_index {
155            let value = String::from(attrs[index].value.clone());
156            let mut value = value.split(' ').collect::<Vec<_>>();
157            value.retain(|x| x != &class_value);
158            attrs[index].value = Tendril::from(value.join(" "));
159        };
160    }
161
162    /// True if `value` is available in `class` attribute.
163    fn has_class(&self, class_value: &str) -> bool {
164        let attribute_index = self.find_attribute("class");
165        let attrs = match self.data() {
166            NodeData::Element { attrs, .. } => attrs,
167            _ => return false,
168        };
169        let attrs = attrs.borrow();
170        match attribute_index {
171            None => false,
172            Some(index) => {
173                attrs[index].value.split(' ').any(|x| x == class_value)
174            }
175        }
176    }
177
178    /// Toggles the given `value` of the `class` attribute. Creates the class
179    /// attribute if not set yet.
180    fn toggle_class(&self, class_value: &str) {
181        let attribute_index = self.find_attribute("class");
182        let attrs = match self.data() {
183            NodeData::Element { attrs, .. } => attrs,
184            _ => return,
185        };
186        let mut attrs = attrs.borrow_mut();
187        match attribute_index {
188            None => {
189                attrs.push(Attribute {
190                    name: QualName::new(None, ns!(), LocalName::from("class")),
191                    value: Tendril::from(class_value),
192                });
193            }
194            Some(index) => {
195                let value = String::from(attrs[index].value.clone());
196                let mut value = value.split(' ').collect::<Vec<_>>();
197                if value.contains(&class_value) {
198                    return;
199                }
200                value.push(class_value);
201                attrs[index].value = Tendril::from(value.join(" "));
202            }
203        };
204    }
205
206    /// Removes the attribute with the given `name`. Does nothing
207    /// if the attribute does not exist. This method does not compare its
208    /// values.
209    fn remove_attribute(&self, name: &str) {
210        let attribute_index = self.find_attribute(name);
211        let attrs = match self.data() {
212            NodeData::Element { attrs, .. } => attrs,
213            _ => return,
214        };
215        let mut attrs = attrs.borrow_mut();
216        if let Some(index) = attribute_index {
217            attrs.remove(index);
218        };
219    }
220}