use alloc::string::String;
use alloc::vec::Vec;
use crate::node::NodeKind;
use crate::tags;
use crate::tree::{Dom, NodeRef};
impl Dom {
pub fn to_html(&self) -> String {
let mut out = String::with_capacity(self.len() * 16);
let mut stack: Vec<Step<'_>> = Vec::new();
for r in self.roots().collect::<Vec<_>>().into_iter().rev() {
stack.push(Step::Open(r));
}
process(&mut out, &mut stack);
out
}
}
impl<'a> NodeRef<'a> {
pub fn to_html(&self) -> String {
let mut out = String::new();
let mut stack: Vec<Step<'a>> = Vec::new();
stack.push(Step::Open(*self));
process(&mut out, &mut stack);
out
}
}
enum Step<'a> {
Open(NodeRef<'a>),
Close(&'a str),
}
fn process(out: &mut String, stack: &mut Vec<Step<'_>>) {
while let Some(step) = stack.pop() {
match step {
Step::Close(tag) => {
out.push_str("</");
out.push_str(tag);
out.push('>');
}
Step::Open(node) => write_open(out, node, stack),
}
}
}
fn write_open<'a>(out: &mut String, node: NodeRef<'a>, stack: &mut Vec<Step<'a>>) {
match node.kind() {
NodeKind::Element { tag } => {
out.push('<');
out.push_str(tag);
for (k, v) in node.attributes() {
out.push(' ');
out.push_str(k);
if !v.is_empty() {
out.push_str("=\"");
escape_attr(out, v);
out.push('"');
}
}
out.push('>');
if tags::is_void(tag) {
return; }
stack.push(Step::Close(tag));
let mut child = node.last_child();
while let Some(c) = child {
stack.push(Step::Open(c));
child = c.prev_sibling();
}
}
NodeKind::Text(t) => {
let raw = node
.parent()
.and_then(|p| p.tag_name())
.is_some_and(tags::is_raw_text);
if raw {
out.push_str(t);
} else {
escape_text(out, t);
}
}
NodeKind::Comment(c) => {
out.push_str("<!--");
out.push_str(c);
out.push_str("-->");
}
NodeKind::Doctype(d) => {
out.push_str("<!doctype ");
out.push_str(d);
out.push('>');
}
}
}
fn escape_text(out: &mut String, s: &str) {
for c in s.chars() {
match c {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
c => out.push(c),
}
}
}
fn escape_attr(out: &mut String, s: &str) {
for c in s.chars() {
match c {
'&' => out.push_str("&"),
'"' => out.push_str("""),
c => out.push(c),
}
}
}