use fmt::Debug;
use std::cell::OnceCell;
use std::{fmt, ops::Deref, slice::Iter as SliceIter};
use crate::{CaseSensitivity, HtmlStr};
use html5ever::{Attribute, LocalName, QualName};
use indexmap::IndexMap;
#[allow(variant_size_differences)]
#[derive(Clone, PartialEq, Eq)]
pub enum NodeKind {
Document,
Fragment,
Doctype(Doctype),
Comment(HtmlStr),
Text(HtmlStr),
Element(NodeData),
ProcessingInstruction(ProcessingInstruction),
}
impl NodeKind {
pub fn is_document(&self) -> bool {
matches!(*self, NodeKind::Document)
}
pub fn is_fragment(&self) -> bool {
matches!(*self, NodeKind::Fragment)
}
pub fn is_doctype(&self) -> bool {
matches!(*self, NodeKind::Doctype(_))
}
pub fn is_comment(&self) -> bool {
matches!(*self, NodeKind::Comment(_))
}
pub fn is_text(&self) -> bool {
matches!(*self, NodeKind::Text(_))
}
pub fn is_element(&self) -> bool {
matches!(*self, NodeKind::Element(_))
}
}
impl Debug for NodeKind {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
NodeKind::Document => write!(f, "Document"),
NodeKind::Fragment => write!(f, "Fragment"),
NodeKind::Doctype(d) => write!(f, "Doctype({:?})", d),
NodeKind::Comment(c) => write!(f, "Comment({:?})", c),
NodeKind::Text(t) => write!(f, "Text({:?})", t),
NodeKind::Element(e) => write!(f, "Element({:?})", e),
NodeKind::ProcessingInstruction(pi) => write!(f, "ProcessingInstruction({:?})", pi),
}
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Doctype {
pub name: HtmlStr,
pub public_id: HtmlStr,
pub system_id: HtmlStr,
}
impl Doctype {
pub fn name(&self) -> &str {
self.name.deref()
}
pub fn public_id(&self) -> &str {
self.public_id.deref()
}
pub fn system_id(&self) -> &str {
self.system_id.deref()
}
}
impl Debug for Doctype {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "<!DOCTYPE {} PUBLIC {:?} {:?}>", self.name(), self.public_id(), self.system_id())
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct NodeData {
pub(crate) name: QualName,
pub(crate) attrs: IndexMap<QualName, HtmlStr>,
id: OnceCell<Option<HtmlStr>>,
classes: OnceCell<Vec<LocalName>>,
}
impl NodeData {
#[doc(hidden)]
pub fn new(name: QualName, attributes: Vec<Attribute>) -> Self {
let attrs = attributes.into_iter().map(|a| (a.name, crate::tendril_util::make(a.value))).collect();
NodeData { attrs, name, id: OnceCell::new(), classes: OnceCell::new() }
}
pub fn is_a(&self, tag: &str) -> bool {
self.name.local.as_ref().eq_ignore_ascii_case(tag)
}
pub fn name(&self) -> &str {
self.name.local.deref()
}
pub fn id(&self) -> Option<&str> {
self.id
.get_or_init(|| self.attrs.iter().find(|(name, _)| name.local.as_ref() == "id").map(|(_, value)| value.clone()))
.as_deref()
}
pub fn has_class(&self, class: &str) -> bool {
self.classes().any(|c| CaseSensitivity::AsciiCaseInsensitive.eq(c.as_bytes(), class.as_bytes()))
}
pub fn classes(&self) -> HtmlClasses {
let classes = self.classes.get_or_init(|| {
let mut classes: Vec<LocalName> = 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
});
HtmlClasses { inner: classes.iter() }
}
pub fn get_attribute(&self, attr: &str) -> Option<&str> {
let qualname = QualName::new(None, ns!(), LocalName::from(attr));
self.attrs.get(&qualname).map(Deref::deref)
}
pub fn has_attribute(&self, attr: &str) -> bool {
let qualname = QualName::new(None, ns!(), LocalName::from(attr));
self.attrs.contains_key(&qualname)
}
pub fn attributes(&self) -> HtmlAttributes {
HtmlAttributes { inner: self.attrs.iter() }
}
}
#[allow(missing_debug_implementations)]
#[derive(Clone)]
pub struct HtmlClasses<'a> {
inner: SliceIter<'a, LocalName>,
}
impl<'a> Iterator for HtmlClasses<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<&'a str> {
self.inner.next().map(Deref::deref)
}
}
pub type AttributesIter<'a> = indexmap::map::Iter<'a, QualName, HtmlStr>;
#[allow(missing_debug_implementations)]
#[derive(Clone)]
pub struct HtmlAttributes<'a> {
inner: AttributesIter<'a>,
}
impl<'a> Iterator for HtmlAttributes<'a> {
type Item = (&'a str, &'a str);
fn next(&mut self) -> Option<(&'a str, &'a str)> {
self.inner.next().map(|(k, v)| (k.local.deref(), v.deref()))
}
}
impl Debug for NodeData {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "<{}", self.name())?;
for (key, value) in self.attributes() {
write!(f, " {}={:?}", key, value)?;
}
write!(f, ">")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProcessingInstruction {
pub target: HtmlStr,
pub data: HtmlStr,
}
impl Deref for ProcessingInstruction {
type Target = str;
fn deref(&self) -> &str {
self.data.deref()
}
}
pub(crate) mod serializable;