use {
super::safe_link_target,
crate::ast::{Ast, Node},
};
pub trait Renderer {
fn format_text(&mut self, text: &str, html: &mut String) {
push_escaped(html, text);
}
fn format_preformatted(&mut self, text: &str, html: &mut String) {
push_escaped(html, text);
}
fn list_item_separator(&self) -> &'static str { "" }
fn render_node(&mut self, _node: &Node, _html: &mut String) -> bool { false }
fn finish(&mut self, _html: &mut String) {}
}
impl Renderer for () {}
#[must_use]
pub fn from_ast(source: &Ast, renderer: &mut impl Renderer) -> String {
render(source.inner(), renderer)
}
#[must_use]
pub fn from_string(
source: impl AsRef<str>,
renderer: &mut impl Renderer,
) -> String {
from_ast(&Ast::from_string(source), renderer)
}
fn push_escaped(html: &mut String, value: &str) {
for character in value.chars() {
match character {
'&' => html.push_str("&"),
'<' => html.push_str("<"),
'>' => html.push_str(">"),
'"' => html.push_str("""),
'\'' => html.push_str("'"),
_ => html.push(character),
}
}
}
pub(super) fn convert(source: &[Node]) -> String { render(source, &mut ()) }
fn render(source: &[Node], renderer: &mut impl Renderer) -> String {
let mut html = String::new();
for node in source {
if renderer.render_node(node, &mut html) {
continue;
}
match node {
Node::Text(text) => {
html.push_str("<p>");
renderer.format_text(text, &mut html);
html.push_str("</p>");
}
Node::Link { to, text } => {
let label = text.as_deref().unwrap_or(to);
if safe_link_target(to) {
html.push_str("<a href=\"");
push_escaped(&mut html, to);
html.push_str("\">");
renderer.format_text(label, &mut html);
html.push_str("</a><br>");
} else {
renderer.format_text(label, &mut html);
html.push_str("<br>");
}
}
Node::Heading { level, text } => {
let tag = match level {
1 => "h1",
2 => "h2",
3 => "h3",
_ => "p",
};
html.push('<');
html.push_str(tag);
html.push('>');
renderer.format_text(text, &mut html);
html.push_str("</");
html.push_str(tag);
html.push('>');
}
Node::List(items) => {
html.push_str("<ul>");
for (index, item) in items.iter().enumerate() {
if index > 0 {
html.push_str(renderer.list_item_separator());
}
html.push_str("<li>");
renderer.format_text(item, &mut html);
html.push_str("</li>");
}
html.push_str("</ul>");
}
Node::Blockquote(text) => {
html.push_str("<blockquote>");
renderer.format_text(text, &mut html);
html.push_str("</blockquote>");
}
Node::PreformattedText { text, .. } => {
html.push_str("<pre>");
renderer.format_preformatted(text, &mut html);
html.push_str("</pre>");
}
Node::Whitespace => {}
}
}
renderer.finish(&mut html);
html
}