diff --git a/Cargo.lock b/Cargo.lock index af19dedf..c221679d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -527,6 +527,7 @@ dependencies = [ "html5ever", "indexmap", "matches", + "once_cell", "selectors", "smallvec", "tendril", diff --git a/Cargo.toml b/Cargo.toml index 57c822be..aedc844a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ smallvec = "1.10.0" tendril = "0.4.3" ahash = "0.8" indexmap = { version = "1.9.2", optional = true } +once_cell = "1.0" [dependencies.getopts] version = "0.2.21" diff --git a/src/element_ref/element.rs b/src/element_ref/element.rs index 6998dc1f..07a76c79 100644 --- a/src/element_ref/element.rs +++ b/src/element_ref/element.rs @@ -106,8 +106,8 @@ impl<'a> Element for ElementRef<'a> { } fn has_id(&self, id: &CssLocalName, case_sensitivity: CaseSensitivity) -> bool { - match self.value().id { - Some(ref val) => case_sensitivity.eq(id.0.as_bytes(), val.as_bytes()), + match self.value().id() { + Some(val) => case_sensitivity.eq(id.0.as_bytes(), val.as_bytes()), None => false, } } diff --git a/src/node.rs b/src/node.rs index 428a0553..f5273262 100644 --- a/src/node.rs +++ b/src/node.rs @@ -2,16 +2,15 @@ #[cfg(not(feature = "deterministic"))] use ahash::AHashMap as HashMap; -use ahash::AHashSet as HashSet; #[cfg(not(feature = "deterministic"))] use std::collections::hash_map; -use std::collections::hash_set; use std::fmt; use std::ops::Deref; +use std::slice::Iter as SliceIter; -use crate::StrTendril; +use crate::{CaseSensitivity, StrTendril}; use html5ever::{Attribute, LocalName, QualName}; -use selectors::attr::CaseSensitivity; +use once_cell::unsync::OnceCell; /// An HTML node. // `Element` is usally the most common variant and hence boxing it @@ -228,41 +227,27 @@ pub struct Element { /// The element name. pub name: QualName, - /// The element ID. - pub id: Option, - - /// The element classes. - pub classes: HashSet, - /// The element attributes. pub attrs: Attributes, + + id: OnceCell>, + + classes: OnceCell>, } impl Element { #[doc(hidden)] pub fn new(name: QualName, attributes: Vec) -> Self { - let mut classes: HashSet = HashSet::new(); - let mut attrs = Attributes::with_capacity(attributes.len()); - let mut id: Option = None; - - for a in attributes { - match a.name.local.deref() { - "id" => { - id = Some(LocalName::from(a.value.deref())); - } - "class" => { - classes.extend(a.value.deref().split_whitespace().map(LocalName::from)); - } - _ => (), - }; - attrs.insert(a.name, crate::tendril_util::make(a.value)); - } + let attrs = attributes + .into_iter() + .map(|a| (a.name, crate::tendril_util::make(a.value))) + .collect(); Element { attrs, name, - id, - classes, + id: OnceCell::new(), + classes: OnceCell::new(), } } @@ -273,7 +258,14 @@ impl Element { /// Returns the element ID. pub fn id(&self) -> Option<&str> { - self.id.as_deref() + self.id + .get_or_init(|| { + self.attrs + .iter() + .find(|(name, _)| name.local.as_ref() == "id") + .map(|(_, value)| value.clone()) + }) + .as_deref() } /// Returns true if element has the class. @@ -284,8 +276,22 @@ impl Element { /// Returns an iterator over the element's classes. pub fn classes(&self) -> Classes { + let classes = self.classes.get_or_init(|| { + let mut classes: Vec = self + .attrs + .iter() + .filter(|(name, _)| name.local.as_ref() == "class") + .flat_map(|(_, value)| value.split_whitespace().map(LocalName::from)) + .collect(); + + classes.sort_unstable(); + classes.dedup(); + + classes + }); + Classes { - inner: self.classes.iter(), + inner: classes.iter(), } } @@ -307,7 +313,7 @@ impl Element { #[allow(missing_debug_implementations)] #[derive(Clone)] pub struct Classes<'a> { - inner: hash_set::Iter<'a, LocalName>, + inner: SliceIter<'a, LocalName>, } impl<'a> Iterator for Classes<'a> {