use crate::{Element, HtmlElement, TextElement};
pub trait Visitor: Sized {
type Error;
fn visit(&mut self, element: &HtmlElement) -> Result<(), Self::Error> {
walk_element(self, element)
}
fn visit_text(&mut self, _text: &str) -> Result<(), Self::Error> {
Ok(())
}
fn visit_attr(&mut self, _name: &str, _value: &str) -> Result<(), Self::Error> {
Ok(())
}
fn visit_children(&mut self, children: &[Element]) -> Result<(), Self::Error> {
walk_children(self, children)
}
}
pub fn walk_element<V: Visitor>(visitor: &mut V, element: &HtmlElement) -> Result<(), V::Error> {
for (name, value) in &element.attrs {
visitor.visit_attr(name, value)?;
}
visitor.visit_children(&element.children)?;
Ok(())
}
pub fn walk_children<V: Visitor>(visitor: &mut V, children: &[Element]) -> Result<(), V::Error> {
for child in children {
match child {
Element::Html(element) => visitor.visit(element)?,
Element::Text(TextElement { text }) => visitor.visit_text(text)?,
}
}
Ok(())
}
pub trait MutVisitor: Sized {
type Error;
fn visit(&mut self, element: &mut HtmlElement) -> Result<(), Self::Error> {
noop_visit_element(self, element)
}
fn visit_text(&mut self, _text: &mut String) -> Result<(), Self::Error> {
Ok(())
}
fn visit_attr(&mut self, _name: &str, _value: &mut String) -> Result<(), Self::Error> {
Ok(())
}
fn visit_children(&mut self, children: &mut [Element]) -> Result<(), Self::Error> {
noop_visit_children(self, children)
}
}
pub fn noop_visit_element<V: MutVisitor>(
visitor: &mut V,
element: &mut HtmlElement,
) -> Result<(), V::Error> {
for (name, value) in &mut element.attrs {
visitor.visit_attr(name, value)?;
}
visitor.visit_children(&mut element.children)?;
Ok(())
}
pub fn noop_visit_children<V: MutVisitor>(
visitor: &mut V,
children: &mut [Element],
) -> Result<(), V::Error> {
for child in children {
match child {
Element::Html(element) => visitor.visit(element)?,
Element::Text(TextElement { text }) => visitor.visit_text(text)?,
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use insta::assert_yaml_snapshot;
use crate::renderer::HtmlElementRenderer;
use crate::*;
use super::*;
#[test]
fn test_mut_visitor_attribute_replacement() {
struct ReplaceAttr;
impl MutVisitor for ReplaceAttr {
type Error = ();
fn visit_attr(&mut self, name: &str, value: &mut String) -> Result<(), Self::Error> {
if name == "href" {
*value = format!("https://example.com/{value}");
}
Ok(())
}
}
let mut element = html().child(head().child(title())).child(
body().child(
ul().child(li().child(a().href("home")))
.child(li().child(a().href("features")))
.child(li().child(a().href("about"))),
),
);
let mut visitor = ReplaceAttr;
visitor.visit(&mut element).unwrap();
assert_yaml_snapshot!(HtmlElementRenderer::new()
.render_to_string(&element)
.unwrap());
}
}