///|
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 {
  ""
}

///|
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")}" } }