mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-18 06:49:47 +02:00
2 failing tests
This commit is contained in:
@@ -160,7 +160,9 @@ export class FlowDB implements DiagramDB {
|
||||
|
||||
if (textObj !== undefined) {
|
||||
this.config = getConfig();
|
||||
txt = this.sanitizeText(textObj.text.trim());
|
||||
// Don't trim text that contains newlines to preserve YAML multi-line formatting
|
||||
const shouldTrim = !textObj.text.includes('\n');
|
||||
txt = this.sanitizeText(shouldTrim ? textObj.text.trim() : textObj.text);
|
||||
vertex.labelType = textObj.type;
|
||||
// strip quotes if string starts and ends with a quote
|
||||
if (txt.startsWith('"') && txt.endsWith('"')) {
|
||||
@@ -1037,7 +1039,7 @@ You have to call mermaid.initialize.`
|
||||
} else {
|
||||
const baseNode = {
|
||||
id: vertex.id,
|
||||
label: vertex.text,
|
||||
label: vertex.text?.replace(/<br>/g, '<br/>'),
|
||||
labelStyle: '',
|
||||
parentId,
|
||||
padding: config.flowchart?.padding || 8,
|
||||
|
@@ -361,7 +361,7 @@ describe('[Text] when parsing with Chevrotain', () => {
|
||||
});
|
||||
|
||||
it('should handle edge case for odd vertex with node id ending with minus', function () {
|
||||
const res = flow.parse('graph TD;A_node-->odd->Vertex Text];');
|
||||
flow.parse('graph TD;A_node-->odd->Vertex Text];');
|
||||
const vert = flow.yy.getVertices();
|
||||
|
||||
expect(vert.get('odd-').type).toBe('odd');
|
||||
|
@@ -308,6 +308,8 @@ export class FlowchartAstVisitor extends BaseVisitor {
|
||||
this.visit(ctx.vertexWithCylinder);
|
||||
} else if (ctx.vertexWithOdd) {
|
||||
this.visit(ctx.vertexWithOdd);
|
||||
} else if (ctx.vertexWithNodeIdOdd) {
|
||||
this.visit(ctx.vertexWithNodeIdOdd);
|
||||
} else if (ctx.vertexWithRect) {
|
||||
this.visit(ctx.vertexWithRect);
|
||||
} else if (ctx.vertexWithNodeData) {
|
||||
@@ -447,6 +449,15 @@ export class FlowchartAstVisitor extends BaseVisitor {
|
||||
this.addVertex(nodeId, nodeTextData.text, 'odd', nodeTextData.labelType);
|
||||
}
|
||||
|
||||
// Special visitor for node IDs ending with minus followed by odd start (e.g., "odd->text]")
|
||||
vertexWithNodeIdOdd(ctx: any): void {
|
||||
// Extract node ID from NodeIdWithOddStart token (remove the trailing ">")
|
||||
const nodeIdWithOddStart = ctx.NodeIdWithOddStart[0].image;
|
||||
const nodeId = nodeIdWithOddStart.slice(0, -1); // Remove the ">" suffix, keep the "-"
|
||||
const nodeTextData = this.visit(ctx.nodeText);
|
||||
this.addVertex(nodeId, nodeTextData.text, 'odd', nodeTextData.labelType);
|
||||
}
|
||||
|
||||
vertexWithRect(ctx: any): void {
|
||||
const nodeId = this.visit(ctx.nodeId);
|
||||
const nodeTextData = this.visit(ctx.nodeText);
|
||||
@@ -726,19 +737,31 @@ export class FlowchartAstVisitor extends BaseVisitor {
|
||||
private parseNodeDataContent(content: string, props: any): void {
|
||||
// Parse YAML-like content: "shape: rounded\nlabel: 'Hello'" or "shape: rounded, label: 'Hello'"
|
||||
// Handle both single-line and multi-line formats
|
||||
const lines = content.split('\n'); // Don't trim yet - we need to preserve indentation for YAML-style parsing
|
||||
|
||||
let i = 0;
|
||||
while (i < lines.length) {
|
||||
// First, split by lines to handle multi-line YAML format
|
||||
const lines = content.split('\n');
|
||||
|
||||
// Process each line
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const trimmedLine = line.trim();
|
||||
|
||||
// Skip empty lines
|
||||
if (!trimmedLine) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this line contains comma-separated properties (single-line format)
|
||||
if (trimmedLine.includes(',') && trimmedLine.includes(':')) {
|
||||
// Split by comma and process each property
|
||||
const properties = this.splitPropertiesByComma(trimmedLine);
|
||||
for (const property of properties) {
|
||||
this.parseProperty(property.trim(), props, lines, i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle single property per line (multi-line YAML format)
|
||||
const colonIndex = trimmedLine.indexOf(':');
|
||||
if (colonIndex > 0) {
|
||||
const key = trimmedLine.substring(0, colonIndex).trim();
|
||||
@@ -746,7 +769,6 @@ export class FlowchartAstVisitor extends BaseVisitor {
|
||||
|
||||
// Skip empty values
|
||||
if (!value) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -788,7 +810,8 @@ export class FlowchartAstVisitor extends BaseVisitor {
|
||||
}
|
||||
|
||||
// Join multiline content with newlines and add final newline
|
||||
props[key] = multilineContent.join('\n') + '\n';
|
||||
const result = multilineContent.join('\n') + '\n';
|
||||
props[key] = result;
|
||||
continue; // Don't increment i again since we already did it in the loop
|
||||
}
|
||||
|
||||
@@ -806,11 +829,11 @@ export class FlowchartAstVisitor extends BaseVisitor {
|
||||
const nextLine = lines[i].trim();
|
||||
if (nextLine.endsWith(quote)) {
|
||||
// Found closing quote
|
||||
multilineValue += '<br/>' + nextLine.substring(0, nextLine.length - 1);
|
||||
multilineValue += '<br>' + nextLine.substring(0, nextLine.length - 1);
|
||||
break;
|
||||
} else {
|
||||
// Continue collecting lines
|
||||
multilineValue += '<br/>' + nextLine;
|
||||
multilineValue += '<br>' + nextLine;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
@@ -820,31 +843,80 @@ export class FlowchartAstVisitor extends BaseVisitor {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove quotes if present (for single-line quoted strings)
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
|
||||
// Convert boolean and numeric values
|
||||
let parsedValue: any = value;
|
||||
if (value === 'true') {
|
||||
parsedValue = true;
|
||||
} else if (value === 'false') {
|
||||
parsedValue = false;
|
||||
} else if (!isNaN(Number(value)) && value !== '') {
|
||||
parsedValue = Number(value);
|
||||
}
|
||||
|
||||
props[key] = parsedValue;
|
||||
// Process single property
|
||||
this.parseProperty(trimmedLine, props, lines, i);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to split properties by comma while respecting quoted strings
|
||||
private splitPropertiesByComma(line: string): string[] {
|
||||
const properties: string[] = [];
|
||||
let current = '';
|
||||
let inQuotes = false;
|
||||
let quoteChar = '';
|
||||
|
||||
for (let i = 0; i < line.length; i++) {
|
||||
const char = line[i];
|
||||
|
||||
if ((char === '"' || char === "'") && !inQuotes) {
|
||||
inQuotes = true;
|
||||
quoteChar = char;
|
||||
current += char;
|
||||
} else if (char === quoteChar && inQuotes) {
|
||||
inQuotes = false;
|
||||
quoteChar = '';
|
||||
current += char;
|
||||
} else if (char === ',' && !inQuotes) {
|
||||
if (current.trim()) {
|
||||
properties.push(current.trim());
|
||||
}
|
||||
current = '';
|
||||
} else {
|
||||
current += char;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last property
|
||||
if (current.trim()) {
|
||||
properties.push(current.trim());
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
// Helper method to parse a single property
|
||||
private parseProperty(property: string, props: any, lines: string[], lineIndex: number): void {
|
||||
const colonIndex = property.indexOf(':');
|
||||
if (colonIndex <= 0) return;
|
||||
|
||||
const key = property.substring(0, colonIndex).trim();
|
||||
let value = property.substring(colonIndex + 1).trim();
|
||||
|
||||
// Skip empty values
|
||||
if (!value) return;
|
||||
|
||||
// Remove quotes if present (for single-line quoted strings)
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
|
||||
// Convert boolean and numeric values
|
||||
let parsedValue: any = value;
|
||||
if (value === 'true') {
|
||||
parsedValue = true;
|
||||
} else if (value === 'false') {
|
||||
parsedValue = false;
|
||||
} else if (!isNaN(Number(value)) && value !== '') {
|
||||
parsedValue = Number(value);
|
||||
}
|
||||
|
||||
props[key] = parsedValue;
|
||||
}
|
||||
|
||||
// Map shape names from node data to FlowDB types
|
||||
private mapShapeNameToType(shapeName: string): string {
|
||||
// Map common shape names to their FlowDB equivalents
|
||||
@@ -2198,17 +2270,15 @@ export class FlowchartAstVisitor extends BaseVisitor {
|
||||
const numbers: number[] = [];
|
||||
|
||||
// Handle properly tokenized numbers (NumberToken, Comma, NumberToken, ...)
|
||||
if (ctx.NumberToken && !ctx.NODE_STRING) {
|
||||
if (ctx.NumberToken) {
|
||||
ctx.NumberToken.forEach((token: any) => {
|
||||
numbers.push(parseInt(token.image, 10));
|
||||
});
|
||||
}
|
||||
|
||||
// Handle mixed case: NumberToken followed by NODE_STRING (e.g., "0" + ",1")
|
||||
if (ctx.NumberToken && ctx.NODE_STRING) {
|
||||
// Add the first number
|
||||
numbers.push(parseInt(ctx.NumberToken[0].image, 10));
|
||||
|
||||
// Handle comma-separated numbers that got tokenized as NODE_STRING (e.g., "0,1")
|
||||
// Only if there are no NumberToken (to avoid conflicts with styles)
|
||||
if (ctx.NODE_STRING && !ctx.NumberToken) {
|
||||
// Parse the comma-separated part
|
||||
const nodeString = ctx.NODE_STRING[0].image;
|
||||
if (nodeString.startsWith(',')) {
|
||||
|
@@ -1430,6 +1430,13 @@ const LeanRightStart = createToken({
|
||||
// The distinction between lean_left and inv_trapezoid is made in the parser
|
||||
|
||||
// Odd vertex tokens
|
||||
// Special token for node IDs ending with minus followed by odd start (e.g., "odd->")
|
||||
const NodeIdWithOddStart = createToken({
|
||||
name: 'NodeIdWithOddStart',
|
||||
pattern: /([A-Za-z0-9!"#$%&'*+.`?\\_/,]|:(?!::)|-(?=[^>.-])|=(?!=))*->/,
|
||||
push_mode: 'text_mode',
|
||||
});
|
||||
|
||||
const OddStart = createToken({
|
||||
name: 'OddStart',
|
||||
pattern: />/,
|
||||
@@ -1820,6 +1827,7 @@ const multiModeLexerDefinition = {
|
||||
LINK_ID,
|
||||
|
||||
// Odd shape start (must come before DirectionValue to avoid conflicts)
|
||||
NodeIdWithOddStart, // Must come before OddStart to handle "nodeId->" pattern
|
||||
OddStart,
|
||||
|
||||
// Direction values (must come after LINK tokens and OddStart)
|
||||
@@ -2025,6 +2033,7 @@ export const allTokens = [
|
||||
HexagonEnd,
|
||||
DiamondStart,
|
||||
DiamondEnd,
|
||||
NodeIdWithOddStart,
|
||||
OddStart,
|
||||
|
||||
// Numbers must come before NODE_STRING to avoid being captured by it
|
||||
@@ -2208,6 +2217,7 @@ export {
|
||||
HexagonEnd,
|
||||
DiamondStart,
|
||||
DiamondEnd,
|
||||
NodeIdWithOddStart,
|
||||
OddStart,
|
||||
|
||||
// Text content
|
||||
|
@@ -201,6 +201,7 @@ export class FlowchartParser extends CstParser {
|
||||
{ ALT: () => this.SUBRULE2(this.vertexWithEllipse) },
|
||||
{ ALT: () => this.SUBRULE2(this.vertexWithCylinder) },
|
||||
{ ALT: () => this.SUBRULE(this.vertexWithOdd) },
|
||||
{ ALT: () => this.SUBRULE(this.vertexWithNodeIdOdd) },
|
||||
{ ALT: () => this.SUBRULE(this.vertexWithRect) },
|
||||
// Node with data syntax only
|
||||
{ ALT: () => this.SUBRULE(this.vertexWithNodeData) },
|
||||
@@ -325,6 +326,13 @@ export class FlowchartParser extends CstParser {
|
||||
this.CONSUME(tokens.SquareEnd);
|
||||
});
|
||||
|
||||
// Special rule for node IDs ending with minus followed by odd start (e.g., "odd->text]")
|
||||
private vertexWithNodeIdOdd = this.RULE('vertexWithNodeIdOdd', () => {
|
||||
this.CONSUME(tokens.NodeIdWithOddStart);
|
||||
this.SUBRULE(this.nodeText);
|
||||
this.CONSUME(tokens.SquareEnd);
|
||||
});
|
||||
|
||||
private vertexWithRect = this.RULE('vertexWithRect', () => {
|
||||
this.SUBRULE(this.nodeId);
|
||||
this.CONSUME(tokens.RectStart);
|
||||
@@ -1094,16 +1102,13 @@ export class FlowchartParser extends CstParser {
|
||||
this.CONSUME(tokens.Comma);
|
||||
this.CONSUME2(tokens.NumberToken);
|
||||
});
|
||||
// Optionally handle mixed case: NumberToken followed by NODE_STRING
|
||||
this.OPTION(() => {
|
||||
this.CONSUME(tokens.NODE_STRING);
|
||||
});
|
||||
},
|
||||
},
|
||||
// Handle comma-separated numbers that got tokenized as NODE_STRING (e.g., "0,1")
|
||||
// Only consume NODE_STRING if it looks like a number list (contains only digits and commas)
|
||||
{
|
||||
ALT: () => {
|
||||
this.CONSUME2(tokens.NODE_STRING);
|
||||
this.CONSUME(tokens.NODE_STRING);
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
@@ -19,7 +19,7 @@ describe('Lexer Comparison Tests', () => {
|
||||
const input = 'graph TD';
|
||||
const expected: ExpectedToken[] = [
|
||||
{ type: 'GRAPH', value: 'graph' },
|
||||
{ type: 'DIR', value: 'TD' },
|
||||
{ type: 'DirectionValue', value: 'TD' },
|
||||
];
|
||||
|
||||
expect(() => runTest('GRA001', input, expected)).not.toThrow();
|
||||
|
Reference in New Issue
Block a user