use hashbrown::{hash_map::Iter, hash_set, HashMap, HashSet};
use fast_html5ever::{Attribute, LocalName, QualName};
use selectors::attr::CaseSensitivity;
use std::fmt;
use std::ops::Deref;
use fast_html5ever::tendril::{fmt::UTF8, Atomic, Tendril};
pub type AtomicStrTendril = Tendril<UTF8, Atomic>;
#[allow(variant_size_differences)]
#[derive(Clone)]
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)]
pub struct Doctype {
pub name: AtomicStrTendril,
pub public_id: AtomicStrTendril,
pub system_id: AtomicStrTendril,
}
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)]
pub struct Comment {
pub comment: AtomicStrTendril,
}
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: AtomicStrTendril,
}
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())
}
}
pub type Attributes = HashMap<QualName, AtomicStrTendril>;
#[derive(Clone, PartialEq, Eq)]
pub struct Element {
pub name: QualName,
pub id: Option<LocalName>,
pub classes: HashSet<LocalName>,
pub attrs: Attributes,
}
impl Element {
#[doc(hidden)]
pub fn new(name: QualName, attributes: Vec<Attribute>) -> Self {
let mut classes: HashSet<LocalName> = HashSet::new();
let mut attrs: HashMap<QualName, AtomicStrTendril> =
HashMap::with_capacity(attributes.len());
let mut id: Option<LocalName> = 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, a.value.into_send().into());
}
Element {
attrs,
name,
id,
classes,
}
}
pub fn name(&self) -> &str {
self.name.local.deref()
}
pub fn id(&self) -> Option<&str> {
self.id.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 {
Classes {
inner: self.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: hash_set::Iter<'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)
}
}
pub type AttributesIter<'a> = Iter<'a, QualName, AtomicStrTendril>;
#[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: AtomicStrTendril,
pub data: AtomicStrTendril,
}
impl Deref for ProcessingInstruction {
type Target = str;
fn deref(&self) -> &str {
self.data.deref()
}
}
pub(crate) mod serializable;