///|
/// 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(("" + tag + ">\n")[:])
}
out.write_stringview("\n")
out.to_string()
}