use comrak::nodes::{AstNode, NodeHtmlBlock, NodeValue};
use crate::util::escape_html;
pub fn transform_mermaid<'a>(root: &'a AstNode<'a>) -> usize {
let mut count = 0;
transform(root, &mut count);
count
}
fn transform<'a>(node: &'a AstNode<'a>, count: &mut usize) {
let replacement = {
let data = node.data.borrow();
if let NodeValue::CodeBlock(cb) = &data.value {
let lang = cb.info.split_whitespace().next().unwrap_or("");
if lang.eq_ignore_ascii_case("mermaid") {
Some(container_html(&cb.literal, *count))
} else {
None
}
} else {
None
}
};
if let Some(html) = replacement {
node.data.borrow_mut().value = NodeValue::HtmlBlock(NodeHtmlBlock {
block_type: 0,
literal: html,
});
*count += 1;
return;
}
for child in node.children() {
transform(child, count);
}
}
fn container_html(src: &str, idx: usize) -> String {
format!(
"<div class=\"docgen-mermaid\" x-data=\"docgenMermaid\" x-init=\"render()\" \
data-mermaid-id=\"docgen-mermaid-{idx}\">\
<pre class=\"docgen-mermaid__src\" hidden>{}</pre>\
<div class=\"docgen-mermaid__out\"></div></div>",
escape_html(src)
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::markdown::{comrak_options, format_ast};
use comrak::{parse_document, Arena};
fn render(md: &str) -> (String, usize) {
let arena = Arena::new();
let opts = comrak_options();
let root = parse_document(&arena, md, &opts);
let n = transform_mermaid(root);
(format_ast(root, &opts), n)
}
#[test]
fn mermaid_block_becomes_island_container() {
let (html, n) = render("```mermaid\ngraph TD; A-->B;\n```\n");
assert_eq!(n, 1);
assert!(html.contains("docgen-mermaid"));
assert!(html.contains("x-data=\"docgenMermaid\""));
assert!(html.contains("x-init=\"render()\""));
assert!(html.contains("graph TD")); assert!(!html.contains("<code")); }
#[test]
fn escapes_diagram_source() {
let (html, _) = render("```mermaid\ngraph TD; A[\"<x>\"]-->B;\n```\n");
assert!(html.contains("<x>"));
assert!(!html.contains("<x>"));
}
#[test]
fn non_mermaid_code_block_untouched() {
let (html, n) = render("```rust\nfn x(){}\n```\n");
assert_eq!(n, 0);
assert!(html.contains("<pre"));
}
#[test]
fn info_string_with_trailing_metadata_is_recognized() {
let (html, n) = render("```mermaid title=\"x\"\ngraph TD;A-->B;\n```\n");
assert_eq!(n, 1);
assert!(html.contains("docgen-mermaid"));
assert!(html.contains("graph TD"));
}
#[test]
fn multiple_diagrams_get_distinct_ids() {
let (html, n) =
render("```mermaid\ngraph TD;A-->B;\n```\n\n```mermaid\ngraph TD;C-->D;\n```\n");
assert_eq!(n, 2);
assert!(html.contains("data-mermaid-id=\"docgen-mermaid-0\""));
assert!(html.contains("data-mermaid-id=\"docgen-mermaid-1\""));
}
}