use std::collections::HashMap;
use std::fmt::Debug;
use crate::common::utils::escape_html;
use crate::parser::extset::RenderExtSet;
use crate::Node;
pub trait Renderer {
fn open(&mut self, tag: &str, attrs: &[(&str, String)]);
fn close(&mut self, tag: &str);
fn self_close(&mut self, tag: &str, attrs: &[(&str, String)]);
fn contents(&mut self, nodes: &[Node]);
fn cr(&mut self);
fn text(&mut self, text: &str);
fn text_raw(&mut self, text: &str);
fn ext(&mut self) -> &mut RenderExtSet;
}
#[derive(Debug, Default)]
pub(crate) struct HTMLRenderer<const XHTML: bool> {
result: String,
ext: RenderExtSet,
}
impl<const XHTML: bool> HTMLRenderer<XHTML> {
pub fn new() -> Self {
Self {
result: String::new(),
ext: RenderExtSet::new(),
}
}
pub fn render(&mut self, node: &Node) {
node.node_value.render(node, self);
}
fn make_attr(&mut self, name: &str, value: &str) {
self.result.push(' ');
self.result.push_str(&escape_html(name));
self.result.push('=');
self.result.push('"');
self.result.push_str(&escape_html(value));
self.result.push('"');
}
fn make_attrs(&mut self, attrs: &[(&str, String)]) {
let mut attr_hash = HashMap::new();
let mut attr_order = Vec::with_capacity(attrs.len());
for (name, value) in attrs {
let entry = attr_hash.entry(*name).or_insert(Vec::new());
entry.push(value.as_str());
attr_order.push(*name);
}
for name in attr_order {
let Some(value) = attr_hash.remove(name) else { continue; };
if name == "class" {
self.make_attr(name, &value.join(" "));
} else if name == "style" {
self.make_attr(name, &value.join(";"));
} else {
for v in value {
self.make_attr(name, v);
}
}
}
}
}
impl<const XHTML: bool> From<HTMLRenderer<XHTML>> for String {
fn from(f: HTMLRenderer<XHTML>) -> Self {
#[cold]
fn replace_null(input: String) -> String {
input.replace('\0', "\u{FFFD}")
}
if f.result.contains('\0') {
replace_null(f.result)
} else {
f.result
}
}
}
impl<const XHTML: bool> Renderer for HTMLRenderer<XHTML> {
fn open(&mut self, tag: &str, attrs: &[(&str, String)]) {
self.result.push('<');
self.result.push_str(tag);
self.make_attrs(attrs);
self.result.push('>');
}
fn close(&mut self, tag: &str) {
self.result.push('<');
self.result.push('/');
self.result.push_str(tag);
self.result.push('>');
}
fn self_close(&mut self, tag: &str, attrs: &[(&str, String)]) {
self.result.push('<');
self.result.push_str(tag);
self.make_attrs(attrs);
if XHTML {
self.result.push(' ');
self.result.push('/');
}
self.result.push('>');
}
fn contents(&mut self, nodes: &[Node]) {
for node in nodes.iter() {
self.render(node);
}
}
fn cr(&mut self) {
match self.result.as_bytes().last() {
Some(b'\n') | None => {}
Some(_) => self.result.push('\n')
}
}
fn text(&mut self, text: &str) {
self.result.push_str(&escape_html(text));
}
fn text_raw(&mut self, text: &str) {
self.result.push_str(text);
}
fn ext(&mut self) -> &mut RenderExtSet {
&mut self.ext
}
}