use crate::parser::{Text, Token};
use super::*;
use core::fmt::Write;
pub fn render_as_html<'a>(tokens: impl AsRef<[Token<'a>]> + 'a) -> String {
let mut buf = String::new();
render_to_buffer(tokens, &mut buf);
buf
}
pub fn render_to_buffer<'a>(tokens: impl AsRef<[Token<'a>]> + 'a, buf: &mut String) {
let mut in_unordered_list = false;
let mut in_ordered_list = false;
let mut was_line_break = false;
let mut in_paragraph = false;
let tokens = tokens.as_ref();
let mut at = 0;
while at < tokens.len() {
let token = &tokens[at];
let is_unordered_item = matches!(token, Token::ListItem(None));
let is_ordered_item = matches!(token, Token::ListItem(Some(_)));
let is_line_break = matches!(token, Token::LineBreak);
let is_text = matches!(token, Token::Text(_) | Token::Url { .. });
let is_before_eof = at + 1 >= tokens.len();
if !in_unordered_list && is_unordered_item {
buf.push_str("<ul>\n");
in_unordered_list = true;
} else if (was_line_break || !is_line_break)
&& (!is_unordered_item || is_line_break)
&& in_unordered_list
{
buf.push_str("</ul>\n");
in_unordered_list = false;
}
if !in_ordered_list && is_ordered_item {
buf.push_str("<ol>\n");
in_ordered_list = true;
} else if (was_line_break || !is_line_break)
&& (!is_ordered_item || is_line_break)
&& in_ordered_list
{
buf.push_str("</ol>\n");
in_ordered_list = false;
}
if in_paragraph {
if is_before_eof {
if !is_line_break {
at = write_token_as_html(buf, tokens, at);
}
buf.push_str("</p>");
if is_line_break {
at = write_token_as_html(buf, tokens, at);
}
in_paragraph = false;
} else if !is_text
&& is_line_break
&& matches!(tokens.get(at + 1).unwrap(), Token::LineBreak)
{
buf.push_str("</p>");
in_paragraph = false;
} else {
at = write_token_as_html(buf, tokens, at);
}
} else if is_text {
buf.push_str("<p>");
in_paragraph = true;
} else {
at = write_token_as_html(buf, tokens, at);
}
was_line_break = is_line_break;
}
}
fn write_text<W: Write>(buf: &mut W, t: &Text) {
let Text {
value,
bold,
italic,
code,
} = t;
let (bold_s, bold_e) = bold.then(|| ("<b>", "</b>")).unwrap_or_default();
let (italic_s, italic_e) = italic.then(|| ("<i>", "</i>")).unwrap_or_default();
let (code_s, code_e) = code.then(|| ("<code>", "</code>")).unwrap_or_default();
write!(
buf,
"{}{}{}{}{}{}{}",
code_s, bold_s, italic_s, value, italic_e, bold_e, code_e
)
.unwrap()
}
fn write_until_line_break<W: Write>(buf: &mut W, tokens: &[Token], mut at: usize) -> usize {
while at < tokens.len() {
if matches!(&tokens[at], Token::LineBreak) {
break;
}
at = write_token_as_html(buf, tokens, at);
}
at
}
fn write_token_as_html<W: Write>(buf: &mut W, tokens: &[Token], mut at: usize) -> usize {
match &tokens[at] {
Token::Text(t) => write_text(buf, t),
Token::CodeFence { code, attrs: _ } => {
write!(buf, "<pre><code>{}</code></pre>", code).unwrap()
}
Token::Header(depth) => {
write!(buf, "<h{}>", depth).unwrap();
at += 1;
at = write_until_line_break(buf, tokens, at);
write!(buf, "</h{}>", depth).unwrap();
return at;
}
Token::Url {
name,
url,
is_image,
} => {
if *is_image {
write!(buf, r#"<img src="{}" alt=""#, url).unwrap();
if let Some(t) = name {
write_text(buf, t);
} else {
buf.write_str(url).unwrap();
}
buf.write_str(r#"">"#).unwrap()
} else {
write!(buf, r#"<a href="{}">"#, url).unwrap();
if let Some(t) = name {
write_text(buf, t);
} else {
buf.write_str(url).unwrap();
}
buf.write_str("</a>").unwrap()
}
}
Token::ListItem(place) => {
if let Some(place) = place {
write!(buf, "<li value=\"{}\">", place).unwrap();
} else {
buf.write_str("<li>").unwrap();
}
at += 1;
at = write_until_line_break(buf, tokens, at);
buf.write_str("</li>").unwrap();
return at;
}
Token::LineBreak => buf.write_char('\n').unwrap(),
Token::Custom(_) => {}
}
at + 1
}