///|
/// A writer passed to custom HTML renderers.
///
/// Renderer authors should use `write_escaped` and `write_attrs` for text and
/// attribute output that comes from Markdown input.
pub struct HtmlWriter {
priv out : StringBuilder
priv block_child : Bool
}
///|
/// Writes raw HTML.
pub fn HtmlWriter::write(self : HtmlWriter, value : String) -> Unit {
self.out <+ "\{value}"
}
///|
/// Returns whether the current element starts with a block child.
pub fn HtmlWriter::starts_with_block_child(self : HtmlWriter) -> Bool {
self.block_child
}
///|
/// Writes escaped HTML text.
pub fn HtmlWriter::write_escaped(self : HtmlWriter, value : String) -> Unit {
self.out.write_escaped_html(value)
}
///|
/// Writes escaped HTML attributes.
pub fn HtmlWriter::write_attrs(self : HtmlWriter, attrs : Array[Attr]) -> Unit {
self.out.write_attrs(attrs)
}
///|
/// Writes escaped HTML attributes except one named attribute.
pub fn HtmlWriter::write_attrs_except1(
self : HtmlWriter,
attrs : Array[Attr],
excluded : String,
) -> Unit {
self.out.write_attrs_except1(attrs, excluded)
}
///|
/// Writes escaped HTML attributes except two named attributes.
pub fn HtmlWriter::write_attrs_except2(
self : HtmlWriter,
attrs : Array[Attr],
first : String,
second : String,
) -> Unit {
self.out.write_attrs_except2(attrs, first, second)
}
///|
/// Writes escaped HTML attributes except three named attributes.
pub fn HtmlWriter::write_attrs_except3(
self : HtmlWriter,
attrs : Array[Attr],
first : String,
second : String,
third : String,
) -> Unit {
self.out.write_attrs_except3(attrs, first, second, third)
}
///|
/// A function that renders an opening HTML element and returns its close text.
pub type HtmlRendererFn = (HtmlWriter, Element) -> String
///|
priv struct HtmlRendererEntry {
tag : Tag
f : HtmlRendererFn
}
///|
priv enum HtmlRendererOp {
RegisterHtmlRenderer(HtmlRendererEntry)
ReplaceHtmlRenderer(HtmlRendererEntry)
DisableHtmlRenderer(Tag)
EnableHtmlRenderer(Tag)
}
///|
/// A composable set of HTML renderers keyed by Markdown event tags.
///
/// `HtmlRenderers` does not parse Markdown. It only teaches `Html` how to
/// render extension event tags emitted by syntax rules.
pub struct HtmlRenderers {
priv ops : Array[HtmlRendererOp]
}
///|
/// Creates an empty composable HTML renderer set.
pub fn HtmlRenderers::HtmlRenderers() -> HtmlRenderers {
{ ops: [] }
}
///|
/// Appends all renderer operations from `other`.
pub fn HtmlRenderers::extend(
self : HtmlRenderers,
other : HtmlRenderers,
) -> HtmlRenderers {
let ops = copy_html_renderer_ops(self.ops)
for op in other.ops {
ops.push(copy_html_renderer_op(op))
}
{ ops, }
}
///|
/// Registers a renderer for `tag`.
pub fn HtmlRenderers::register(
self : HtmlRenderers,
tag : Tag,
f : HtmlRendererFn,
) -> HtmlRenderers {
let ops = copy_html_renderer_ops(self.ops)
ops.push(RegisterHtmlRenderer({ tag, f }))
{ ops, }
}
///|
/// Replaces the renderer for `tag`.
pub fn HtmlRenderers::replace(
self : HtmlRenderers,
tag : Tag,
f : HtmlRendererFn,
) -> HtmlRenderers {
let ops = copy_html_renderer_ops(self.ops)
ops.push(ReplaceHtmlRenderer({ tag, f }))
{ ops, }
}
///|
/// Disables the renderer for `tag`, causing `Html` to use unknown-tag fallback.
pub fn HtmlRenderers::disable(self : HtmlRenderers, tag : Tag) -> HtmlRenderers {
let ops = copy_html_renderer_ops(self.ops)
ops.push(DisableHtmlRenderer(tag))
{ ops, }
}
///|
/// Re-enables the renderer for `tag`.
pub fn HtmlRenderers::enable(self : HtmlRenderers, tag : Tag) -> HtmlRenderers {
let ops = copy_html_renderer_ops(self.ops)
ops.push(EnableHtmlRenderer(tag))
{ ops, }
}
///|
priv struct PreparedHtmlRenderers {
mut entries : Array[HtmlRendererEntry]
mut disabled : Array[Tag]
mut overrides : Array[HtmlRendererEntry]
}
///|
fn PreparedHtmlRenderers::PreparedHtmlRenderers(
renderers : HtmlRenderers,
) -> PreparedHtmlRenderers {
let prepared = { entries: [], disabled: [], overrides: [] }
prepared.apply(renderers)
prepared
}
///|
fn PreparedHtmlRenderers::apply(
self : PreparedHtmlRenderers,
renderers : HtmlRenderers,
) -> Unit {
for op in renderers.ops {
match op {
RegisterHtmlRenderer(entry) => self.register(entry)
ReplaceHtmlRenderer(entry) => self.replace(entry)
DisableHtmlRenderer(tag) => self.disable(tag)
EnableHtmlRenderer(tag) => self.enable(tag)
}
}
}
///|
fn PreparedHtmlRenderers::find(
self : PreparedHtmlRenderers,
tag : Tag,
) -> HtmlRendererFn? {
if array_contains_tag(self.disabled, tag) {
return None
}
for entry in self.entries {
if entry.tag == tag {
return Some(entry.f)
}
}
None
}
///|
fn PreparedHtmlRenderers::register(
self : PreparedHtmlRenderers,
entry : HtmlRendererEntry,
) -> Unit {
match self.override_for(entry.tag) {
Some(replace) => self.override_registered(replace)
None => self.register_raw(entry)
}
}
///|
fn PreparedHtmlRenderers::register_raw(
self : PreparedHtmlRenderers,
entry : HtmlRendererEntry,
) -> Unit {
for old in self.entries {
if old.tag == entry.tag {
abort("duplicate HTML renderer: " + entry.tag.to_string())
}
}
self.entries.push(entry)
}
///|
fn PreparedHtmlRenderers::replace(
self : PreparedHtmlRenderers,
entry : HtmlRendererEntry,
) -> Unit {
self.remember_override(entry)
self.override_registered(entry)
}
///|
fn PreparedHtmlRenderers::remember_override(
self : PreparedHtmlRenderers,
entry : HtmlRendererEntry,
) -> Unit {
let next : Array[HtmlRendererEntry] = []
for old in self.overrides {
if old.tag != entry.tag {
next.push(old)
}
}
next.push(entry)
self.overrides = next
}
///|
fn PreparedHtmlRenderers::override_for(
self : PreparedHtmlRenderers,
tag : Tag,
) -> HtmlRendererEntry? {
for entry in self.overrides {
if entry.tag == tag {
return Some(entry)
}
}
None
}
///|
fn PreparedHtmlRenderers::override_registered(
self : PreparedHtmlRenderers,
entry : HtmlRendererEntry,
) -> Unit {
let next : Array[HtmlRendererEntry] = []
for old in self.entries {
if old.tag != entry.tag {
next.push(old)
}
}
self.entries = next
self.entries.push(entry)
}
///|
fn PreparedHtmlRenderers::disable(
self : PreparedHtmlRenderers,
tag : Tag,
) -> Unit {
if !array_contains_tag(self.disabled, tag) {
self.disabled.push(tag)
}
}
///|
fn PreparedHtmlRenderers::enable(
self : PreparedHtmlRenderers,
tag : Tag,
) -> Unit {
let next_disabled : Array[Tag] = []
for disabled in self.disabled {
if disabled != tag {
next_disabled.push(disabled)
}
}
self.disabled = next_disabled
}
///|
fn copy_html_renderer_ops(ops : Array[HtmlRendererOp]) -> Array[HtmlRendererOp] {
let out : Array[HtmlRendererOp] = []
for op in ops {
out.push(copy_html_renderer_op(op))
}
out
}
///|
fn copy_html_renderer_op(op : HtmlRendererOp) -> HtmlRendererOp {
match op {
RegisterHtmlRenderer(entry) => RegisterHtmlRenderer(entry)
ReplaceHtmlRenderer(entry) => ReplaceHtmlRenderer(entry)
DisableHtmlRenderer(tag) => DisableHtmlRenderer(tag)
EnableHtmlRenderer(tag) => EnableHtmlRenderer(tag)
}
}
///|
fn array_contains_tag(tags : Array[Tag], tag : Tag) -> Bool {
for current in tags {
if current == tag {
return true
}
}
false
}
///|