using System.Text; namespace SQLLinter.Infrastructure.Diagram; public static class MermaidRenderer { public static string ToMermaidContent(BpmnDiagram diagram) { var sb = new StringBuilder(); sb.AppendLine("flowchart TB"); // main: print Start, then tasks/gateways, then End var mainStart = diagram.Main.Nodes.Where(n => n.Type == BpmnNodeType.Start).ToList(); var mainTasks = diagram.Main.Nodes.Where(n => n.Type == BpmnNodeType.Task || n.Type == BpmnNodeType.Gateway).ToList(); var mainEnd = diagram.Main.Nodes.Where(n => n.Type == BpmnNodeType.End).ToList(); foreach (var node in mainStart) { var nid = SanitizeId(node.Id); var label = Escape(node.Label); sb.AppendLine($" {nid}((\"{label}\"))"); } foreach (var node in mainTasks) { var nid = SanitizeId(node.Id); var label = Escape(node.Label); if (node.Type == BpmnNodeType.Gateway) sb.AppendLine($" {nid}{{\"{label}\"}}"); else sb.AppendLine($" {nid}[\"{label}\"]"); } foreach (var node in mainEnd) { var nid = SanitizeId(node.Id); var label = Escape(node.Label); sb.AppendLine($" {nid}((\"{label}\"))"); } sb.AppendLine(); // subprocesses as subgraphs: pool label, start, tasks/gateways, end foreach (var proc in diagram.Subprocesses) { var procId = SanitizeId(proc.Id); sb.AppendLine($" subgraph {procId} [\"{Escape(proc.Name)}\"]"); var startNodes = proc.Nodes.Where(n => n.Type == BpmnNodeType.Start).ToList(); var taskNodes = proc.Nodes.Where(n => n.Type == BpmnNodeType.Task || n.Type == BpmnNodeType.Gateway).ToList(); var endNodes = proc.Nodes.Where(n => n.Type == BpmnNodeType.End).ToList(); var poolNodes = proc.Nodes.Where(n => n.Type == BpmnNodeType.Subprocess).ToList(); // pool label nodes (usually first) foreach (var node in poolNodes) { var nid = SanitizeId(node.Id); var label = Escape(node.Label); sb.AppendLine($" {nid}[\"{label}\"]"); } foreach (var node in startNodes) { var nid = SanitizeId(node.Id); var label = Escape(node.Label); sb.AppendLine($" {nid}((\"{label}\"))"); } foreach (var node in taskNodes) { var nid = SanitizeId(node.Id); var label = Escape(node.Label); if (node.Type == BpmnNodeType.Gateway) sb.AppendLine($" {nid}{{\"{label}\"}}"); else sb.AppendLine($" {nid}[\"{label}\"]"); } foreach (var node in endNodes) { var nid = SanitizeId(node.Id); var label = Escape(node.Label); sb.AppendLine($" {nid}((\"{label}\"))"); } // edges inside subprocess foreach (var e in proc.Edges) { var from = SanitizeId(e.From); var to = SanitizeId(e.To); var arrow = e.Dashed ? "-.->" : "-->"; var lbl = string.IsNullOrWhiteSpace(e.Label) ? string.Empty : $" |{Escape(e.Label)}|"; sb.AppendLine($" {from} {arrow} {to}{lbl}"); } sb.AppendLine(" end"); sb.AppendLine(); } // main edges foreach (var e in diagram.Main.Edges) { var from = SanitizeId(e.From); var to = SanitizeId(e.To); var arrow = e.Dashed ? "-.->" : "-->"; var lbl = string.IsNullOrWhiteSpace(e.Label) ? string.Empty : $" |{Escape(e.Label)}|"; sb.AppendLine($" {from} {arrow} {to}{lbl}"); } return sb.ToString(); } public static string RenderMarkdown(BpmnDiagram diagram) { var content = ToMermaidContent(diagram); var sb = new StringBuilder(); sb.AppendLine("```mermaid"); sb.Append(content); sb.AppendLine("```"); return sb.ToString(); } public static string RenderHtml(BpmnDiagram diagram) { var content = ToMermaidContent(diagram); var sb = new StringBuilder(); // Put raw mermaid text inside .mermaid container so mermaid.js can render it sb.AppendLine("
\n" + content + "\n
"); return sb.ToString(); } private static string Escape(string s) { if (s == null) return string.Empty; return s.Replace("\"", "\\\"").Replace("\n", " ").Replace("\r", " "); } private static string SanitizeId(string s) { if (string.IsNullOrEmpty(s)) return "id"; var sb = new StringBuilder(); foreach (var ch in s) { if (char.IsLetterOrDigit(ch)) sb.Append(ch); else sb.Append('_'); } return sb.ToString(); } }