#[cfg(not(feature = "deterministic"))]
use ahash::AHashMap as HashMap;
#[cfg(not(feature = "deterministic"))]
use std::collections::hash_map;
use std::fmt;
use std::ops::Deref;
use std::slice::Iter as SliceIter;
use crate::{CaseSensitivity, StrTendril};
use html5ever::{Attribute, LocalName, QualName};
use once_cell::unsync::OnceCell;
#[allow(variant_size_differences)]
#[derive(Clone, PartialEq, Eq)]
pub enum Node {
Document,
Fragment,
Doctype(Doctype),
Comment(Comment),
Text(Text),
Element(Element),
ProcessingInstruction(ProcessingInstruction),
}
impl Node {
pub fn is_document(&self) -> bool {
matches!(*self, Node::Document)
}
pub fn is_fragment(&self) -> bool {
matches!(*self, Node::Fragment)
}
pub fn is_doctype(&self) -> bool {
matches!(*self, Node::Doctype(_))
}
pub fn is_comment(&self) -> bool {
matches!(*self, Node::Comment(_))
}
pub fn is_text(&self) -> bool {
matches!(*self, Node::Text(_))
}
pub fn is_element(&self) -> bool {
matches!(*self, Node::Element(_))
}
pub fn as_doctype(&self) -> Option<&Doctype> {
match *self {
Node::Doctype(ref d) => Some(d),
_ => None,
}
}
pub fn as_comment(&self) -> Option<&Comment> {
match *self {
Node::Comment(ref c) => Some(c),
_ => None,
}
}
pub fn as_text(&self) -> Option<&Text> {
match *self {
Node::Text(ref t) => Some(t),
_ => None,
}
}
pub fn as_element(&self) -> Option<&Element> {
match *self {
Node::Element(ref e) => Some(e),
_ => None,
}
}
pub fn as_processing_instruction(&self) -> Option<&ProcessingInstruction> {
match *self {
Node::ProcessingInstruction(ref pi) => Some(pi),
_ => None,
}
}
}
impl fmt::Debug for Node {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
Node::Document => write!(f, "Document"),
Node::Fragment => write!(f, "Fragment"),
Node::Doctype(ref d) => write!(f, "Doctype({:?})", d),
Node::Comment(ref c) => write!(f, "Comment({:?})", c),
Node::Text(ref t) => write!(f, "Text({:?})", t),
Node::Element(ref e) => write!(f, "Element({:?})", e),
Node::ProcessingInstruction(ref pi) => write!(f, "ProcessingInstruction({:?})", pi),
}
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Doctype {
pub name: StrTendril,
pub public_id: StrTendril,
pub system_id: StrTendril,
}
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 fmt::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 Comment {
pub comment: StrTendril,
}
impl Deref for Comment {
type Target = str;
fn deref(&self) -> &str {
self.comment.deref()
}
}
impl fmt::Debug for Comment {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "<!-- {:?} -->", self.deref())
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Text {
pub text: StrTendril,
}
impl Deref for Text {
type Target = str;
fn deref(&self) -> &str {
self.text.deref()
}
}
impl fmt::Debug for Text {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{:?}", self.deref())
}
}
#[cfg(feature = "deterministic")]
pub type Attributes = indexmap::IndexMap<QualName, StrTendril>;
#[cfg(not(feature = "deterministic"))]
pub type Attributes = HashMap<QualName, StrTendril>;
#[derive(Clone, PartialEq, Eq)]
pub struct Element {
pub name: QualName,
pub attrs: Attributes,
id: OnceCell<Option<StrTendril>>,
classes: OnceCell<Vec<LocalName>>,
}
impl Element {
#[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();
Element {
attrs,
name,
id: OnceCell::new(),
classes: OnceCell::new(),
}
}
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, case_sensitive: CaseSensitivity) -> bool {
self.classes()
.any(|c| case_sensitive.eq(c.as_bytes(), class.as_bytes()))
}
pub fn classes(&self) -> Classes {
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
});
Classes {
inner: classes.iter(),
}
}
pub fn attr(&self, attr: &str) -> Option<&str> {
let qualname = QualName::new(None, ns!(), LocalName::from(attr));
self.attrs.get(&qualname).map(Deref::deref)
}
pub fn attrs(&self) -> Attrs {
Attrs {
inner: self.attrs.iter(),
}
}
}
#[allow(missing_debug_implementations)]
#[derive(Clone)]
pub struct Classes<'a> {
inner: SliceIter<'a, LocalName>,
}
impl<'a> Iterator for Classes<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<&'a str> {
self.inner.next().map(Deref::deref)
}
}
#[cfg(feature = "deterministic")]
pub type AttributesIter<'a> = indexmap::map::Iter<'a, QualName, StrTendril>;
#[cfg(not(feature = "deterministic"))]
pub type AttributesIter<'a> = hash_map::Iter<'a, QualName, StrTendril>;
#[allow(missing_debug_implementations)]
#[derive(Clone)]
pub struct Attrs<'a> {
inner: AttributesIter<'a>,
}
impl<'a> Iterator for Attrs<'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 fmt::Debug for Element {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "<{}", self.name())?;
for (key, value) in self.attrs() {
write!(f, " {}={:?}", key, value)?;
}
write!(f, ">")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProcessingInstruction {
pub target: StrTendril,
pub data: StrTendril,
}
impl Deref for ProcessingInstruction {
type Target = str;
fn deref(&self) -> &str {
self.data.deref()
}
}
pub(crate) mod serializable;