///|
/// Escape all user-controlled HTML text. No script, URL or raw HTML passthrough.
pub fn escape_html(text : String) -> String {
  let out = StringBuilder()
  for c in text.to_array() {
    let replacement = match c {
      '&' => "&"
      '<' => "<"
      '>' => ">"
      '"' => """
      '\'' => "'"
      _ => c.to_string()
    }
    out.write_stringview(replacement[:])
  }
  out.to_string()
}

///|
pub fn inline_html(text : String) -> String {
  let out = StringBuilder()
  for part in inline_text(text) {
    if part.bold {
      out.write_stringview("")
    }
    if part.italic {
      out.write_stringview("")
    }
    if part.underline {
      out.write_stringview("")
    }
    out.write_stringview(escape_html(part.text)[:])
    if part.underline {
      out.write_stringview("")
    }
    if part.italic {
      out.write_stringview("")
    }
    if part.bold {
      out.write_stringview("")
    }
  }
  out.to_string()
}

///|
/// Semantic reading preview, not industry pagination. Notes and boneyards are
/// excluded; outline is opt-in. Source-line anchors support editor navigation.
pub fn render_html(doc : Document, outline? : Bool = false) -> String {
  let out = StringBuilder()
  out.write_stringview(
    "MoonFountain preview
\n", ) for e in doc.elements { if e.kind == Note || e.kind == Boneyard { continue } if !outline && (e.kind == Section || e.kind == Synopsis) { continue } if e.kind == PageBreak { out.write_stringview("
\n") continue } let (tag, class_name) = match e.kind { SceneHeading => ("h2", "scene") Character => ("p", if e.dual { "character dual" } else { "character" }) Dialogue => ("p", "dialogue") Parenthetical => ("p", "parenthetical") Transition => ("p", "transition") Centered => ("p", "centered") Lyric => ("p", "lyric") Section => ("h3", "section") Synopsis => ("p", "synopsis") TitleField => ("p", "title") _ => ("p", "action") } out.write_stringview( ("<" + tag + " class=\"" + class_name + "\" id=\"line-\{e.line}\" data-line=\"\{e.line}\">")[:], ) out.write_stringview(inline_html(e.text)[:]) if e.kind == Character && e.detail != "" { out.write_stringview((" " + escape_html(e.detail))[:]) } if e.kind == Character && e.dual { out.write_stringview(" [simultaneous]") } if e.kind == SceneHeading && e.detail != "" { out.write_stringview( (" #" + escape_html(e.detail) + "#")[:], ) } out.write_stringview(("\n")[:]) } out.write_stringview("
\n") out.to_string() }