1use std::cell::RefCell;
2use std::collections::HashSet;
3use std::sync::OnceLock;
4
5use html5ever::Attribute;
6
7/// Returns a [`HashSet`] containing the HTML elements that are inline by default.
8///
9/// [MDN: List of "inline" elements](https://yari-demos.prod.mdn.mozit.cloud/en-US/docs/Web/HTML/Inline_elements)
10fn inline_elements() -> &'static HashSet<&'static str> {
11 static INLINE_ELEMENTS: OnceLock<HashSet<&str>> = OnceLock::new();
12 &INLINE_ELEMENTS.get_or_init(|| {
13 HashSet::from_iter([
14 "a", "abbr", "acronym", "audio", "b", "bdi", "bdo", "big", "br", "button", "canvas",
15 "cite", "code", "data", "datalist", "del", "dfn", "em", "embed", "i", "iframe", "img",
16 "input", "ins", "kbd", "label", "map", "mark", "meter", "noscript", "object", "output",
17 "picture", "progress", "q", "ruby", "s", "samp", "script", "select", "slot", "small",
18 "span", "strong", "sub", "sup", "svg", "template", "textarea", "time", "tt", "u",
19 "var", "video", "wbr",
20 ])
21 })
22}
23
24#[derive(Debug, Clone)]
25pub struct HtmlElement {
26 pub(crate) tag: String,
27 pub(crate) attrs: RefCell<Vec<Attribute>>,
28}
29
30impl HtmlElement {
31 /// Returns whether this [`HtmlElement`] is an inline element.
32 pub fn is_inline(&self) -> bool {
33 inline_elements().contains(self.tag.as_str())
34 }
35
36 /// Returns the attribute with the specified name.
37 pub fn attr(&self, name: &str) -> Option<String> {
38 self.attrs
39 .borrow()
40 .iter()
41 .find(|attr| attr.name.local.to_string() == name)
42 .map(|attr| attr.value.to_string())
43 }
44
45 /// Returns the list of classes on this [`HtmlElement`].
46 pub fn classes(&self) -> Vec<String> {
47 self.attrs
48 .borrow()
49 .iter()
50 .find(|attr| attr.name.local.to_string() == "class")
51 .map(|attr| {
52 attr.value
53 .split(' ')
54 .map(|class| class.trim().to_string())
55 .collect::<Vec<_>>()
56 })
57 .unwrap_or_default()
58 }
59
60 /// Returns whether this [`HtmlElement`] has the specified class.
61 pub fn has_class(&self, class: &str) -> bool {
62 self.has_any_classes(&[class])
63 }
64
65 /// Returns whether this [`HtmlElement`] has any of the specified classes.
66 pub fn has_any_classes(&self, classes: &[&str]) -> bool {
67 self.attrs.borrow().iter().any(|attr| {
68 attr.name.local.to_string() == "class"
69 && attr
70 .value
71 .split(' ')
72 .any(|class| classes.contains(&class.trim()))
73 })
74 }
75}