#![deny(unsafe_code)]
use std::fmt::Debug;
use std::hash::Hash;
use std::ops::ControlFlow;
#[cfg(feature = "capture")]
use markup5ever::Prefix;
pub use markup5ever::interface::QuirksMode;
pub use markup5ever::{LocalName, Namespace, QualName};
#[cfg(feature = "capture")]
use serde::{Deserialize, Serialize};
pub trait LayoutDom {
type NodeId: Copy + Eq + Hash + Debug + 'static;
fn document(&self) -> Self::NodeId;
fn is_live(&self, _id: Self::NodeId) -> bool {
true
}
fn quirks_mode(&self) -> QuirksMode {
QuirksMode::NoQuirks
}
fn parent(&self, id: Self::NodeId) -> Option<Self::NodeId>;
fn prev_sibling(&self, id: Self::NodeId) -> Option<Self::NodeId>;
fn next_sibling(&self, id: Self::NodeId) -> Option<Self::NodeId>;
fn dom_children(&self, id: Self::NodeId) -> impl Iterator<Item = Self::NodeId> + '_;
fn flat_children(&self, id: Self::NodeId) -> impl Iterator<Item = Self::NodeId> + '_ {
self.dom_children(id)
}
fn kind(&self, id: Self::NodeId) -> NodeKind;
fn opaque_id(&self, id: Self::NodeId) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
let mut hasher = DefaultHasher::new();
id.hash(&mut hasher);
hasher.finish()
}
fn element_name(&self, id: Self::NodeId) -> Option<&QualName>;
fn attribute(&self, id: Self::NodeId, ns: &Namespace, local: &LocalName) -> Option<&str>;
fn attributes(&self, id: Self::NodeId) -> impl Iterator<Item = AttributeView<'_>> + '_;
fn text(&self, id: Self::NodeId) -> Option<&str>;
fn walk<V>(&self, visitor: &mut V) -> ControlFlow<V::Stop>
where
V: NodeVisitor<Self> + ?Sized,
{
walk_subtree(self, self.document(), visitor)
}
fn has_class(&self, id: Self::NodeId, class: &str) -> bool {
self.attributes(id).any(|a| {
a.name.local.as_ref() == "class" && a.value.split_whitespace().any(|c| c == class)
})
}
fn first_with_class(&self, id: Self::NodeId, class: &str) -> Option<Self::NodeId> {
if self.has_class(id, class) {
return Some(id);
}
self.dom_children(id)
.find_map(|c| self.first_with_class(c, class))
}
fn all_with_class(&self, id: Self::NodeId, class: &str) -> Vec<Self::NodeId> {
let mut out = Vec::new();
if self.has_class(id, class) {
out.push(id);
}
for child in self.dom_children(id) {
out.extend(self.all_with_class(child, class));
}
out
}
fn first_tag(&self, id: Self::NodeId, local: &str) -> Option<Self::NodeId> {
if self
.element_name(id)
.is_some_and(|q| q.local.as_ref() == local)
{
return Some(id);
}
self.dom_children(id).find_map(|c| self.first_tag(c, local))
}
}
pub trait LayoutDomMut: LayoutDom {
fn create_element(&mut self, name: QualName) -> Self::NodeId;
fn create_text(&mut self, data: &str) -> Self::NodeId;
fn append_child(&mut self, parent: Self::NodeId, child: Self::NodeId);
fn insert_before(
&mut self,
parent: Self::NodeId,
child: Self::NodeId,
reference: Option<Self::NodeId>,
);
fn move_before(
&mut self,
parent: Self::NodeId,
child: Self::NodeId,
reference: Option<Self::NodeId>,
);
fn remove(&mut self, node: Self::NodeId);
fn set_attribute(&mut self, node: Self::NodeId, name: QualName, value: &str);
fn remove_attribute(&mut self, node: Self::NodeId, name: QualName);
fn set_text(&mut self, node: Self::NodeId, data: &str);
fn set_inner_html(&mut self, node: Self::NodeId, html: &str);
fn drain_mutations(&mut self, out: &mut Vec<DomMutation<Self::NodeId>>);
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DomMutation<Id> {
Inserted { node: Id, parent: Id },
Removed { node: Id, former_parent: Id },
AttributeChanged {
node: Id,
name: QualName,
old_value: Option<String>,
},
CharacterDataChanged { node: Id },
SubtreeReplaced { node: Id },
Moved {
node: Id,
from_parent: Id,
to_parent: Id,
},
}
#[cfg(feature = "capture")]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct CapturedQualName {
pub prefix: Option<String>,
pub ns: String,
pub local: String,
}
#[cfg(feature = "capture")]
impl From<&QualName> for CapturedQualName {
fn from(value: &QualName) -> Self {
Self {
prefix: value.prefix.as_ref().map(ToString::to_string),
ns: value.ns.to_string(),
local: value.local.to_string(),
}
}
}
#[cfg(feature = "capture")]
impl CapturedQualName {
pub fn into_qual_name(self) -> QualName {
QualName::new(
self.prefix.map(Prefix::from),
Namespace::from(self.ns),
LocalName::from(self.local),
)
}
}
#[cfg(feature = "capture")]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum CapturedMutation {
Inserted {
node: u64,
parent: u64,
},
Removed {
node: u64,
former_parent: u64,
},
AttributeChanged {
node: u64,
name: CapturedQualName,
old_value: Option<String>,
},
CharacterDataChanged {
node: u64,
},
SubtreeReplaced {
node: u64,
},
Moved {
node: u64,
from_parent: u64,
to_parent: u64,
},
}
#[cfg(feature = "capture")]
impl CapturedMutation {
pub fn capture<Id>(mutation: &DomMutation<Id>, to_raw: impl Fn(&Id) -> u64) -> Self {
match mutation {
DomMutation::Inserted { node, parent } => Self::Inserted {
node: to_raw(node),
parent: to_raw(parent),
},
DomMutation::Removed {
node,
former_parent,
} => Self::Removed {
node: to_raw(node),
former_parent: to_raw(former_parent),
},
DomMutation::AttributeChanged {
node,
name,
old_value,
} => Self::AttributeChanged {
node: to_raw(node),
name: CapturedQualName::from(name),
old_value: old_value.clone(),
},
DomMutation::CharacterDataChanged { node } => {
Self::CharacterDataChanged { node: to_raw(node) }
},
DomMutation::SubtreeReplaced { node } => Self::SubtreeReplaced { node: to_raw(node) },
DomMutation::Moved {
node,
from_parent,
to_parent,
} => Self::Moved {
node: to_raw(node),
from_parent: to_raw(from_parent),
to_parent: to_raw(to_parent),
},
}
}
pub fn replay<Id>(self, from_raw: impl Fn(u64) -> Id) -> DomMutation<Id> {
match self {
Self::Inserted { node, parent } => DomMutation::Inserted {
node: from_raw(node),
parent: from_raw(parent),
},
Self::Removed {
node,
former_parent,
} => DomMutation::Removed {
node: from_raw(node),
former_parent: from_raw(former_parent),
},
Self::AttributeChanged {
node,
name,
old_value,
} => DomMutation::AttributeChanged {
node: from_raw(node),
name: name.into_qual_name(),
old_value,
},
Self::CharacterDataChanged { node } => DomMutation::CharacterDataChanged {
node: from_raw(node),
},
Self::SubtreeReplaced { node } => DomMutation::SubtreeReplaced {
node: from_raw(node),
},
Self::Moved {
node,
from_parent,
to_parent,
} => DomMutation::Moved {
node: from_raw(node),
from_parent: from_raw(from_parent),
to_parent: from_raw(to_parent),
},
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum NodeKind {
Document,
Doctype,
Element,
Text,
Comment,
ProcessingInstruction,
DocumentFragment,
}
#[derive(Clone, Copy, Debug)]
pub struct AttributeView<'a> {
pub name: &'a QualName,
pub value: &'a str,
}
pub trait NodeVisitor<D: LayoutDom + ?Sized> {
type Stop;
fn enter(&mut self, _dom: &D, _id: D::NodeId) -> ControlFlow<Self::Stop, Descent> {
ControlFlow::Continue(Descent::Descend)
}
fn exit(&mut self, _dom: &D, _id: D::NodeId) -> ControlFlow<Self::Stop> {
ControlFlow::Continue(())
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Descent {
Descend,
Skip,
}
pub fn walk_subtree<D, V>(dom: &D, root: D::NodeId, visitor: &mut V) -> ControlFlow<V::Stop>
where
D: LayoutDom + ?Sized,
V: NodeVisitor<D> + ?Sized,
{
match visitor.enter(dom, root)? {
Descent::Skip => ControlFlow::Continue(()),
Descent::Descend => {
for child in dom.dom_children(root) {
walk_subtree(dom, child, visitor)?;
}
visitor.exit(dom, root)
},
}
}