Skip to content

Commit e5122be

Browse files
committed
Update parser to be an abstract class, added logic for arrow and strokes of edges
1 parent ede7ad7 commit e5122be

13 files changed

+1437
-259
lines changed

.editorconfig

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ charset = utf-8
77
trim_trailing_whitespace = true
88
insert_final_newline = true
99
quote_type = single
10+
max_line_length = 120
11+
1012

1113
[*.md]
1214
indent_size = 4

cspell.json

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"version": "0.2",
3+
"language": "en",
4+
"words": [
5+
"Bkgnd",
6+
"Foregnd",
7+
"githubusercontent",
8+
"mkdirp",
9+
"rels",
10+
"streetsidesoftware",
11+
"tsmerge",
12+
"Visio",
13+
"visualstudio",
14+
"vsmarketplacebadge"
15+
],
16+
"flagWords": ["hte"]
17+
}

package-lock.json

+995
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@inquirer/prompts": "^6.0.1",
1717
"adm-zip": "^0.5.16",
1818
"commander": "^12.1.0",
19+
"cspell": "^8.15.2",
1920
"figlet": "^1.7.0",
2021
"ora": "^8.1.0",
2122
"vite": "^5.4.9",

src/Scribe.ts

+203-56
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,231 @@
11
import { getMermaidShapeByValue } from './shapes/flowchartShapes.js';
2-
import { Shape, Connector, Page } from './types';
2+
import { Shape, Edge, Page, Style } from './types';
33

44
interface NodeRecord {
55
ID: string;
66
Shape: Shape;
77
NodeDef: string;
88
}
99

