///|
/// Extract the language identifier from a `class="language-xxx"` /
/// `class="lang-xxx"` attribute (port of `commonmark.getCodeLanguage`).
fn get_code_language(node : @dom.Node) -> String {
let class = @domext.attr_or(node, "class", "")
let token = StringBuilder()
let tokens : Array[String] = []
for ch in class {
if ch.is_whitespace() {
let part = token.to_string()
if part != "" {
tokens.push(part)
token.reset()
}
} else {
token.write_char(ch)
}
}
let part = token.to_string()
if part != "" {
tokens.push(part)
}
for part in tokens {
match part.strip_prefix("language-") {
Some(language) if language.length() > 0 => return language.to_owned()
_ => ()
}
match part.strip_prefix("lang-") {
Some(language) if language.length() > 0 => return language.to_owned()
_ => ()
}
}
""
}
///|
/// Collect the raw text content of a code/pre subtree (port of
/// `commonmark.getCodeWithoutTags`): `br`/`div` introduce newlines,
/// script/style/textarea subtrees are skipped, the first code/pre class
/// provides the info string.
fn get_code_without_tags(start : @dom.Node) -> (String, String) {
let buf = StringBuilder()
let mut info_string = ""
fn walk(node : @dom.Node) {
if node.kind is Element &&
@domext.node_name(node) is ("code" | "pre") &&
info_string is "" {
info_string = get_code_language(node)
}
if node.kind is Element &&
@domext.node_name(node) is ("style" | "script" | "textarea") {
return
}
if node.kind is Element && @domext.node_name(node) is ("br" | "div") {
buf.write_char('\n')
}
if node.kind is Text {
buf.write_string(protect_internal_marker_literals(node.data))
return
}
for child in node.children {
walk(child)
}
}
walk(start)
(buf.to_string(), info_string)
}
///|
/// Render an inline code span with a dynamically sized backtick fence (port
/// of `commonmark.renderInlineCode`).
fn render_inline_code(
_ctx : RenderCtx,
out : StringBuilder,
node : @dom.Node,
) -> Unit {
let fence_char = '`'
let (code_content, _) = get_code_without_tags(node)
if @textutils.trim_space(code_content) is "" {
// No stripping occurs if the code span contains only spaces.
out.write_char(fence_char)
out.write_string(code_content)
out.write_char(fence_char)
return
}
let code = @textutils.collapse_inline_code_content(code_content)
let max_count = @textutils.calculate_code_fence_occurrences(fence_char, code) +
1
let fence = fence_char.to_string().repeat(max_count)
// Pad when the content starts or ends with a backtick.
let code = if code.has_prefix("`") { " " + code } else { code }
let code = if code.has_suffix("`") { code + " " } else { code }
out.write_string(fence)
out.write_string(code)
out.write_string(fence)
}
///|
/// Render a fenced code block (port of `commonmark.renderBlockCode`).
fn render_block_code(
ctx : RenderCtx,
out : StringBuilder,
node : @dom.Node,
) -> Unit {
let (code, info_string) = get_code_without_tags(node)
let code = if code.has_suffix("\n") {
code[:code.length() - 1].to_owned()
} else {
code
}
let fence_char = ctx.options.code_block_fence.get_char(0).unwrap()
let fence = @textutils.calculate_code_fence(fence_char, code)
let info_string = if fence_char == '`' && info_string.contains("`") {
""
} else {
protect_internal_marker_literals(info_string)
}
// Keep the original content intact: newlines become a marker so the
// post-render newline trimming leaves them alone.
let code = code.replace_all(old="\n", new=@escape.marker_code_block_newline)
out.write_string("\n\n")
out.write_string(fence)
out.write_string(info_string)
out.write_char('\n')
out.write_string(code)
out.write_char('\n')
out.write_string(fence)
out.write_string("\n\n")
}