///|
pub fn render_document(
blocks : Array[@parsing.Block],
target~ : KatexTarget,
disable_math? : Bool = false,
) -> String {
blocks.map(b => render_block(b, target~, disable_math~)).join("\n")
}
///|
pub fn render_pre(text : String) -> String {
"\{open_tag("pre", "")}\{escape_html(text)}\{close_tag("pre")}"
}
///|
fn escape_html(text : String) -> String {
text
.replace_all(old="&", new="&")
.replace_all(old="<", new="<")
.replace_all(old=">", new=">")
.replace_all(old="\"", new=""")
}
///|
fn attr(name : String, value : String) -> String {
" \{name}=\"\{escape_html(value)}\""
}
///|
fn open_tag(name : String, attrs : String) -> String {
"<\{name}\{attrs}>"
}
///|
fn close_tag(name : String) -> String {
"\{name}>"
}
///|
fn render_block(
block : @parsing.Block,
target~ : KatexTarget,
disable_math~ : Bool,
) -> String {
match block {
Heading(level, inlines) =>
"\{open_tag("h\{level}", "")}\{render_inlines(inlines, target~, disable_math~)}\{close_tag("h\{level}")}"
Paragraph(inlines) =>
"\{open_tag("p", "")}\{render_inlines(inlines, target~, disable_math~)}\{close_tag("p")}"
Blockquote(blocks) =>
"\{open_tag("blockquote", "")}\n\{blocks.map(b => render_block(b, target~, disable_math~)).join("\n")}\n\{close_tag("blockquote")}"
UnorderedList(items) => render_list(items, "ul", target~, disable_math~)
OrderedList(items) => render_list(items, "ol", target~, disable_math~)
FencedCode(_, code) =>
"\{open_tag("pre", "")}\{open_tag("code", "")}\{escape_html(code)}\{close_tag("code")}\{close_tag("pre")}"
ThematicBreak => "
"
DisplayMath(latex) => {
guard !disable_math else { escape_html("$$\{latex}$$") }
let rendered = render_math(latex, true, target~)
guard !rendered.is_empty() else { "" }
let attrs = attr("class", "math-display") +
attr("style", "display: block; margin: 1em 0; text-align: center")
"\{open_tag("span", attrs)}\{rendered}\{close_tag("span")}"
}
Table(header, rows) => render_table(header, rows, target~, disable_math~)
FootnoteDef(label, blocks) =>
"\{open_tag("div", attr("id", "fn:\{label}"))}\n\{blocks.map(b => render_block(b, target~, disable_math~)).join("\n")}\n\{close_tag("div")}"
}
}
///|
fn render_list(
items : Array[@parsing.MdListItem],
kind : String,
target~ : KatexTarget,
disable_math~ : Bool,
) -> String {
"\{open_tag(kind, "")}\n\{items.map(e => render_list_item(e, target~, disable_math~)).join("\n")}\n\{close_tag(kind)}"
}
///|
fn render_list_item(
item : @parsing.MdListItem,
target~ : KatexTarget,
disable_math~ : Bool,
) -> String {
let checkbox = match item.checked {
Some(true) =>
open_tag(
"input",
attr("checked", "") + attr("disabled", "") + attr("type", "checkbox"),
)
Some(false) =>
open_tag("input", attr("disabled", "") + attr("type", "checkbox"))
None => ""
}
let content = render_inlines(item.content, target~, disable_math~)
let children = item.children
.map(b => render_block(b, target~, disable_math~))
.join("\n")
"\{open_tag("li", "")}\{checkbox}\{content}\n\{children}\{close_tag("li")}"
}
///|
fn render_table(
header : @parsing.TableRow,
rows : Array[@parsing.TableRow],
target~ : KatexTarget,
disable_math~ : Bool,
) -> String {
let thead = "\{open_tag("thead", "")}\{render_table_row(header, "th", target~, disable_math~)}\{close_tag("thead")}"
let tbody = "\{open_tag("tbody", "")}\n\{rows.map(r => render_table_row(r, "td", target~, disable_math~)).join("\n")}\n\{close_tag("tbody")}"
"\{open_tag("table", "")}\n\{thead}\n\{tbody}\n\{close_tag("table")}"
}
///|
fn render_table_row(
row : @parsing.TableRow,
cell_tag : String,
target~ : KatexTarget,
disable_math~ : Bool,
) -> String {
"\{open_tag("tr", "")}\{row.cells.map(c => render_table_cell(c, cell_tag, target~, disable_math~)).join("")}\{close_tag("tr")}"
}
///|
fn render_table_cell(
cells : Array[@parsing.Inline],
cell_tag : String,
target~ : KatexTarget,
disable_math~ : Bool,
) -> String {
"\{open_tag(cell_tag, "")}\{render_inlines(cells, target~, disable_math~)}\{close_tag(cell_tag)}"
}
///|
fn render_inlines(
inlines : Array[@parsing.Inline],
target~ : KatexTarget,
disable_math~ : Bool,
) -> String {
inlines.map(e => render_inline(e, target~, disable_math~)).join("")
}
///|
fn render_inline(
inline : @parsing.Inline,
target~ : KatexTarget,
disable_math~ : Bool,
) -> String {
match inline {
Plain(text) => escape_html(text)
Strong(inlines) =>
"\{open_tag("strong", "")}\{render_inlines(inlines, target~, disable_math~)}\{close_tag("strong")}"
Emph(inlines) =>
"\{open_tag("em", "")}\{render_inlines(inlines, target~, disable_math~)}\{close_tag("em")}"
Code(text) =>
"\{open_tag("code", "")}\{escape_html(text)}\{close_tag("code")}"
Link(inlines, url) =>
"\{open_tag("a", attr("href", url))}\{render_inlines(inlines, target~, disable_math~)}\{close_tag("a")}"
Image(alt, url) => open_tag("img", attr("src", url) + attr("alt", alt))
SoftBreak => "\n"
HardBreak => "
"
InlineMath(latex) => {
guard disable_math else { render_math(latex, false, target~) }
escape_html("$\{latex}$")
}
FootnoteRef(label) =>
"\{open_tag("sup", "")}\{open_tag("a", attr("href", "#fn:\{label}"))}\{escape_html(label)}\{close_tag("a")}\{close_tag("sup")}"
}
}