use std::fmt::Debug;
#[allow(unused_imports)]
use html5ever::namespace_url;
use html5ever::LocalName;
use html5ever::{local_name, ns, Attribute, QualName};
use selectors::attr::CaseSensitivity;
use tendril::StrTendril;
use super::NodeId;
use crate::entities::{into_tendril, wrap_attrs, wrap_tendril, Attr, StrWrap};
fn contains_class(classes: &str, target_class: &str) -> bool {
classes.split_ascii_whitespace().any(|c| c == target_class)
}
#[inline]
fn dedup_classes<'a>(source: &'a str, mut existing: Vec<&'a str>) -> StrTendril {
for cls in source.split_ascii_whitespace() {
if !existing.contains(&cls) {
existing.push(cls);
}
}
StrTendril::from(existing.join(" "))
}
#[derive(Debug, Clone)]
pub enum NodeData {
Document,
Fragment,
Doctype {
name: StrWrap,
public_id: StrWrap,
system_id: StrWrap,
},
Text { contents: StrWrap },
Comment { contents: StrWrap },
Element(Element),
ProcessingInstruction { target: StrWrap, contents: StrWrap },
}
#[derive(Debug, Clone)]
pub struct Element {
pub name: QualName,
pub attrs: Vec<Attr>,
pub template_contents: Option<NodeId>,
pub mathml_annotation_xml_integration_point: bool,
}
impl Element {
pub fn new(
name: QualName,
attrs: Vec<Attribute>,
template_contents: Option<NodeId>,
mathml_annotation_xml_integration_point: bool,
) -> Element {
Element {
name,
attrs: wrap_attrs(attrs),
template_contents,
mathml_annotation_xml_integration_point,
}
}
pub fn node_name(&self) -> StrTendril {
StrTendril::from(self.name.local.as_ref())
}
pub fn class(&self) -> Option<StrTendril> {
self.attrs
.iter()
.find(|a| a.name.local == local_name!("class"))
.map(|a| into_tendril(a.value.clone()))
}
pub fn id(&self) -> Option<StrTendril> {
self.attrs
.iter()
.find(|a| a.name.local == local_name!("id"))
.map(|a| into_tendril(a.value.clone()))
}
pub fn has_class(&self, class: &str) -> bool {
self.attr_ref(local_name!("class"))
.is_some_and(|class_val| contains_class(class_val, class))
}
pub fn has_class_bytes(&self, name: &[u8], case_sensitivity: CaseSensitivity) -> bool {
self.attr_ref(local_name!("class"))
.is_some_and(|class_val| {
class_val
.split_ascii_whitespace()
.any(|c| case_sensitivity.eq(name, c.as_bytes()))
})
}
pub fn add_class(&mut self, classes: &str) {
if classes.trim().is_empty() {
return;
}
let attr = self
.attrs
.iter_mut()
.find(|attr| attr.name.local == local_name!("class"));
match attr {
Some(attr) => {
let existing: Vec<&str> = attr.value.split_ascii_whitespace().collect();
let value = dedup_classes(classes, existing);
attr.value = wrap_tendril(value)
}
None => {
let value = dedup_classes(classes, Vec::new());
let name = QualName::new(None, ns!(), local_name!("class"));
self.attrs.push(Attr {
name,
value: wrap_tendril(value),
});
}
}
}
pub fn remove_class(&mut self, class: &str) {
if class.trim().is_empty() {
return;
}
if let Some(attr) = self
.attrs
.iter_mut()
.find(|attr| attr.name.local == local_name!("class"))
{
let excluding: Vec<&str> = class.split_ascii_whitespace().collect();
let mut existing: Vec<&str> = attr.value.split_ascii_whitespace().collect();
existing.retain(|x| !excluding.contains(x));
attr.value = wrap_tendril(StrTendril::from(existing.join(" ")));
}
}
pub fn attr(&self, name: &str) -> Option<StrTendril> {
self.attrs
.iter()
.find(|attr| &attr.name.local == name)
.map(|attr| into_tendril(attr.value.clone()))
}
pub fn set_attr(&mut self, name: &str, val: &str) {
let attr = self.attrs.iter_mut().find(|a| &a.name.local == name);
match attr {
Some(attr) => attr.value = wrap_tendril(StrTendril::from(val)),
None => {
let value = StrTendril::from(val);
let name = QualName::new(None, ns!(), LocalName::from(name));
self.attrs.push(Attr {
name,
value: wrap_tendril(value),
})
}
}
}
pub fn remove_attr(&mut self, name: &str) {
self.attrs.retain(|attr| &attr.name.local != name);
}
pub fn remove_attrs(&mut self, names: &[&str]) {
self.attrs
.retain(|attr| !names.contains(&attr.name.local.as_ref()));
}
pub fn retain_attrs(&mut self, names: &[&str]) {
self.attrs
.retain(|a| names.contains(&a.name.local.as_ref()));
}
pub fn remove_all_attrs(&mut self) {
self.attrs.clear();
}
pub fn has_attr(&self, name: &str) -> bool {
self.attrs.iter().any(|attr| &attr.name.local == name)
}
pub fn attr_ref(&self, local_name: LocalName) -> Option<&str> {
self.attrs
.iter()
.find(|a| a.name.local == local_name)
.map(|a| a.value.as_ref())
}
pub(crate) fn add_attrs_if_missing(&mut self, attrs: Vec<Attribute>) {
let attrs = wrap_attrs(attrs);
let existing_names = self
.attrs
.iter()
.map(|e| e.name.clone())
.collect::<Vec<_>>();
self.attrs.extend(
attrs
.into_iter()
.filter(|attr| !existing_names.contains(&attr.name)),
);
}
pub fn rename(&mut self, name: &str) {
let new_name = QualName::new(None, ns!(), LocalName::from(name));
self.name = new_name;
}
pub fn is_link(&self) -> bool {
matches!(
self.name.local,
local_name!("a") | local_name!("area") | local_name!("link")
) && self
.attrs
.iter()
.any(|a| a.name.local == local_name!("href"))
}
}