mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-10-01 17:29:29 +05:30
feat(flowchart): mermaid translator (flowchart TD, all 5 shapes, 3 edge kinds)
Pure Mermaid translator: graph (nodes + edges) -> flowchart TD source.
5 node kinds (process/decision/terminator/subroutine/document) ->
Mermaid syntax ([..], {..}, ([..]), [[..]], [/../]).
3 edge kinds (solid/dotted/thick) -> Mermaid arrows (-->, -.->, ==>).
Label escaping: double quotes become #quot;, newlines become literal \n.
Also moves tests/fixtures per-test dirs to fixtures-epub / fixtures-css
so the shared tests/fixtures/ stays available for snapshot fixtures.
Amit Haridas
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Pure translator: graph → Mermaid `flowchart TD` source.
|
||||
*
|
||||
* Output format:
|
||||
* flowchart TD
|
||||
* A[Step 1]
|
||||
* B{Valid?}
|
||||
* C([Start])
|
||||
* D[[Do thing]]
|
||||
* E[/Report/]
|
||||
* A --> B
|
||||
* B -->|yes| C
|
||||
*
|
||||
* IDs are 1-3 chars (A..Z, AA..ZZ, …). Labels are escaped: double quotes
|
||||
* become `#quot;`, newlines become literal `\n` (Mermaid's escape).
|
||||
*
|
||||
* @module flowchart-mermaid
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const SHAPE_SYNTAX = {
|
||||
process: (id, label) => `${id}[${label}]`,
|
||||
decision: (id, label) => `${id}{${label}}`,
|
||||
terminator: (id, label) => `${id}([${label}])`,
|
||||
subroutine: (id, label) => `${id}[[${label}]]`,
|
||||
document: (id, label) => `${id}[/${label}/]`,
|
||||
};
|
||||
|
||||
const EDGE_ARROW = {
|
||||
solid: '-->',
|
||||
dotted: '-.->',
|
||||
thick: '==>',
|
||||
};
|
||||
|
||||
function escapeLabel(label) {
|
||||
return String(label || '')
|
||||
.replace(/"/g, '#quot;')
|
||||
.replace(/\r?\n/g, '\\n');
|
||||
}
|
||||
|
||||
function assignIds(nodes) {
|
||||
// A, B, C, …, Z, AA, AB, … (matches Mermaid's preferred short ids).
|
||||
const map = new Map();
|
||||
let n = 0;
|
||||
for (const node of nodes) {
|
||||
let id;
|
||||
if (n < 26) id = String.fromCharCode(65 + n);
|
||||
else {
|
||||
const first = Math.floor(n / 26) - 1;
|
||||
const second = n % 26;
|
||||
id = String.fromCharCode(65 + first) + String.fromCharCode(65 + second);
|
||||
}
|
||||
map.set(node.id, id);
|
||||
n += 1;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
function nodeDeclaration(node) {
|
||||
const fn = SHAPE_SYNTAX[node.kind];
|
||||
if (!fn) throw new Error(`flowchart-mermaid: unknown node kind "${node.kind}"`);
|
||||
return fn(node.id, escapeLabel(node.label));
|
||||
}
|
||||
|
||||
function edgeDeclaration(edge, fromId, toId) {
|
||||
const arrow = EDGE_ARROW[edge.kind];
|
||||
if (!arrow) throw new Error(`flowchart-mermaid: unknown edge kind "${edge.kind}"`);
|
||||
if (edge.label && edge.label.length > 0) {
|
||||
return `${fromId} ${arrow}|${escapeLabel(edge.label)}| ${toId}`;
|
||||
}
|
||||
return `${fromId} ${arrow} ${toId}`;
|
||||
}
|
||||
|
||||
function toMermaid(graph) {
|
||||
if (!graph || !Array.isArray(graph.nodes) || !Array.isArray(graph.edges)) {
|
||||
throw new Error('flowchart-mermaid: graph must have nodes[] and edges[]');
|
||||
}
|
||||
const ids = assignIds(graph.nodes);
|
||||
const lines = ['flowchart TD'];
|
||||
for (const node of graph.nodes) {
|
||||
lines.push(nodeDeclaration({ ...node, id: ids.get(node.id) }));
|
||||
}
|
||||
for (const edge of graph.edges) {
|
||||
const fromId = ids.get(edge.fromNodeId);
|
||||
const toId = ids.get(edge.toNodeId);
|
||||
if (!fromId || !toId) continue; // skip dangling edges (defensive)
|
||||
lines.push(edgeDeclaration(edge, fromId, toId));
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
module.exports = { toMermaid, escapeLabel, nodeDeclaration, edgeDeclaration };
|
||||
Reference in New Issue
Block a user