mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-18 14:59:53 +02:00
Refactor mindmap grammar to enhance node type support, including BangNode, CloudNode, and HexagonNode. Update value converter for new string handling and improve test coverage for node definitions.
This commit is contained in:
@@ -59,14 +59,14 @@ export class MindmapValidator {
|
||||
) {
|
||||
rootNodeIndentation = 0;
|
||||
} else if (row.indent === undefined) {
|
||||
console.debug('FAIL 1', rootNodeIndentation, row.indent);
|
||||
// console.debug('FAIL 1', rootNodeIndentation, row.indent);
|
||||
// If we've already found a root node, report an error
|
||||
accept('error', 'Multiple root nodes are not allowed in a mindmap.', {
|
||||
node: row,
|
||||
property: 'item',
|
||||
});
|
||||
} else if (rootNodeIndentation >= row.indent) {
|
||||
console.debug('FAIL 2', rootNodeIndentation, row.indent, row.item);
|
||||
// console.debug('FAIL 2', rootNodeIndentation, row.indent, row.item);
|
||||
accept('error', 'Multiple root nodes are not allowed in a mindmap.', {
|
||||
node: row,
|
||||
property: 'item',
|
||||
|
@@ -18,19 +18,25 @@ Item:
|
||||
|
||||
// Use a special rule order to handle the parsing precedence
|
||||
Node:
|
||||
CircleNode | OtherComplex | SimpleNode | RoundedNode | SquareNode;
|
||||
SquareNode | RoundedNode | CircleNode | BangNode | CloudNode | HexagonNode | SimpleNode;
|
||||
|
||||
// Specifically handle double parentheses case - highest priority
|
||||
CircleNode:
|
||||
(id=ID)? desc=(CIRCLE_STR);
|
||||
// id=ID '((' desc=(CIRCLE_STR) '))';
|
||||
// id=ID '((' desc=(ID|STRING) '))';
|
||||
(id=ID)? desc=(CIRCLE_STR|CIRCLE_QSTR);
|
||||
BangNode:
|
||||
(id=ID)? desc=(BANG_STR|BANG_QSTR);
|
||||
|
||||
RoundedNode:
|
||||
(id=ID)? desc=(ROUNDED_STR_QUOTES|ROUNDED_STR);
|
||||
(id=ID)? desc=(ROUNDED_STR|ROUNDED_QSTR);
|
||||
|
||||
SquareNode:
|
||||
(id=ID)? desc=(SQUARE_STR_QUOTES|SQUARE_STR);
|
||||
(id=ID)? desc=(SQUARE_STR|SQUARE_QSTR);
|
||||
|
||||
CloudNode:
|
||||
(id=ID)? desc=(CLOUD_STR|CLOUD_QSTR);
|
||||
|
||||
HexagonNode:
|
||||
(id=ID)? desc=(HEXAGON_STR|HEXAGON_QSTR);
|
||||
|
||||
// Handle other complex node variants
|
||||
OtherComplex:
|
||||
@@ -47,33 +53,37 @@ SimpleNode:
|
||||
id=ID;
|
||||
|
||||
IconDecoration:
|
||||
ICON_KEYWORD content=(ID|STRING) ')';
|
||||
content=(ICON);
|
||||
|
||||
ClassDecoration:
|
||||
CLASS_KEYWORD content=(ID|STRING);
|
||||
content=(CLASS);
|
||||
|
||||
// This should be processed before whitespace is ignored
|
||||
terminal INDENTATION: /[ \t]{2,}/; // Two or more spaces/tabs for indentation
|
||||
|
||||
// Keywords with fixed text patterns
|
||||
terminal MINDMAP_KEYWORD: 'mindmap\n';
|
||||
terminal ICON_KEYWORD: '::icon(';
|
||||
terminal CLASS_KEYWORD: ':::';
|
||||
// terminal ICON_KEYWORD: '::icon(';
|
||||
// terminal CLASS_KEYWORD: ':::';
|
||||
|
||||
// Basic token types
|
||||
// terminal CIRCLE_STR: /[\s\S]*?\)\)/;
|
||||
terminal CIRCLE_STR: /\(\(([\s\S]*?)\)\)/;
|
||||
terminal ROUNDED_STR_QUOTES: /\(\"([\s\S]*?)\"\)/;
|
||||
terminal ROUNDED_STR: /\(([\s\S]*?)\)/;
|
||||
terminal SQUARE_STR_QUOTES: /\[\"([\s\S]*?)\"\]/;
|
||||
terminal CIRCLE_QSTR: "((\"" -> "\"))";
|
||||
terminal CIRCLE_STR: "((" -> "))";
|
||||
terminal BANG_QSTR: "))\"" -> "\"((";
|
||||
terminal BANG_STR: "))" -> "((";
|
||||
terminal CLOUD_QSTR: ")\"" -> "\"(";
|
||||
terminal CLOUD_STR: ")" -> "(";
|
||||
terminal HEXAGON_QSTR: "{{\"" -> "\"}}";
|
||||
terminal HEXAGON_STR: "{{" -> "}}";
|
||||
terminal ROUNDED_QSTR: "(\"" -> "\")";
|
||||
terminal ROUNDED_STR: "(" -> ")";
|
||||
terminal SQUARE_QSTR: /\[\"([\s\S]*?)\"\]/;
|
||||
terminal SQUARE_STR: /\[([\s\S]*?)\]/;
|
||||
terminal BANG_STR_QUOTES: /\[\"([\s\S]*?)\"\]/;
|
||||
terminal BANG_STR: /\[([\s\S]*?)\]/;
|
||||
terminal CLOUD_STR_QUOTES: /\)\"([\s\S]*?)\"\(/;
|
||||
terminal CLOUD_STR: /\)([\s\S]*?)\(/;
|
||||
// terminal CIRCLE_STR: /(?!\(\()[\s\S]+?(?!\(\()/;
|
||||
|
||||
terminal ICON: "::icon(" -> ")";
|
||||
terminal CLASS: /:::([^\n:])*/;
|
||||
|
||||
terminal ID: /[a-zA-Z0-9_\-\.\/]+/;
|
||||
terminal STRING: /"[^"]*"|'[^']*'/;
|
||||
|
||||
// Modified indentation rule to have higher priority than WS
|
||||
|
||||
|
@@ -11,26 +11,36 @@ export class MindmapValueConverter extends AbstractMermaidValueConverter {
|
||||
console.debug('MermaidValueConverter', rule.name, input);
|
||||
if (rule.name === 'CIRCLE_STR') {
|
||||
return input.replace('((', '').replace('))', '').trim();
|
||||
} else if (rule.name === 'CIRCLE_QSTR') {
|
||||
return input.replace('(("', '').replace('"))', '').trim();
|
||||
} else if (rule.name === 'ROUNDED_STR') {
|
||||
return input.replace('(', '').replace(')', '').trim();
|
||||
} else if (rule.name === 'ROUNDED_STR_QUOTES') {
|
||||
} else if (rule.name === 'ROUNDED_QSTR') {
|
||||
return input.replace('("', '').replace('")', '').trim();
|
||||
} else if (rule.name === 'SQUARE_STR') {
|
||||
return input.replace('[', '').replace(']', '').trim();
|
||||
} else if (rule.name === 'SQUARE_STR_QUOTES') {
|
||||
} else if (rule.name === 'SQUARE_QSTR') {
|
||||
return input.replace('["', '').replace('"]', '').trim();
|
||||
} else if (rule.name === 'BANG_STR') {
|
||||
return input.replace('))', '').replace('((', '').trim();
|
||||
} else if (rule.name === 'BANG_STR_QUOTES') {
|
||||
} else if (rule.name === 'BANG_QSTR') {
|
||||
return input.replace('))"', '').replace('"((', '').trim();
|
||||
} else if (rule.name === 'HEXAGON_STR') {
|
||||
return input.replace('{{', '').replace('}}', '').trim();
|
||||
} else if (rule.name === 'HEXAGON_QSTR') {
|
||||
return input.replace('{{"', '').replace('"}}', '').trim();
|
||||
} else if (rule.name === 'CLOUD_STR') {
|
||||
return input.replace(')', '').replace('(', '').trim();
|
||||
} else if (rule.name === 'CLOUD_STR_QUOTES') {
|
||||
} else if (rule.name === 'CLOUD_QSTR') {
|
||||
return input.replace(')"', '').replace('"(', '').trim();
|
||||
} else if (rule.name === 'ARCH_TEXT_ICON') {
|
||||
return input.replace(/["()]/g, '');
|
||||
} else if (rule.name === 'ARCH_TITLE') {
|
||||
return input.replace(/[[\]]/g, '').trim();
|
||||
} else if (rule.name === 'CLASS') {
|
||||
return input.replace(':::', '').trim();
|
||||
} else if (rule.name === 'ICON') {
|
||||
return input.replace('::icon(', '').replace(')', '').trim();
|
||||
} else if (rule.name === 'INDENTATION') {
|
||||
return input.length;
|
||||
}
|
||||
|
22
packages/parser/tests/mindmap-poc.test.ts
Normal file
22
packages/parser/tests/mindmap-poc.test.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { validatedMindmapParse as validatedParse, mindmapParse as parse } from './test-util.js';
|
||||
import type { CircleNode, SimpleNode, OtherComplex } from '../src/language/generated/ast.js';
|
||||
|
||||
describe('Nodes (ported from mindmap.spec.ts)', () => {
|
||||
it('MMP-21 should be possible to have comments in a mindmap', () => {
|
||||
const result = parse(
|
||||
'mindmap\nroot(Root)\n Child(Child)\n a(a)\n\n %% This is a comment\n b[New Stuff]'
|
||||
);
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
console.debug(result.parserErrors);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
const childNode = result.value.MindmapRows[1].item as OtherComplex;
|
||||
const aNode = result.value.MindmapRows[2].item as OtherComplex;
|
||||
const bNode = result.value.MindmapRows[3].item as OtherComplex;
|
||||
expect(rootNode.desc).toBe('Root');
|
||||
expect(childNode.desc).toBe('Child');
|
||||
expect(aNode.desc).toBe('a');
|
||||
expect(bNode.desc).toBe('New Stuff');
|
||||
});
|
||||
});
|
@@ -174,7 +174,7 @@ describe('Nodes (ported from mindmap.spec.ts)', () => {
|
||||
expect(childNode.desc).toBe('child1');
|
||||
});
|
||||
|
||||
it.only('MMP-9 should handle an id and type for a node definition', () => {
|
||||
it('MMP-9 should handle an id and type for a node definition', () => {
|
||||
const result = parse('mindmap\nroot\n theId(child1)');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
@@ -256,6 +256,51 @@ describe('Decorations (ported from mindmap.spec.ts)', () => {
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
expect(rootNode.desc).toBe('The root');
|
||||
});
|
||||
it('MMP-16.2 should handle an id and type for a node definition', () => {
|
||||
const result = parse(`mindmap
|
||||
id1[SquareNode I am]
|
||||
id2["SquareNode I am"]
|
||||
id3("RoundedNode I am")
|
||||
id4(RoundedNode I am)
|
||||
id5(("CircleNode I am"))
|
||||
id6((CircleNode I am))
|
||||
id7))BangNode I am((
|
||||
id8))"BangNode I am"((
|
||||
id9)"CloudNode I am"(
|
||||
id10)CloudNode I am(
|
||||
id11{{"HexagonNode I am"}}
|
||||
id12{{HexagonNode I am}}
|
||||
id13
|
||||
`);
|
||||
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
expect(result.value.MindmapRows).toHaveLength(13);
|
||||
expect(result.value.MindmapRows[0].item.$type).toBe('SquareNode');
|
||||
expect(result.value.MindmapRows[1].item.$type).toBe('SquareNode');
|
||||
expect(result.value.MindmapRows[2].item.$type).toBe('RoundedNode');
|
||||
expect(result.value.MindmapRows[3].item.$type).toBe('RoundedNode');
|
||||
expect(result.value.MindmapRows[4].item.$type).toBe('CircleNode');
|
||||
expect(result.value.MindmapRows[5].item.$type).toBe('CircleNode');
|
||||
expect(result.value.MindmapRows[6].item.$type).toBe('BangNode');
|
||||
expect(result.value.MindmapRows[7].item.$type).toBe('BangNode');
|
||||
expect(result.value.MindmapRows[8].item.$type).toBe('CloudNode');
|
||||
expect(result.value.MindmapRows[9].item.$type).toBe('CloudNode');
|
||||
expect(result.value.MindmapRows[10].item.$type).toBe('HexagonNode');
|
||||
expect(result.value.MindmapRows[11].item.$type).toBe('HexagonNode');
|
||||
expect(result.value.MindmapRows[12].item.$type).toBe('SimpleNode');
|
||||
|
||||
let id = 1;
|
||||
for (const row of result.value.MindmapRows as MindmapRow[]) {
|
||||
const item = row.item as Node;
|
||||
expect(item.id).toBeDefined();
|
||||
expect(item?.id).toBe('id' + id);
|
||||
if (item.id !== 'id13') {
|
||||
expect(item.desc).toBe(item.$type + ' I am');
|
||||
}
|
||||
id++;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Descriptions (ported from mindmap.spec.ts)', () => {
|
||||
|
Reference in New Issue
Block a user