10-
export class Scribe {
11-
nodes: NodeRecord[] = [];
12-
edges: Connector[] = [];
13-
14-
constructor() {}
10+
const shapeToNode = (shape: Shape) => {
11+
const nodeId = `n0${shape.ID}`;
12+
const nodeShape = getMermaidShapeByValue(shape.Name);
13+
const nodeDef = `${nodeId}@{ shape: ${nodeShape}, label: ${shape.Text} }`;
14+
return { ID: nodeId, Shape: shape, NodeDef: nodeDef };
15+
};
16+
17+
const shapeToConnector = (shape: Shape, connectors: Edge[]) => {
18+
const edge = shape as Edge;
19+
const connector = connectors.find((c) => c.ID === shape.ID);
20+
if (connector) {
21+
edge.FromNode = `n0${connector.FromNode}`;
22+
edge.ToNode = `n0${connector.ToNode}`;
23+
}
24+
return edge;
25+
};
26+
27+
export const writeMermaidCode = (page: Page) => {
28+
const nodes: NodeRecord[] = [];
29+
const edges: Edge[] = [];
30+
const styles: string[] = [];
31+
32+
for (const shape of page.Shapes) {
33+
if (shape.Type === 'connector') {
34+
edges.push(shapeToConnector(shape, page.Edges));
35+
} else {
36+
nodes.push(shapeToNode(shape));
37+
}
38+
}
1539

16-
private shapeToNode = (shape: Shape) => {
17-
const nodeId = `n0${shape.ID}`;
18-
const nodeShape = getMermaidShapeByValue(shape.Name);
19-
const nodeDef = `${nodeId}@{ shape: ${nodeShape}, label: ${shape.Text} }`;
20-
this.nodes.push({ ID: nodeId, Shape: shape, NodeDef: nodeDef });
21-
};
40+
const nodeCount = nodes.length;
41+
let index = 0;
2242

23-
private shapeToConnector = (shape: Shape, connectors: Connector[]) => {
24-
const edge = shape as Connector;
25-
const connector = connectors.find((c) => c.ID === shape.ID);
26-
if (!connector) {
43+
let mermaidSyntax = 'flowchart TD\r\n';
44+
nodes.forEach((node) => {
45+
if (node.Shape.Type === 'connector') {
2746
return;
2847
}
2948

30-
edge.FromNode = `n0${connector.FromNode}`;
31-
edge.ToNode = `n0${connector.ToNode}`;
32-
this.edges.push(edge);
33-
};
34-
35-
writeMermaidCode = (page: Page) => {
36-
this.nodes = [];
37-
this.edges = [];
38-
for (const shape of page.Shapes) {
39-
if (shape.Type === 'connector') {
40-
this.shapeToConnector(shape, page.Connectors);
41-
} else {
42-
this.shapeToNode(shape);
43-
}
49+
mermaidSyntax += node.NodeDef;
50+
if (index < nodeCount - 1) {
51+
mermaidSyntax += '\r\n';
52+
}
53+
const style = getStyleStatement(node.Shape.Style);
54+
if (style) {
55+
styles.push(`style ${node.ID} ${style}`);
4456
}
57+
index++;
58+
});
4559

46-
const nodeCount = this.nodes.length;
47-
let index = 0;
60+
const edgeCount = edges.length;
61+
index = 0;
4862

49-
let mermaidSyntax = 'flowchart TD\r\n';
50-
this.nodes.forEach((node) => {
51-
if (node.Shape.Type === 'connector') {
52-
return;
53-
}
63+
if (edgeCount > 0) {
64+
mermaidSyntax += '\r\n';
65+
edges.forEach((edge) => {
66+
const edgeStart = `${edge.FromNode}`;
67+
const edgeEnd = `${edge.ToNode}`;
68+
69+
mermaidSyntax += buildEdgeStatement(edgeStart, edgeEnd, edge.Style, edge.Text);
5470

55-
mermaidSyntax += node.NodeDef;
56-
if (index < nodeCount - 1) {
71+
if (index < edgeCount - 1) {
5772
mermaidSyntax += '\r\n';
5873
}
5974

75+
const style = getStyleStatement(edge.Style);
76+
if (style) {
77+
styles.push(`linkStyle ${index} ${style}`);
78+
}
6079
index++;
6180
});
81+
}
6282

63-
const edgeCount = this.edges.length;
64-
index = 0;
65-
66-
if (edgeCount > 0) {
67-
mermaidSyntax += '\r\n';
68-
this.edges.forEach((connector) => {
69-
const edgeStart = `${connector.FromNode}`;
70-
const edgeEnd = `${connector.ToNode}`;
83+
const styleCount = styles.length;
84+
index = 0;
7185

72-
mermaidSyntax += `${edgeStart} --> ${edgeEnd}`;
86+
if (styleCount > 0) {
87+
mermaidSyntax += '\r\n';
88+
styles.forEach((style) => {
89+
mermaidSyntax += style;
7390

74-
if (index < edgeCount - 1) {
75-
mermaidSyntax += '\r\n';
76-
}
91+
if (index < styleCount - 1) {
92+
mermaidSyntax += '\r\n';
93+
}
7794

78-
index++;
79-
});
95+
index++;
96+
});
97+
}
98+
99+
return mermaidSyntax;
100+
};
101+
102+
const getStyleStatement = (style: Style): string => {
103+
let styleStatement = '';
104+
105+
// Handle fill properties
106+
if (style.FillForeground && style.FillBackground && style.FillPattern !== undefined) {
107+
switch (style.FillPattern) {
108+
case 0:
109+
// No fill
110+
styleStatement += `fill: none,`;
111+
break;
112+
case 1:
113+
// Solid fill, foreground color only
114+
styleStatement += `fill: ${style.FillForeground},`;
115+
break;
116+
case 2:
117+
// Horizontal stripes example (customize based on pattern type)
118+
styleStatement += `background: repeating-linear-gradient(0deg, ${style.FillForeground}, ${style.FillForeground} 10px, ${style.FillBackground} 10px, ${style.FillBackground} 20px),`;
119+
break;
120+
case 6:
121+
// Crosshatch fill example
122+
styleStatement += `background: repeating-linear-gradient(45deg, ${style.FillForeground}, ${style.FillForeground} 10px, ${style.FillBackground} 10px, ${style.FillBackground} 20px),`;
123+
break;
124+
// Add more cases here for different fill patterns
125+
default:
126+
styleStatement += `fill: ${style.FillForeground},`; // Default solid fill
80127
}
81-
82-
return mermaidSyntax;
83-
};
128+
}
129+
130+
if (style.LineWeight && style.LineWeight > 2) {
131+
styleStatement += `stroke-width: ${Math.round(style.LineWeight)},`; // LineWeight to stroke-width
132+
}
133+
134+
if (style.LineColor) {
135+
styleStatement += `stroke: ${style.LineColor},`;
136+
}
137+
138+
if (style.LinePattern) {
139+
switch (style.LinePattern) {
140+
case 1:
141+
// Dashed line
142+
styleStatement += `stroke-dasharray: 5, 5,`; // Customizable dash length
143+
break;
144+
case 2:
145+
// Dotted line
146+
styleStatement += `stroke-dasharray: 1, 5,`;
147+
break;
148+
case 3:
149+
// Dash-dot line
150+
styleStatement += `stroke-dasharray: 5, 5, 1, 5,`;
151+
break;
152+
// Add more cases for other line patterns as needed
153+
}
154+
}
155+
156+
if (style.Rounding && style.Rounding > 0) {
157+
styleStatement += `border-radius: ${style.Rounding}px,`;
158+
}
159+
160+
// Handle line caps (start and end of lines)
161+
if (style.LineCap) {
162+
switch (style.LineCap) {
163+
case 0:
164+
styleStatement += `stroke-linecap: butt,`; // Flat ends
165+
break;
166+
case 1:
167+
styleStatement += `stroke-linecap: round,`; // Rounded ends
168+
break;
169+
case 2:
170+
styleStatement += `stroke-linecap: square,`; // Square ends
171+
break;
172+
}
173+
}
174+
175+
if (style.FillForeground && styleStatement.indexOf('fill') === -1) {
176+
styleStatement += `fill: ${style.FillForeground},`;
177+
}
178+
179+
return styleStatement.trim().replace(/,$/, '');
180+
};
181+
182+
const buildEdgeStatement = (start: string, end: string, style: Style, text: string): string => {
183+
let startArrow = getArrow(style.BeginArrow);
184+
let endArrow = getArrow(style.EndArrow);
185+
186+
if (startArrow === '&') {
187+
startArrow = '<';
188+
}
189+
if (startArrow === '&') {
190+
startArrow = '>';
191+
}
192+
193+
let { startStroke, endStroke } = getStroke(style.LinePattern);
194+
195+
if (startArrow === '<' && endArrow === '') {
196+
return `${end} ${endStroke} ${text} ${startStroke}> ${start}`;
197+
}
198+
199+
return `${start} ${startArrow}${startStroke}${text}${endStroke}${endArrow} ${end}`;
200+
};
201+
202+
const getStroke = (linePattern: number) => {
203+
let startStroke = '--';
204+
let endStroke = '--';
205+
206+
if (linePattern) {
207+
switch (linePattern) {
208+
case 2:
209+
case 3:
210+
// Dotted line
211+
startStroke = '-.';
212+
endStroke = '.-';
213+
break;
214+
// Add more cases for other line patterns as needed
215+
}
216+
}
217+
218+
return { startStroke, endStroke };
219+
};
220+
221+
function getArrow(arrow: number): string {
222+
switch (arrow) {
223+
case 0:
224+
return '';
225+
case 6:
226+
case 7:
227+
return 'o';
228+
default:
229+
return '&';
230+
}
84231
}

src/index.ts

+13-23
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { Command } from 'commander';
22
import figlet from 'figlet';
33
import * as fs from 'fs';
4-
import { Parser } from './Parser.js';
5-
import { Scribe } from './Scribe.js';
64
import ora from 'ora';
75
import path from 'path';
6+
import { getFileParser } from './parser/Parser.js';
7+
import { writeMermaidCode } from './Scribe.js';
88

99
const program = new Command();
1010
const supportedFileTypes = ['.vsdx'];
@@ -13,50 +13,40 @@ console.log(figlet.textSync('convert2mermaid'));
1313
program
1414
.name('convert2mermaid')
1515
.version('1.0.0')
16-
.description(
17-
'A utility to convert diagrams in other formats to MermaidJs markdown syntax'
18-
)
16+
.description('A utility to convert diagrams in other formats to MermaidJs markdown syntax')
1917
.requiredOption('-i, --inputFile <value>', 'Input file')
2018
.option('-d, --diagramType [value]', 'Type of diagram', 'flowchart')
21-
.option(
22-
'-o, --outputFile [value]',
23-
'Output file name - defaults to input filename'
24-
)
19+
.option('-o, --outputFile [value]', 'Output file name - defaults to input filename')
2520
.option('-f, --outputFormat [value]', 'Output format', 'mmd')
2621

2722
.parse(process.argv);
2823

2924
const options = program.opts();
3025
const fileExt = path.extname(options.inputFile);
3126
if (!supportedFileTypes.includes(fileExt)) {
32-
console.error(
33-
`Unsupported file type: ${fileExt}. Supported file types are: ${supportedFileTypes}`
34-
);
27+
console.error(`Unsupported file type: ${fileExt}. Supported file types are: ${supportedFileTypes}`);
3528
process.exit(1);
3629
}
3730

3831
parseData(options.inputFile);
3932

4033
async function parseData(filepath: string) {
4134
try {
42-
let outputFilePath =
43-
options.outputFile || options.inputFile.replace(fileExt, '.mmd');
35+
let outputFilePath = options.outputFile || options.inputFile.replace(fileExt, '.mmd');
4436

45-
const fileParser = new Parser(filepath);
46-
const scribe = new Scribe();
47-
const pages = await fileParser.parse();
37+
const fileParser = getFileParser(filepath);
38+
const pages = await fileParser.parseDiagram();
4839
const pageCount = pages.length;
4940
let currentPageIndex = 1;
5041

42+
const spinner = ora(`Processing ${pageCount} page${pageCount > 1 ? 's' : ''}`).start();
43+
5144
for (const page of pages) {
5245
if (pageCount > 1) {
53-
outputFilePath = outputFilePath.replace(
54-
'.mmd',
55-
`_${currentPageIndex}.mmd`
56-
);
46+
outputFilePath = outputFilePath.replace('.mmd', `_${currentPageIndex}.mmd`);
5747
}
58-
const spinner = ora(`Processing page ${page.Name}`).start();
59-
const mermaidSyntax = scribe.writeMermaidCode(page);
48+
console.log(`Working on ${page.Name}...`);
49+
const mermaidSyntax = writeMermaidCode(page);
6050
fs.writeFileSync(outputFilePath, mermaidSyntax);
6151
if (pageCount > 1) {
6252
currentPageIndex++;

0 commit comments

Comments
 (0)