use crate::encode;
use crate::Attribute;
use std::fmt::Display;
#[derive(Debug, Clone)]
pub struct Element {
tag: String,
attributes: Vec<Attribute>,
children: Vec<Node>,
}
impl Element {
fn is_void(&self) -> bool {
[
"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source",
"track", "wbr", "!doctype",
]
.contains(&self.tag.to_lowercase().as_str())
}
}
#[derive(Debug, Clone)]
pub struct Text(String);
#[derive(Debug, Clone)]
pub struct Fragment(Vec<Node>);
#[derive(Debug, Clone)]
pub enum Node {
Element(Element),
Text(Text),
RawText(Text),
Fragment(Fragment),
}
pub fn text(text: impl Display) -> Node {
Node::Text(Text(text.to_string()))
}
pub fn raw_text(text: impl Display) -> Node {
Node::RawText(Text(text.to_string()))
}
enum Tag<'n> {
Open(&'n Node),
Close(&'n Element),
}
impl Node {
#[doc(hidden)]
pub fn element(tag: String, attributes: Vec<Attribute>) -> Node {
let tag = tag.to_ascii_lowercase();
let tag = if tag == "doctype" {
String::from("!doctype")
} else {
tag
};
let tag_override = attributes.iter().find_map(|attr| match attr {
Attribute::Regular("data_tagname", t) => Some(t.clone()),
_ => None,
});
Node::Element(Element {
tag: tag_override.unwrap_or(tag),
attributes,
children: vec![],
})
}
#[doc(hidden)]
pub fn fragment() -> Node {
Node::Fragment(Fragment(vec![]))
}
#[doc(hidden)]
pub fn append_child(&mut self, child: Node) {
match self {
Node::Fragment(Fragment(nodes)) => nodes.push(child),
Node::Element(Element { children, .. }) => children.push(child),
Node::Text(_) | Node::RawText(_) => panic!("cannot add child to text node"),
}
}
}
impl Display for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut visit_later = vec![Tag::Open(self)];
while let Some(t) = visit_later.pop() {
match t {
Tag::Open(Node::Text(Text(s))) => {
write!(f, "{}", encode::html(s))?;
}
Tag::Open(Node::RawText(Text(s))) => {
write!(f, "{s}")?;
}
Tag::Open(Node::Element(el)) => {
let attributes = el
.attributes
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join("");
write!(f, "<{}{}>", el.tag.replace('_', "-"), attributes)?;
if el.is_void() {
continue;
}
visit_later.push(Tag::Close(el));
for child in el.children.iter().rev() {
visit_later.push(Tag::Open(child));
}
}
Tag::Open(Node::Fragment(fragment)) => {
for child in fragment.0.iter().rev() {
visit_later.push(Tag::Open(child));
}
}
Tag::Close(el) => {
write!(f, "</{}>", el.tag.replace('_', "-"))?;
}
}
}
Ok(())
}
}
impl From<Node> for String {
fn from(value: Node) -> Self {
value.to_string()
}
}
impl<T> From<T> for Node
where
T: IntoIterator<Item = Node>,
{
fn from(value: T) -> Self {
Node::Fragment(Fragment(value.into_iter().collect::<Vec<Node>>()))
}
}