适配 flowchart / sequenceDiagram / classDiagram / erDiagram,优先流程图(最常用)
1. 行预处理
- 每一行先
trimEnd(),移除行尾空格 - 跳过空行压缩:连续多个空行 → 只保留最多1个空行
- 识别行类型:
- 图表声明行:
flowchart TD/graph LR/sequenceDiagram→直接顶格输出,重置缩进 - 块开始:
subgraph xxx/alt/loop/par→ 当前缩进输出本行,缩进+1 - 块结束:
end→缩进-1,再输出本行(end和块关键字同级) - 注释行:
%% xxx→ 跟随当前缩进,原样保留注释内容 - 连线/语句:
A-->B、style A fill:#ccc、classDef→ 当前缩进输出,并且自动给连线两侧补空格:A-->B→A --> B
- 图表声明行:
2. 连线空白规则
(重点美化)匹配正则:/(\s*)([A-Za-z0-9_\-]+)(\s*)(--[>-]|-.->|==>)(\s*)([^;]+)/
- 箭头运算符:
-->,-.->,==>,---两侧强制加单个空格 - ❗ 标签内字符串
["hello --> world"]内部不替换,需要跳过引号包裹区域(难点)
3. 缩进规则分图表类型
✅ flowchart / graph
flowchart TD A --> B subgraph Group B --> C C --> D end D --> Esubgraph:本行按当前缩进,之后层级+1end:层级先-1,再打印 end- 节点连线、style、classDef:使用当前层级缩进
✅ sequenceDiagram
sequenceDiagram participant U as User participant S as Server loop 循环 U->>S: 请求 S-->>U: 返回 endparticipant/actor:永远顶格,不受缩进影响loop/alt/opt/par:块开启,缩进+1;end 回退缩进
✅ classDiagram
classDiagram class User { +String name +login() }class X {:当前缩进,遇到{缩进+1}:缩进-1,输出}
4. 注释处理(非常影响可读性)
%%单行注释:跟随当前缩进,原样保留内容- 不移动注释位置:注释属于当前行,不会漂移到别的节点旁边
限制:不支持多行注释
%% ... \n ... %%,Mermaid原生也很少用
边界 case(必须做判断,否则会破坏图表)
- 引号内文本跳过:
A["Hello --> World"],里面的-->不要替换空格!实现小技巧:Lexer 扫描时维护
inQuote标记,碰到"切换开关,引号内部不做箭头正则替换。 - 多行标签:
A["line1\nline2"],内部换行保持原样。 - 单行多连线
A-->B B-->C:可选配置项「是否强制拆分成多行」,建议默认不拆分(避免破坏用户意图)。 - 非法语法容错:遇到无法识别的行,原样输出,不报错,不要直接中断格式化。
- 特殊字符
# , : |在标签内,不做任何修改。
极简可运行 JS Demo
(流程图格式化核心,可嵌入编辑器按钮点击事件)
仅演示 flowChart,可扩展其他图表
functionformatMermaid(source){constlines=source.split(/\r?\n/);letindentLevel=0;constindent=" ";constresult=[];letinQuote=false;functionformatArrow(line){// 简单替换箭头,跳过引号内部letout="";for(leti=0;i<line.length;i++){constc=line[i];if(c==='"')inQuote=!inQuote;out+=c;if(!inQuote){out=out.replace(/(-->|-.->|==>|---)/g," $1 ");}}// 清理多余连续空格out=out.replace(/\s+/g," ").trim();returnout;}for(constrawLineoflines){constline=rawLine.trim();if(line===""){result.push("");continue;}// 图表声明行if(/^(flowchart|graph|sequenceDiagram|classDiagram|erDiagram)/.test(line)){indentLevel=0;result.push(line);continue;}// end 块结束,先减缩进if(/^end\b/.test(line)){indentLevel=Math.max(0,indentLevel-1);result.push(indent.repeat(indentLevel)+line);continue;}// subgraph / loop / alt 块开始if(/^(subgraph|loop|alt|opt|par)\b/.test(line)){constformatted=formatArrow(line);result.push(indent.repeat(indentLevel)+formatted);indentLevel++;continue;}// 注释行if(/^%%/.test(line)){result.push(indent.repeat(indentLevel)+line);continue;}// 普通连线、style、classDefconstformatted=formatArrow(line);result.push(indent.repeat(indentLevel)+formatted);}// 合并空行,连续空行只保留1个returnresult.join("\n").replace(/\n\s*\n+/g,"\n\n");}