Merge branch 'develop' into sidv/fixTripleParsing

* develop: (59 commits)
  fix!(deps): fix zenuml style leakage. update @zenuml/core to ^3.0.6 to fix the style leakage.
  Update selectSvgElement.ts
  create `Group` type
  Add specialChars in textNoTagsToken, alphaNumToken
  Return Unicode Text to idStringToken definition
  Add underscore to unit test on special Chars
  Revert to old docs concerning quotations marks in string
  Refactor unit tests and remove repetition
  Correct idStringToken definition to include all individual special tokens
  Add unit tests for node ids with special Chars
  Create lychee.toml
  create `selectSvgElement`
  change `svgElem` to `SVG` in `configureSvgSize`
  add `configureSvgSize` in infoRenderer
  run docs:build
  remove info sandbox test case
  Remove replaceAll method in addLink
  Modify HREF token regex to contain space
  Add unit tests for stange node names
  Remove escaped quotes with backslash feature
  ...
This commit is contained in:
Sidharth Vinod
2023-08-08 13:37:02 +05:30
21 changed files with 702 additions and 513 deletions

44
.github/lychee.toml vendored Normal file
View File

@@ -0,0 +1,44 @@
############################# Display #############################
# Verbose program output
# Accepts log level: "error", "warn", "info", "debug", "trace"
verbose = "debug"
# Don't show interactive progress bar while checking links.
no_progress = true
############################# Cache ###############################
# Enable link caching. This can be helpful to avoid checking the same links on
# multiple runs.
cache = true
# Discard all cached requests older than this duration.
max_cache_age = "1d"
############################# Requests ############################
# Comma-separated list of accepted status codes for valid links.
accept = [200, 429]
############################# Exclusions ##########################
# Exclude URLs and mail addresses from checking (supports regex).
exclude = [
# Network error: Forbidden
"https://codepen.io",
# Timeout error, maybe Twitter has anti-bot defenses against GitHub's CI servers?
"https://twitter.com/mermaidjs_",
# Don't check files that are generated during the build via `pnpm docs:code`
'packages/mermaid/src/docs/config/setup/*',
# Ignore slack invite
"https://join.slack.com/"
]
# Exclude all private IPs from checking.
# Equivalent to setting `exclude_private`, `exclude_link_local`, and
# `exclude_loopback` to true.
exclude_all_private = true

View File

@@ -20,7 +20,7 @@ on:
- cron: '30 8 * * *'
jobs:
linkChecker:
link-checker:
runs-on: ubuntu-latest
permissions:
# lychee only uses the GITHUB_TOKEN to avoid rate-limiting
@@ -39,10 +39,7 @@ jobs:
uses: lycheeverse/lychee-action@v1.8.0
with:
args: >-
--verbose
--no-progress
--cache
--max-cache-age 1d
--config .github/lychee.toml
packages/mermaid/src/docs/**/*.md
README.md
README.zh-CN.md

View File

@@ -1,17 +0,0 @@
# These links are ignored by our link checker https://github.com/lycheeverse/lychee
# The file allows you to list multiple regular expressions for exclusion (one pattern per line).
# Network error: Forbidden
https://codepen.io
# Timeout error, maybe Twitter has anti-bot defenses against GitHub's CI servers?
https://twitter.com/mermaidjs_
# Don't check files that are generated during the build via `pnpm docs:code`
packages/mermaid/src/docs/config/setup/*
# Ignore localhost
http://localhost:3333/
# Ignore slack invite
https://join.slack.com/

View File

@@ -891,4 +891,27 @@ graph TD
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('66: apply class called default on node called default', () => {
imgSnapshotTest(
`
graph TD
classDef default fill:#a34,stroke:#000,stroke-width:4px,color:#fff
hello --> default
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('67: should be able to style default node independently', () => {
imgSnapshotTest(
`
flowchart TD
classDef default fill:#a34
hello --> default
style default stroke:#000,stroke-width:4px
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
});

View File

@@ -96,7 +96,7 @@ mermaid.initialize(config);
#### Defined in
[mermaidAPI.ts:666](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L666)
[mermaidAPI.ts:668](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L668)
## Functions
@@ -127,7 +127,7 @@ Return the last node appended
#### Defined in
[mermaidAPI.ts:307](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L307)
[mermaidAPI.ts:309](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L309)
---
@@ -320,4 +320,4 @@ Remove any existing elements from the given document
#### Defined in
[mermaidAPI.ts:357](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L357)
[mermaidAPI.ts:359](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L359)

View File

@@ -608,12 +608,12 @@ It is possible to escape characters using the syntax exemplified here.
```mermaid-example
flowchart LR
A["A double quote:#quot;"] -->B["A dec char:#9829;"]
A["A double quote:#quot;"] --> B["A dec char:#9829;"]
```
```mermaid
flowchart LR
A["A double quote:#quot;"] -->B["A dec char:#9829;"]
A["A double quote:#quot;"] --> B["A dec char:#9829;"]
```
Numbers given are base 10, so `#` can be encoded as `#35;`. It is also supported to use HTML character names.

View File

@@ -33,7 +33,7 @@
],
"license": "MIT",
"dependencies": {
"@zenuml/core": "^3.0.3"
"@zenuml/core": "^3.0.6"
},
"devDependencies": {
"mermaid": "workspace:^"

View File

@@ -79,8 +79,10 @@ export type DrawDefinition = (
*/
export type ParseDirectiveDefinition = (statement: string, context: string, type: string) => void;
export type HTML = d3.Selection<HTMLIFrameElement, unknown, Element, unknown>;
export type HTML = d3.Selection<HTMLIFrameElement, unknown, Element | null, unknown>;
export type SVG = d3.Selection<SVGSVGElement, unknown, Element, unknown>;
export type SVG = d3.Selection<SVGSVGElement, unknown, Element | null, unknown>;
export type Group = d3.Selection<SVGGElement, unknown, Element | null, unknown>;
export type DiagramStylesProvider = (options?: any) => string;

View File

@@ -49,7 +49,7 @@ describe('diagram detection', () => {
"Parse error on line 2:
graph TD; A-->
--------------^
Expecting 'AMP', 'ALPHA', 'COLON', 'PIPE', 'TESTSTR', 'DOWN', 'DEFAULT', 'NUM', 'COMMA', 'MINUS', 'BRKT', 'DOT', 'PUNCTUATION', 'UNICODE_TEXT', 'PLUS', 'EQUALS', 'MULT', 'UNDERSCORE', got 'EOF'"
Expecting 'AMP', 'COLON', 'PIPE', 'TESTSTR', 'DOWN', 'DEFAULT', 'NUM', 'COMMA', 'NODE_STRING', 'BRKT', 'MINUS', 'MULT', 'UNICODE_TEXT', got 'EOF'"
`);
await expect(getDiagramFromText('sequenceDiagram; A-->B')).rejects
.toThrowErrorMatchingInlineSnapshot(`

View File

@@ -6,6 +6,40 @@ setConfig({
securityLevel: 'strict',
});
const keywords = [
'graph',
'flowchart',
'flowchart-elk',
'style',
'default',
'linkStyle',
'interpolate',
'classDef',
'class',
'href',
'call',
'click',
'_self',
'_blank',
'_parent',
'_top',
'end',
'subgraph',
'kitty',
];
const doubleEndedEdges = [
{ edgeStart: 'x--', edgeEnd: '--x', stroke: 'normal', type: 'double_arrow_cross' },
{ edgeStart: 'x==', edgeEnd: '==x', stroke: 'thick', type: 'double_arrow_cross' },
{ edgeStart: 'x-.', edgeEnd: '.-x', stroke: 'dotted', type: 'double_arrow_cross' },
{ edgeStart: 'o--', edgeEnd: '--o', stroke: 'normal', type: 'double_arrow_circle' },
{ edgeStart: 'o==', edgeEnd: '==o', stroke: 'thick', type: 'double_arrow_circle' },
{ edgeStart: 'o-.', edgeEnd: '.-o', stroke: 'dotted', type: 'double_arrow_circle' },
{ edgeStart: '<--', edgeEnd: '-->', stroke: 'normal', type: 'double_arrow_point' },
{ edgeStart: '<==', edgeEnd: '==>', stroke: 'thick', type: 'double_arrow_point' },
{ edgeStart: '<-.', edgeEnd: '.->', stroke: 'dotted', type: 'double_arrow_point' },
];
describe('[Edges] when parsing', () => {
beforeEach(function () {
flow.parser.yy = flowDb;
@@ -39,9 +73,10 @@ describe('[Edges] when parsing', () => {
expect(edges[0].type).toBe('arrow_circle');
});
describe('cross', function () {
it('should handle double edged nodes and edges', function () {
const res = flow.parser.parse('graph TD;\nA x--x B;');
describe('edges', function () {
doubleEndedEdges.forEach((edgeType) => {
it(`should handle ${edgeType.stroke} ${edgeType.type} with no text`, function () {
const res = flow.parser.parse(`graph TD;\nA ${edgeType.edgeStart}${edgeType.edgeEnd} B;`);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -51,14 +86,15 @@ describe('[Edges] when parsing', () => {
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_cross');
expect(edges[0].type).toBe(`${edgeType.type}`);
expect(edges[0].text).toBe('');
expect(edges[0].stroke).toBe('normal');
expect(edges[0].length).toBe(1);
expect(edges[0].stroke).toBe(`${edgeType.stroke}`);
});
it('should handle double edged nodes with text', function () {
const res = flow.parser.parse('graph TD;\nA x-- text --x B;');
it(`should handle ${edgeType.stroke} ${edgeType.type} with text`, function () {
const res = flow.parser.parse(
`graph TD;\nA ${edgeType.edgeStart} text ${edgeType.edgeEnd} B;`
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -68,14 +104,17 @@ describe('[Edges] when parsing', () => {
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_cross');
expect(edges[0].type).toBe(`${edgeType.type}`);
expect(edges[0].text).toBe('text');
expect(edges[0].stroke).toBe('normal');
expect(edges[0].length).toBe(1);
expect(edges[0].stroke).toBe(`${edgeType.stroke}`);
});
it('should handle double edged nodes and edges on thick arrows', function () {
const res = flow.parser.parse('graph TD;\nA x==x B;');
it.each(keywords)(
`should handle ${edgeType.stroke} ${edgeType.type} with %s text`,
function (keyword) {
const res = flow.parser.parse(
`graph TD;\nA ${edgeType.edgeStart} ${keyword} ${edgeType.edgeEnd} B;`
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -85,165 +124,11 @@ describe('[Edges] when parsing', () => {
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_cross');
expect(edges[0].text).toBe('');
expect(edges[0].stroke).toBe('thick');
expect(edges[0].length).toBe(1);
});
it('should handle double edged nodes with text on thick arrows', function () {
const res = flow.parser.parse('graph TD;\nA x== text ==x B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_cross');
expect(edges[0].text).toBe('text');
expect(edges[0].stroke).toBe('thick');
expect(edges[0].length).toBe(1);
});
it('should handle double edged nodes and edges on dotted arrows', function () {
const res = flow.parser.parse('graph TD;\nA x-.-x B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_cross');
expect(edges[0].text).toBe('');
expect(edges[0].stroke).toBe('dotted');
expect(edges[0].length).toBe(1);
});
it('should handle double edged nodes with text on dotted arrows', function () {
const res = flow.parser.parse('graph TD;\nA x-. text .-x B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_cross');
expect(edges[0].text).toBe('text');
expect(edges[0].stroke).toBe('dotted');
expect(edges[0].length).toBe(1);
});
});
describe('circle', function () {
it('should handle double edged nodes and edges', function () {
const res = flow.parser.parse('graph TD;\nA o--o B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_circle');
expect(edges[0].text).toBe('');
expect(edges[0].stroke).toBe('normal');
expect(edges[0].length).toBe(1);
});
it('should handle double edged nodes with text', function () {
const res = flow.parser.parse('graph TD;\nA o-- text --o B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_circle');
expect(edges[0].text).toBe('text');
expect(edges[0].stroke).toBe('normal');
expect(edges[0].length).toBe(1);
});
it('should handle double edged nodes and edges on thick arrows', function () {
const res = flow.parser.parse('graph TD;\nA o==o B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_circle');
expect(edges[0].text).toBe('');
expect(edges[0].stroke).toBe('thick');
expect(edges[0].length).toBe(1);
});
it('should handle double edged nodes with text on thick arrows', function () {
const res = flow.parser.parse('graph TD;\nA o== text ==o B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_circle');
expect(edges[0].text).toBe('text');
expect(edges[0].stroke).toBe('thick');
expect(edges[0].length).toBe(1);
});
it('should handle double edged nodes and edges on dotted arrows', function () {
const res = flow.parser.parse('graph TD;\nA o-.-o B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_circle');
expect(edges[0].text).toBe('');
expect(edges[0].stroke).toBe('dotted');
expect(edges[0].length).toBe(1);
});
it('should handle double edged nodes with text on dotted arrows', function () {
const res = flow.parser.parse('graph TD;\nA o-. text .-o B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('double_arrow_circle');
expect(edges[0].text).toBe('text');
expect(edges[0].stroke).toBe('dotted');
expect(edges[0].length).toBe(1);
expect(edges[0].type).toBe(`${edgeType.type}`);
expect(edges[0].text).toBe(`${keyword}`);
expect(edges[0].stroke).toBe(`${edgeType.stroke}`);
}
);
});
});

View File

@@ -24,7 +24,7 @@ A["\`The cat in **the** hat\`"]-- "\`The *bat* in the chat\`" -->B["The dog in t
expect(vert['A'].labelType).toBe('markdown');
expect(vert['B'].id).toBe('B');
expect(vert['B'].text).toBe('The dog in the hog');
expect(vert['B'].labelType).toBe('text');
expect(vert['B'].labelType).toBe('string');
expect(edges.length).toBe(2);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
@@ -35,7 +35,7 @@ A["\`The cat in **the** hat\`"]-- "\`The *bat* in the chat\`" -->B["The dog in t
expect(edges[1].end).toBe('C');
expect(edges[1].type).toBe('arrow_point');
expect(edges[1].text).toBe('The rat in the mat');
expect(edges[1].labelType).toBe('text');
expect(edges[1].labelType).toBe('string');
});
it('mardown formatting in subgraphs', function () {
const res = flow.parser.parse(`flowchart LR

View File

@@ -6,6 +6,29 @@ setConfig({
securityLevel: 'strict',
});
const keywords = [
'graph',
'flowchart',
'flowchart-elk',
'style',
'default',
'linkStyle',
'interpolate',
'classDef',
'class',
'href',
'call',
'click',
'_self',
'_blank',
'_parent',
'_top',
'end',
'subgraph',
];
const specialChars = ['#', ':', '0', '&', ',', '*', '.', '\\', 'v', '-', '/', '_'];
describe('[Singlenodes] when parsing', () => {
beforeEach(function () {
flow.parser.yy = flowDb;
@@ -259,4 +282,90 @@ describe('[Singlenodes] when parsing', () => {
expect(edges.length).toBe(0);
expect(vert['i_d'].styles.length).toBe(0);
});
it.each(keywords)('should handle keywords between dashes "-"', function (keyword) {
const res = flow.parser.parse(`graph TD;a-${keyword}-node;`);
const vert = flow.parser.yy.getVertices();
expect(vert[`a-${keyword}-node`].text).toBe(`a-${keyword}-node`);
});
it.each(keywords)('should handle keywords between periods "."', function (keyword) {
const res = flow.parser.parse(`graph TD;a.${keyword}.node;`);
const vert = flow.parser.yy.getVertices();
expect(vert[`a.${keyword}.node`].text).toBe(`a.${keyword}.node`);
});
it.each(keywords)('should handle keywords between underscores "_"', function (keyword) {
const res = flow.parser.parse(`graph TD;a_${keyword}_node;`);
const vert = flow.parser.yy.getVertices();
expect(vert[`a_${keyword}_node`].text).toBe(`a_${keyword}_node`);
});
it.each(keywords)('should handle nodes ending in %s', function (keyword) {
const res = flow.parser.parse(`graph TD;node_${keyword};node.${keyword};node-${keyword};`);
const vert = flow.parser.yy.getVertices();
expect(vert[`node_${keyword}`].text).toBe(`node_${keyword}`);
expect(vert[`node.${keyword}`].text).toBe(`node.${keyword}`);
expect(vert[`node-${keyword}`].text).toBe(`node-${keyword}`);
});
const errorKeywords = [
'graph',
'flowchart',
'flowchart-elk',
'style',
'linkStyle',
'interpolate',
'classDef',
'class',
'_self',
'_blank',
'_parent',
'_top',
'end',
'subgraph',
];
it.each(errorKeywords)('should throw error at nodes beginning with %s', function (keyword) {
const str = `graph TD;${keyword}.node;${keyword}-node;${keyword}/node`;
const vert = flow.parser.yy.getVertices();
expect(() => flow.parser.parse(str)).toThrowError();
});
const workingKeywords = ['default', 'href', 'click', 'call'];
it.each(workingKeywords)('should parse node beginning with %s', function (keyword) {
flow.parser.parse(`graph TD; ${keyword}.node;${keyword}-node;${keyword}/node;`);
const vert = flow.parser.yy.getVertices();
expect(vert[`${keyword}.node`].text).toBe(`${keyword}.node`);
expect(vert[`${keyword}-node`].text).toBe(`${keyword}-node`);
expect(vert[`${keyword}/node`].text).toBe(`${keyword}/node`);
});
it.each(specialChars)(
'should allow node ids of single special characters',
function (specialChar) {
flow.parser.parse(`graph TD; ${specialChar} --> A`);
const vert = flow.parser.yy.getVertices();
expect(vert[`${specialChar}`].text).toBe(`${specialChar}`);
}
);
it.each(specialChars)(
'should allow node ids with special characters at start of id',
function (specialChar) {
flow.parser.parse(`graph TD; ${specialChar}node --> A`);
const vert = flow.parser.yy.getVertices();
expect(vert[`${specialChar}node`].text).toBe(`${specialChar}node`);
}
);
it.each(specialChars)(
'should allow node ids with special characters at end of id',
function (specialChar) {
flow.parser.parse(`graph TD; node${specialChar} --> A`);
const vert = flow.parser.yy.getVertices();
expect(vert[`node${specialChar}`].text).toBe(`node${specialChar}`);
}
);
});

View File

@@ -26,15 +26,6 @@ describe('[Style] when parsing', () => {
expect(vert['Q'].styles[0]).toBe('background:#fff');
});
// log.debug(flow.parser.parse('graph TD;style Q background:#fff;'));
it('should handle styles for edges', function () {
const res = flow.parser.parse('graph TD;a-->b;\nstyle #0 stroke: #f66;');
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(1);
});
it('should handle multiple styles for a vortex', function () {
const res = flow.parser.parse('graph TD;style R background:#fff,border:1px solid red;');

View File

@@ -305,6 +305,95 @@ describe('[Text] when parsing', () => {
expect(vert['C'].type).toBe('round');
expect(vert['C'].text).toBe('Chimpansen hoppar');
});
const keywords = [
'graph',
'flowchart',
'flowchart-elk',
'style',
'default',
'linkStyle',
'interpolate',
'classDef',
'class',
'href',
'call',
'click',
'_self',
'_blank',
'_parent',
'_top',
'end',
'subgraph',
'kitty',
];
const shapes = [
{ start: '[', end: ']', name: 'square' },
{ start: '(', end: ')', name: 'round' },
{ start: '{', end: '}', name: 'diamond' },
{ start: '(-', end: '-)', name: 'ellipse' },
{ start: '([', end: '])', name: 'stadium' },
{ start: '>', end: ']', name: 'odd' },
{ start: '[(', end: ')]', name: 'cylinder' },
{ start: '(((', end: ')))', name: 'doublecircle' },
{ start: '[/', end: '\\]', name: 'trapezoid' },
{ start: '[\\', end: '/]', name: 'inv_trapezoid' },
{ start: '[/', end: '/]', name: 'lean_right' },
{ start: '[\\', end: '\\]', name: 'lean_left' },
{ start: '[[', end: ']]', name: 'subroutine' },
{ start: '{{', end: '}}', name: 'hexagon' },
];
shapes.forEach((shape) => {
it.each(keywords)(`should handle %s keyword in ${shape.name} vertex`, function (keyword) {
const rest = flow.parser.parse(
`graph TD;A_${keyword}_node-->B${shape.start}This node has a ${keyword} as text${shape.end};`
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['B'].type).toBe(`${shape.name}`);
expect(vert['B'].text).toBe(`This node has a ${keyword} as text`);
});
});
it.each(keywords)('should handle %s keyword in rect vertex', function (keyword) {
const rest = flow.parser.parse(
`graph TD;A_${keyword}_node-->B[|borders:lt|This node has a ${keyword} as text];`
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['B'].type).toBe('rect');
expect(vert['B'].text).toBe(`This node has a ${keyword} as text`);
});
it('should handle edge case for odd vertex with node id ending with minus', function () {
const res = flow.parser.parse('graph TD;A_node-->odd->Vertex Text];');
const vert = flow.parser.yy.getVertices();
expect(vert['odd-'].type).toBe('odd');
expect(vert['odd-'].text).toBe('Vertex Text');
});
it('should allow forward slashes in lean_right vertices', function () {
const rest = flow.parser.parse(`graph TD;A_node-->B[/This node has a / as text/];`);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['B'].type).toBe('lean_right');
expect(vert['B'].text).toBe(`This node has a / as text`);
});
it('should allow back slashes in lean_left vertices', function () {
const rest = flow.parser.parse(`graph TD;A_node-->B[\\This node has a \\ as text\\];`);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['B'].type).toBe('lean_left');
expect(vert['B'].text).toBe(`This node has a \\ as text`);
});
it('should handle åäö and minus', function () {
const res = flow.parser.parse('graph TD;A-->C{Chimpansen hoppar åäö-ÅÄÖ};');
@@ -484,4 +573,33 @@ describe('[Text] when parsing', () => {
expect(vert['A'].text).toBe(',.?!+-*');
expect(edges[0].text).toBe(',.?!+-*');
});
it('should throw error at nested set of brackets', function () {
const str = 'graph TD; A[This is a () in text];';
expect(() => flow.parser.parse(str)).toThrowError("got 'PS'");
});
it('should throw error for strings and text at the same time', function () {
const str = 'graph TD;A(this node has "string" and text)-->|this link has "string" and text|C;';
expect(() => flow.parser.parse(str)).toThrowError("got 'STR'");
});
it('should throw error for escaping quotes in text state', function () {
//prettier-ignore
const str = 'graph TD; A[This is a \"()\" in text];'; //eslint-disable-line no-useless-escape
expect(() => flow.parser.parse(str)).toThrowError("got 'STR'");
});
it('should throw error for nested quoatation marks', function () {
const str = 'graph TD; A["This is a "()" in text"];';
expect(() => flow.parser.parse(str)).toThrowError("Expecting 'SQE'");
});
it('should throw error', function () {
const str = `graph TD; node[hello ) world] --> works`;
expect(() => flow.parser.parse(str)).toThrowError("got 'PE'");
});
});

View File

@@ -13,6 +13,12 @@
%x acc_descr_multiline
%x dir
%x vertex
%x text
%x ellipseText
%x trapText
%x edgeText
%x thickEdgeText
%x dottedEdgeText
%x click
%x href
%x callbackname
@@ -36,28 +42,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
<acc_descr_multiline>[\}] { this.popState(); }
<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value";
// <acc_descr_multiline>.*[^\n]* { return "acc_descr_line"}
["][`] { this.begin("md_string");}
<md_string>[^`"]+ { return "MD_STR";}
<md_string>[`]["] { this.popState();}
["] this.begin("string");
<string>["] this.popState();
<string>[^"]* return "STR";
"style" return 'STYLE';
"default" return 'DEFAULT';
"linkStyle" return 'LINKSTYLE';
"interpolate" return 'INTERPOLATE';
"classDef" return 'CLASSDEF';
"class" return 'CLASS';
/*
---interactivity command---
'href' adds a link to the specified node. 'href' can only be specified when the
line was introduced with 'click'.
'href "<link>"' attaches the specified link to the node that was specified by 'click'.
*/
"href"[\s]+["] this.begin("href");
<href>["] this.popState();
<href>[^"]* return 'HREF';
/*
---interactivity command---
@@ -74,6 +58,28 @@ Function arguments are optional: 'call <callbackname>()' simply executes 'callba
<callbackargs>\) this.popState();
<callbackargs>[^)]* return 'CALLBACKARGS';
<md_string>[^`"]+ { return "MD_STR";}
<md_string>[`]["] { this.popState();}
<*>["][`] { this.begin("md_string");}
<string>[^"]+ return "STR";
<string>["] this.popState();
<*>["] this.pushState("string");
"style" return 'STYLE';
"default" return 'DEFAULT';
"linkStyle" return 'LINKSTYLE';
"interpolate" return 'INTERPOLATE';
"classDef" return 'CLASSDEF';
"class" return 'CLASS';
/*
---interactivity command---
'href' adds a link to the specified node. 'href' can only be specified when the
line was introduced with 'click'.
'href "<link>"' attaches the specified link to the node that was specified by 'click'.
*/
"href"[\s] return 'HREF';
/*
'click' is the keyword to introduce a line that contains interactivity commands.
'click' must be followed by an existing node-id. All commands are attached to
@@ -112,7 +118,7 @@ that id.
.*direction\s+RL[^\n]* return 'direction_rl';
.*direction\s+LR[^\n]* return 'direction_lr';
[0-9]+ { return 'NUM';}
[0-9]+ return 'NUM';
\# return 'BRKT';
":::" return 'STYLE_SEPARATOR';
":" return 'COLON';
@@ -120,42 +126,60 @@ that id.
";" return 'SEMI';
"," return 'COMMA';
"*" return 'MULT';
\s*[xo<]?\-\-+[-xo>]\s* return 'LINK';
\s*[xo<]?\=\=+[=xo>]\s* return 'LINK';
\s*[xo<]?\-?\.+\-[xo>]?\s* return 'LINK';
\s*\~\~[\~]+\s* return 'LINK';
\s*[xo<]?\-\-\s* return 'START_LINK';
\s*[xo<]?\=\=\s* return 'START_LINK';
\s*[xo<]?\-\.\s* return 'START_LINK';
"(-" return '(-';
"-)" return '-)';
"([" return 'STADIUMSTART';
"])" return 'STADIUMEND';
"[[" return 'SUBROUTINESTART';
"]]" return 'SUBROUTINEEND';
"[|" return 'VERTEX_WITH_PROPS_START';
"[(" return 'CYLINDERSTART';
")]" return 'CYLINDEREND';
"(((" return 'DOUBLECIRCLESTART';
")))" return 'DOUBLECIRCLEEND';
\- return 'MINUS';
"." return 'DOT';
[\_] return 'UNDERSCORE';
\+ return 'PLUS';
\% return 'PCT';
"=" return 'EQUALS';
\= return 'EQUALS';
<INITIAL,edgeText>\s*[xo<]?\-\-+[-xo>]\s* { this.popState(); return 'LINK'; }
<INITIAL>\s*[xo<]?\-\-\s* { this.pushState("edgeText"); return 'START_LINK'; }
<edgeText>[^-]|\-(?!\-)+ return 'EDGE_TEXT';
<INITIAL,thickEdgeText>\s*[xo<]?\=\=+[=xo>]\s* { this.popState(); return 'LINK'; }
<INITIAL>\s*[xo<]?\=\=\s* { this.pushState("thickEdgeText"); return 'START_LINK'; }
<thickEdgeText>[^=]|\=(?!=) return 'EDGE_TEXT';
<INITIAL,dottedEdgeText>\s*[xo<]?\-?\.+\-[xo>]?\s* { this.popState(); return 'LINK'; }
<INITIAL>\s*[xo<]?\-\.\s* { this.pushState("dottedEdgeText"); return 'START_LINK'; }
<dottedEdgeText>[^\.]|\.(?!-) return 'EDGE_TEXT';
<*>\s*\~\~[\~]+\s* return 'LINK';
<ellipseText>[-/\)][\)] { this.popState(); return '-)'; }
<ellipseText>[^\(\)\[\]\{\}]|-/!\)+ return "TEXT"
<*>"(-" { this.pushState("ellipseText"); return '(-'; }
<text>"])" { this.popState(); return 'STADIUMEND'; }
<*>"([" { this.pushState("text"); return 'STADIUMSTART'; }
<text>"]]" { this.popState(); return 'SUBROUTINEEND'; }
<*>"[[" { this.pushState("text"); return 'SUBROUTINESTART'; }
"[|" { return 'VERTEX_WITH_PROPS_START'; }
\> { this.pushState("text"); return 'TAGEND'; }
<text>")]" { this.popState(); return 'CYLINDEREND'; }
<*>"[(" { this.pushState("text") ;return 'CYLINDERSTART'; }
<text>")))" { this.popState(); return 'DOUBLECIRCLEEND'; }
<*>"(((" { this.pushState("text"); return 'DOUBLECIRCLESTART'; }
<trapText>[\\(?=\])][\]] { this.popState(); return 'TRAPEND'; }
<trapText>\/(?=\])\] { this.popState(); return 'INVTRAPEND'; }
<trapText>\/(?!\])|\\(?!\])|[^\\\[\]\(\)\{\}\/]+ return 'TEXT';
<*>"[/" { this.pushState("trapText"); return 'TRAPSTART'; }
<*>"[\\" { this.pushState("trapText"); return 'INVTRAPSTART'; }
"<" return 'TAGSTART';
">" return 'TAGEND';
"^" return 'UP';
"\|" return 'SEP';
"v" return 'DOWN';
[A-Za-z]+ return 'ALPHA';
"\\]" return 'TRAPEND';
"[/" return 'TRAPSTART';
"/]" return 'INVTRAPEND';
"[\\" return 'INVTRAPSTART';
[!"#$%&'*+,-.`?\\_/] return 'PUNCTUATION';
"*" return 'MULT';
"#" return 'BRKT';
"&" return 'AMP';
([A-Za-z0-9!"\#$%&'*+\.`?\\_\/]|\-(?=[^\>\-\.])|=(?!=))+ return 'NODE_STRING';
"-" return 'MINUS'
[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|
@@ -218,13 +242,20 @@ that id.
[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|
[\uFFD2-\uFFD7\uFFDA-\uFFDC]
return 'UNICODE_TEXT';
"|" return 'PIPE';
"(" return 'PS';
")" return 'PE';
"[" return 'SQS';
"]" return 'SQE';
"{" return 'DIAMOND_START'
"}" return 'DIAMOND_STOP'
<text>"|" { this.popState(); return 'PIPE'; }
<*>"|" { this.pushState("text"); return 'PIPE'; }
<text>")" { this.popState(); return 'PE'; }
<*>"(" { this.pushState("text"); return 'PS'; }
<text>"]" { this.popState(); return 'SQE'; }
<*>"[" { this.pushState("text"); return 'SQS'; }
<text>(\}) { this.popState(); return 'DIAMOND_STOP' }
<*>"{" { this.pushState("text"); return 'DIAMOND_START' }
<text>[^\[\]\(\)\{\}\|\"]+ return "TEXT";
"\"" return 'QUOTE';
(\r?\n)+ return 'NEWLINE';
\s return 'SPACE';
@@ -255,11 +286,11 @@ openDirective
;
typeDirective
: type_directive { yy.parseDirective($1, 'type_directive'); }
: type_directive { yy.parseDirective($type_directive, 'type_directive'); }
;
argDirective
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
: arg_directive { $arg_directive = $arg_directive.trim().replace(/'/g, '"'); yy.parseDirective($arg_directive, 'arg_directive'); }
;
closeDirective
@@ -275,15 +306,15 @@ document
{ $$ = [];}
| document line
{
if(!Array.isArray($2) || $2.length > 0){
$1.push($2);
if(!Array.isArray($line) || $line.length > 0){
$document.push($line);
}
$$=$1;}
$$=$document;}
;
line
: statement
{$$=$1;}
{$$=$statement;}
| SEMI
| NEWLINE
| SPACE
@@ -296,15 +327,15 @@ graphConfig
| GRAPH NODIR
{ yy.setDirection('TB');$$ = 'TB';}
| GRAPH DIR FirstStmtSeperator
{ yy.setDirection($2);$$ = $2;}
{ yy.setDirection($DIR);$$ = $DIR;}
// | GRAPH SPACE TAGEND FirstStmtSeperator
// { yy.setDirection("LR");$$ = $3;}
// { yy.setDirection("LR");$$ = $TAGEND;}
// | GRAPH SPACE TAGSTART FirstStmtSeperator
// { yy.setDirection("RL");$$ = $3;}
// { yy.setDirection("RL");$$ = $TAGSTART;}
// | GRAPH SPACE UP FirstStmtSeperator
// { yy.setDirection("BT");$$ = $3;}
// { yy.setDirection("BT");$$ = $UP;}
// | GRAPH SPACE DOWN FirstStmtSeperator
// { yy.setDirection("TB");$$ = $3;}
// { yy.setDirection("TB");$$ = $DOWN;}
;
ending: endToken ending
@@ -332,7 +363,7 @@ spaceList
statement
: verticeStatement separator
{ /* console.warn('finat vs', $1.nodes); */ $$=$1.nodes}
{ /* console.warn('finat vs', $verticeStatement.nodes); */ $$=$verticeStatement.nodes}
| styleStatement separator
{$$=[];}
| linkStyleStatement separator
@@ -343,110 +374,121 @@ statement
{$$=[];}
| clickStatement separator
{$$=[];}
| subgraph SPACE text SQS text SQE separator document end
{$$=yy.addSubGraph($3,$8,$5);}
| subgraph SPACE text separator document end
{$$=yy.addSubGraph($3,$5,$3);}
// | subgraph SPACE text separator document end
// {$$=yy.addSubGraph($3,$5,$3);}
| subgraph SPACE textNoTags SQS text SQE separator document end
{$$=yy.addSubGraph($textNoTags,$document,$text);}
| subgraph SPACE textNoTags separator document end
{$$=yy.addSubGraph($textNoTags,$document,$textNoTags);}
// | subgraph SPACE textNoTags separator document end
// {$$=yy.addSubGraph($textNoTags,$document,$textNoTags);}
| subgraph separator document end
{$$=yy.addSubGraph(undefined,$3,undefined);}
{$$=yy.addSubGraph(undefined,$document,undefined);}
| direction
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); }
| acc_title acc_title_value { $$=$acc_title_value.trim();yy.setAccTitle($$); }
| acc_descr acc_descr_value { $$=$acc_descr_value.trim();yy.setAccDescription($$); }
| acc_descr_multiline_value { $$=$acc_descr_multiline_value.trim();yy.setAccDescription($$); }
;
separator: NEWLINE | SEMI | EOF ;
verticeStatement: verticeStatement link node
{ /* console.warn('vs',$1.stmt,$3); */ yy.addLink($1.stmt,$3,$2); $$ = { stmt: $3, nodes: $3.concat($1.nodes) } }
{ /* console.warn('vs',$verticeStatement.stmt,$node); */ yy.addLink($verticeStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($verticeStatement.nodes) } }
| verticeStatement link node spaceList
{ /* console.warn('vs',$1.stmt,$3); */ yy.addLink($1.stmt,$3,$2); $$ = { stmt: $3, nodes: $3.concat($1.nodes) } }
|node spaceList {/*console.warn('noda', $1);*/ $$ = {stmt: $1, nodes:$1 }}
|node { /*console.warn('noda', $1);*/ $$ = {stmt: $1, nodes:$1 }}
{ /* console.warn('vs',$verticeStatement.stmt,$node); */ yy.addLink($verticeStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($verticeStatement.nodes) } }
|node spaceList {/*console.warn('noda', $node);*/ $$ = {stmt: $node, nodes:$node }}
|node { /*console.warn('noda', $node);*/ $$ = {stmt: $node, nodes:$node }}
;
node: styledVertex
{ /* console.warn('nod', $1); */ $$ = [$1];}
{ /* console.warn('nod', $styledVertex); */ $$ = [$styledVertex];}
| node spaceList AMP spaceList styledVertex
{ $$ = $1.concat($5); /* console.warn('pip', $1[0], $5, $$); */ }
{ $$ = $node.concat($styledVertex); /* console.warn('pip', $node[0], $styledVertex, $$); */ }
;
styledVertex: vertex
{ /* console.warn('nod', $1); */ $$ = $1;}
{ /* console.warn('nod', $vertex); */ $$ = $vertex;}
| vertex STYLE_SEPARATOR idString
{$$ = $1;yy.setClass($1,$3)}
{$$ = $vertex;yy.setClass($vertex,$idString)}
;
vertex: idString SQS text SQE
{$$ = $1;yy.addVertex($1,$3,'square');}
{$$ = $idString;yy.addVertex($idString,$text,'square');}
| idString DOUBLECIRCLESTART text DOUBLECIRCLEEND
{$$ = $1;yy.addVertex($1,$3,'doublecircle');}
{$$ = $idString;yy.addVertex($idString,$text,'doublecircle');}
| idString PS PS text PE PE
{$$ = $1;yy.addVertex($1,$4,'circle');}
{$$ = $idString;yy.addVertex($idString,$text,'circle');}
| idString '(-' text '-)'
{$$ = $1;yy.addVertex($1,$3,'ellipse');}
{$$ = $idString;yy.addVertex($idString,$text,'ellipse');}
| idString STADIUMSTART text STADIUMEND
{$$ = $1;yy.addVertex($1,$3,'stadium');}
{$$ = $idString;yy.addVertex($idString,$text,'stadium');}
| idString SUBROUTINESTART text SUBROUTINEEND
{$$ = $1;yy.addVertex($1,$3,'subroutine');}
| idString VERTEX_WITH_PROPS_START ALPHA COLON ALPHA PIPE text SQE
{$$ = $1;yy.addVertex($1,$7,'rect',undefined,undefined,undefined, Object.fromEntries([[$3, $5]]));}
{$$ = $idString;yy.addVertex($idString,$text,'subroutine');}
| idString VERTEX_WITH_PROPS_START NODE_STRING\[field] COLON NODE_STRING\[value] PIPE text SQE
{$$ = $idString;yy.addVertex($idString,$text,'rect',undefined,undefined,undefined, Object.fromEntries([[$field, $value]]));}
| idString CYLINDERSTART text CYLINDEREND
{$$ = $1;yy.addVertex($1,$3,'cylinder');}
{$$ = $idString;yy.addVertex($idString,$text,'cylinder');}
| idString PS text PE
{$$ = $1;yy.addVertex($1,$3,'round');}
{$$ = $idString;yy.addVertex($idString,$text,'round');}
| idString DIAMOND_START text DIAMOND_STOP
{$$ = $1;yy.addVertex($1,$3,'diamond');}
{$$ = $idString;yy.addVertex($idString,$text,'diamond');}
| idString DIAMOND_START DIAMOND_START text DIAMOND_STOP DIAMOND_STOP
{$$ = $1;yy.addVertex($1,$4,'hexagon');}
{$$ = $idString;yy.addVertex($idString,$text,'hexagon');}
| idString TAGEND text SQE
{$$ = $1;yy.addVertex($1,$3,'odd');}
{$$ = $idString;yy.addVertex($idString,$text,'odd');}
| idString TRAPSTART text TRAPEND
{$$ = $1;yy.addVertex($1,$3,'trapezoid');}
{$$ = $idString;yy.addVertex($idString,$text,'trapezoid');}
| idString INVTRAPSTART text INVTRAPEND
{$$ = $1;yy.addVertex($1,$3,'inv_trapezoid');}
{$$ = $idString;yy.addVertex($idString,$text,'inv_trapezoid');}
| idString TRAPSTART text INVTRAPEND
{$$ = $1;yy.addVertex($1,$3,'lean_right');}
{$$ = $idString;yy.addVertex($idString,$text,'lean_right');}
| idString INVTRAPSTART text TRAPEND
{$$ = $1;yy.addVertex($1,$3,'lean_left');}
{$$ = $idString;yy.addVertex($idString,$text,'lean_left');}
| idString
{ /*console.warn('h: ', $1);*/$$ = $1;yy.addVertex($1);}
{ /*console.warn('h: ', $idString);*/$$ = $idString;yy.addVertex($idString);}
;
link: linkStatement arrowText
{$1.text = $2;$$ = $1;}
{$linkStatement.text = $arrowText;$$ = $linkStatement;}
| linkStatement TESTSTR SPACE
{$1.text = $2;$$ = $1;}
{$linkStatement.text = $TESTSTR;$$ = $linkStatement;}
| linkStatement arrowText SPACE
{$1.text = $2;$$ = $1;}
{$linkStatement.text = $arrowText;$$ = $linkStatement;}
| linkStatement
{$$ = $1;}
| START_LINK text LINK
{var inf = yy.destructLink($3, $1); $$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length,"text":$2};}
{$$ = $linkStatement;}
| START_LINK edgeText LINK
{var inf = yy.destructLink($LINK, $START_LINK); $$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length,"text":$edgeText};}
;
edgeText: edgeTextToken
{$$={text:$edgeTextToken, type:'text'};}
| edgeText edgeTextToken
{$$={text:$edgeText.text+''+$edgeTextToken, type:$edgeText.type};}
|STR
{$$={text: $STR, type: 'string'};}
| MD_STR
{$$={text:$MD_STR, type:'markdown'};}
;
linkStatement: LINK
{var inf = yy.destructLink($1);$$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length};}
{var inf = yy.destructLink($LINK);$$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length};}
;
arrowText:
PIPE text PIPE
{$$ = $2;}
{$$ = $text;}
;
text: textToken
{ $$={text:$1, type: 'text'};}
{ $$={text:$textToken, type: 'text'};}
| text textToken
{ $$={text:$1.text+''+$2, type: $1.type};}
{ $$={text:$text.text+''+$textToken, type: $text.type};}
| STR
{ $$={text: $1, type: 'text'};}
{ $$ = {text: $STR, type: 'string'};}
| MD_STR
{ $$={text: $1, type: 'markdown'};}
{ $$={text: $MD_STR, type: 'markdown'};}
;
@@ -456,109 +498,104 @@ keywords
textNoTags: textNoTagsToken
{$$=$1;}
{$$={text:$textNoTagsToken, type: 'text'};}
| textNoTags textNoTagsToken
{$$=$1+''+$2;}
{$$={text:$textNoTags.text+''+$textNoTagsToken, type: $textNoTags.type};}
| STR
{ $$={text: $STR, type: 'text'};}
| MD_STR
{ $$={text: $MD_STR, type: 'markdown'};}
;
classDefStatement:CLASSDEF SPACE DEFAULT SPACE stylesOpt
{$$ = $1;yy.addClass($3,$5);}
| CLASSDEF SPACE alphaNum SPACE stylesOpt
{$$ = $1;yy.addClass($3,$5);}
classDefStatement:CLASSDEF SPACE idString SPACE stylesOpt
{$$ = $CLASSDEF;yy.addClass($idString,$stylesOpt);}
;
classStatement:CLASS SPACE alphaNum SPACE alphaNum
{$$ = $1;yy.setClass($3, $5);}
classStatement:CLASS SPACE idString\[vertex] SPACE idString\[class]
{$$ = $CLASS;yy.setClass($vertex, $class);}
;
clickStatement
: CLICK CALLBACKNAME {$$ = $1;yy.setClickEvent($1, $2);}
| CLICK CALLBACKNAME SPACE STR {$$ = $1;yy.setClickEvent($1, $2);yy.setTooltip($1, $4);}
| CLICK CALLBACKNAME CALLBACKARGS {$$ = $1;yy.setClickEvent($1, $2, $3);}
| CLICK CALLBACKNAME CALLBACKARGS SPACE STR {$$ = $1;yy.setClickEvent($1, $2, $3);yy.setTooltip($1, $5);}
| CLICK HREF {$$ = $1;yy.setLink($1, $2);}
| CLICK HREF SPACE STR {$$ = $1;yy.setLink($1, $2);yy.setTooltip($1, $4);}
| CLICK HREF SPACE LINK_TARGET {$$ = $1;yy.setLink($1, $2, $4);}
| CLICK HREF SPACE STR SPACE LINK_TARGET {$$ = $1;yy.setLink($1, $2, $6);yy.setTooltip($1, $4);}
| CLICK alphaNum {$$ = $1;yy.setClickEvent($1, $2);}
| CLICK alphaNum SPACE STR {$$ = $1;yy.setClickEvent($1, $2);yy.setTooltip($1, $4);}
| CLICK STR {$$ = $1;yy.setLink($1, $2);}
| CLICK STR SPACE STR {$$ = $1;yy.setLink($1, $2);yy.setTooltip($1, $4);}
| CLICK STR SPACE LINK_TARGET {$$ = $1;yy.setLink($1, $2, $4);}
| CLICK STR SPACE STR SPACE LINK_TARGET {$$ = $1;yy.setLink($1, $2, $6);yy.setTooltip($1, $4);}
: CLICK CALLBACKNAME {$$ = $CLICK;yy.setClickEvent($CLICK, $CALLBACKNAME);}
| CLICK CALLBACKNAME SPACE STR {$$ = $CLICK;yy.setClickEvent($CLICK, $CALLBACKNAME);yy.setTooltip($CLICK, $STR);}
| CLICK CALLBACKNAME CALLBACKARGS {$$ = $CLICK;yy.setClickEvent($CLICK, $CALLBACKNAME, $CALLBACKARGS);}
| CLICK CALLBACKNAME CALLBACKARGS SPACE STR {$$ = $CLICK;yy.setClickEvent($CLICK, $CALLBACKNAME, $CALLBACKARGS);yy.setTooltip($CLICK, $STR);}
| CLICK HREF STR {$$ = $CLICK;yy.setLink($CLICK, $STR);}
| CLICK HREF STR SPACE STR {$$ = $CLICK;yy.setLink($CLICK, $STR1);yy.setTooltip($CLICK, $STR2);}
| CLICK HREF STR SPACE LINK_TARGET {$$ = $CLICK;yy.setLink($CLICK, $STR, $LINK_TARGET);}
| CLICK HREF STR\[link] SPACE STR\[tooltip] SPACE LINK_TARGET {$$ = $CLICK;yy.setLink($CLICK, $link, $LINK_TARGET);yy.setTooltip($CLICK, $tooltip);}
| CLICK alphaNum {$$ = $CLICK;yy.setClickEvent($CLICK, $alphaNum);}
| CLICK alphaNum SPACE STR {$$ = $CLICK;yy.setClickEvent($CLICK, $alphaNum);yy.setTooltip($CLICK, $STR);}
| CLICK STR {$$ = $CLICK;yy.setLink($CLICK, $STR);}
| CLICK STR\[link] SPACE STR\[tooltip] {$$ = $CLICK;yy.setLink($CLICK, $link);yy.setTooltip($CLICK, $tooltip);}
| CLICK STR SPACE LINK_TARGET {$$ = $CLICK;yy.setLink($CLICK, $STR, $LINK_TARGET);}
| CLICK STR\[link] SPACE STR\[tooltip] SPACE LINK_TARGET {$$ = $CLICK;yy.setLink($CLICK, $link, $LINK_TARGET);yy.setTooltip($CLICK, $tooltip);}
;
styleStatement:STYLE SPACE alphaNum SPACE stylesOpt
{$$ = $1;yy.addVertex($3,undefined,undefined,$5);}
| STYLE SPACE HEX SPACE stylesOpt
{$$ = $1;yy.updateLink($3,$5);}
styleStatement:STYLE SPACE idString SPACE stylesOpt
{$$ = $STYLE;yy.addVertex($idString,undefined,undefined,$stylesOpt);}
;
linkStyleStatement
: LINKSTYLE SPACE DEFAULT SPACE stylesOpt
{$$ = $1;yy.updateLink([$3],$5);}
{$$ = $LINKSTYLE;yy.updateLink([$DEFAULT],$stylesOpt);}
| LINKSTYLE SPACE numList SPACE stylesOpt
{$$ = $1;yy.updateLink($3,$5);}
{$$ = $LINKSTYLE;yy.updateLink($numList,$stylesOpt);}
| LINKSTYLE SPACE DEFAULT SPACE INTERPOLATE SPACE alphaNum SPACE stylesOpt
{$$ = $1;yy.updateLinkInterpolate([$3],$7);yy.updateLink([$3],$9);}
{$$ = $LINKSTYLE;yy.updateLinkInterpolate([$DEFAULT],$alphaNum);yy.updateLink([$DEFAULT],$stylesOpt);}
| LINKSTYLE SPACE numList SPACE INTERPOLATE SPACE alphaNum SPACE stylesOpt
{$$ = $1;yy.updateLinkInterpolate($3,$7);yy.updateLink($3,$9);}
{$$ = $LINKSTYLE;yy.updateLinkInterpolate($numList,$alphaNum);yy.updateLink($numList,$stylesOpt);}
| LINKSTYLE SPACE DEFAULT SPACE INTERPOLATE SPACE alphaNum
{$$ = $1;yy.updateLinkInterpolate([$3],$7);}
{$$ = $LINKSTYLE;yy.updateLinkInterpolate([$DEFAULT],$alphaNum);}
| LINKSTYLE SPACE numList SPACE INTERPOLATE SPACE alphaNum
{$$ = $1;yy.updateLinkInterpolate($3,$7);}
{$$ = $LINKSTYLE;yy.updateLinkInterpolate($numList,$alphaNum);}
;
numList: NUM
{$$ = [$1]}
{$$ = [$NUM]}
| numList COMMA NUM
{$1.push($3);$$ = $1;}
{$numList.push($NUM);$$ = $numList;}
;
stylesOpt: style
{$$ = [$1]}
{$$ = [$style]}
| stylesOpt COMMA style
{$1.push($3);$$ = $1;}
{$stylesOpt.push($style);$$ = $stylesOpt;}
;
style: styleComponent
|style styleComponent
{$$ = $1 + $2;}
{$$ = $style + $styleComponent;}
;
styleComponent: ALPHA | COLON | MINUS | NUM | UNIT | SPACE | HEX | BRKT | DOT | STYLE | PCT ;
styleComponent: NUM | NODE_STRING| COLON | UNIT | SPACE | BRKT | STYLE | PCT ;
/* Token lists */
idStringToken : NUM | NODE_STRING | DOWN | MINUS | DEFAULT | COMMA | COLON | AMP | BRKT | MULT | UNICODE_TEXT;
textToken : textNoTagsToken | TAGSTART | TAGEND | START_LINK | PCT | DEFAULT;
textToken : TEXT | TAGSTART | TAGEND | UNICODE_TEXT;
textNoTagsToken: alphaNumToken | SPACE | MINUS | keywords ;
textNoTagsToken: NUM | NODE_STRING | SPACE | MINUS | AMP | UNICODE_TEXT | COLON | MULT | BRKT | keywords | START_LINK ;
edgeTextToken : EDGE_TEXT | UNICODE_TEXT ;
alphaNumToken : NUM | UNICODE_TEXT | NODE_STRING | DIR | DOWN | MINUS | COMMA | COLON | AMP | BRKT | MULT;
idString
:idStringToken
{$$=$1}
{$$=$idStringToken}
| idString idStringToken
{$$=$1+''+$2}
{$$=$idString+''+$idStringToken}
;
alphaNum
: alphaNumStatement
{$$=$1;}
| alphaNum alphaNumStatement
{$$=$1+''+$2;}
: alphaNumToken
{$$=$alphaNumToken;}
| alphaNum alphaNumToken
{$$=$alphaNum+''+$alphaNumToken;}
;
alphaNumStatement
: DIR
{$$=$1;}
| alphaNumToken
{$$=$1;}
| DOWN
{$$='v';}
| MINUS
{$$='-';}
;
direction
: direction_tb
@@ -571,9 +608,4 @@ direction
{ $$={stmt:'dir', value:'LR'};}
;
alphaNumToken : PUNCTUATION | AMP | UNICODE_TEXT | NUM| ALPHA | COLON | COMMA | PLUS | EQUALS | MULT | DOT | BRKT| UNDERSCORE ;
idStringToken : ALPHA|UNDERSCORE |UNICODE_TEXT | NUM| COLON | COMMA | PLUS | MINUS | DOWN |EQUALS | MULT | BRKT | DOT | PUNCTUATION | AMP | DEFAULT;
graphCodeTokens: STADIUMSTART | STADIUMEND | SUBROUTINESTART | SUBROUTINEEND | VERTEX_WITH_PROPS_START | CYLINDERSTART | CYLINDEREND | TRAPSTART | TRAPEND | INVTRAPSTART | INVTRAPEND | PIPE | PS | PE | SQS | SQE | DIAMOND_START | DIAMOND_STOP | TAGSTART | TAGEND | ARROW_CROSS | ARROW_POINT | ARROW_CIRCLE | ARROW_OPEN | QUOTE | SEMI;
%%

View File

@@ -1,7 +1,7 @@
import { select } from 'd3';
import { log } from '../../logger.js';
import { getConfig } from '../../config.js';
import type { DrawDefinition, HTML, SVG } from '../../diagram-api/types.js';
import { configureSvgSize } from '../../setupGraphViewbox.js';
import type { DrawDefinition, Group, SVG } from '../../diagram-api/types.js';
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
/**
* Draws a an info picture in the tag with id: id based on the graph definition in text.
@@ -11,40 +11,20 @@ import type { DrawDefinition, HTML, SVG } from '../../diagram-api/types.js';
* @param version - MermaidJS version.
*/
const draw: DrawDefinition = (text, id, version) => {
try {
log.debug('rendering info diagram\n' + text);
const { securityLevel } = getConfig();
// handle root and document for when rendering in sandbox mode
let sandboxElement: HTML | undefined;
let document: Document | null | undefined;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
document = sandboxElement.nodes()[0].contentDocument;
}
const svg: SVG = selectSvgElement(id);
configureSvgSize(svg, 100, 400, true);
// @ts-ignore - figure out how to assign HTML to document type
const root: HTML =
sandboxElement !== undefined && document !== undefined && document !== null
? select(document)
: select('body');
const svg: SVG = root.select('#' + id);
svg.attr('height', 100);
svg.attr('width', 400);
const g = svg.append('g');
g.append('text') // text label for the x axis
const group: Group = svg.append('g');
group
.append('text')
.attr('x', 100)
.attr('y', 40)
.attr('class', 'version')
.attr('font-size', '32px')
.attr('font-size', 32)
.style('text-anchor', 'middle')
.text('v ' + version);
} catch (e) {
log.error('error while rendering info diagram', e);
}
.text(`v${version}`);
};
export const renderer = { draw };

View File

@@ -390,7 +390,7 @@ It is possible to escape characters using the syntax exemplified here.
```mermaid-example
flowchart LR
A["A double quote:#quot;"] -->B["A dec char:#9829;"]
A["A double quote:#quot;"] --> B["A dec char:#9829;"]
```
Numbers given are base 10, so `#` can be encoded as `#35;`. It is also supported to use HTML character names.

View File

@@ -284,7 +284,9 @@ export const cleanUpSvgCode = (
* TODO replace btoa(). Replace with buf.toString('base64')?
*/
export const putIntoIFrame = (svgCode = '', svgElement?: D3Element): string => {
const height = svgElement ? svgElement.viewBox.baseVal.height + 'px' : IFRAME_HEIGHT;
const height = svgElement?.viewBox?.baseVal?.height
? svgElement.viewBox.baseVal.height + 'px'
: IFRAME_HEIGHT;
const base64encodedSrc = btoa('<body style="' + IFRAME_BODY_STYLE + '">' + svgCode + '</body>');
return `<iframe style="width:${IFRAME_WIDTH};height:${height};${IFRAME_STYLES}" src="data:text/html;base64,${base64encodedSrc}" sandbox="${IFRAME_SANDBOX_OPTS}">
${IFRAME_NOT_SUPPORTED_MSG}

View File

@@ -0,0 +1,22 @@
import { select } from 'd3';
import { getConfig } from '../config.js';
import type { HTML, SVG } from '../diagram-api/types.js';
/**
* Selects the SVG element using {@link id}.
*
* @param id - The diagram ID.
* @returns The selected {@link SVG} element using {@link id}.
*/
export const selectSvgElement = (id: string): SVG => {
const { securityLevel } = getConfig();
// handle root and document for when rendering in sandbox mode
let root: HTML = select('body');
if (securityLevel === 'sandbox') {
const sandboxElement: HTML = select(`#i${id}`);
const doc: Document = sandboxElement.node()?.contentDocument ?? document;
root = select(doc.body as HTMLIFrameElement);
}
const svg: SVG = root.select(`#${id}`);
return svg;
};

View File

@@ -1,4 +1,5 @@
import { log } from './logger.js';
import { SVG } from './diagram-api/types.js';
/**
* Applies d3 attributes
@@ -35,7 +36,7 @@ export const calculateSvgSizeAttrs = function (height, width, useMaxWidth) {
/**
* Applies attributes from `calculateSvgSizeAttrs`
*
* @param {SVGSVGElement} svgElem The SVG Element to configure
* @param {SVG} svgElem The SVG Element to configure
* @param {number} height The height of the SVG
* @param {number} width The width of the SVG
* @param {boolean} useMaxWidth Whether or not to use max-width and set width to 100%

16
pnpm-lock.yaml generated
View File

@@ -413,8 +413,8 @@ importers:
packages/mermaid-zenuml:
dependencies:
'@zenuml/core':
specifier: ^3.0.3
version: 3.0.3(ts-node@10.9.1)
specifier: ^3.0.6
version: 3.0.6(ts-node@10.9.1)
devDependencies:
mermaid:
specifier: workspace:^
@@ -5738,14 +5738,14 @@ packages:
resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
dev: true
/@zenuml/core@3.0.3(ts-node@10.9.1):
resolution: {integrity: sha512-Wp6yF5iERvGXrR3z/mNhQYP2uI54Bd7RQ2ZwA26Lca+tufj4X8pfqGVSORLqbUl4wjjdcwHc2RNT8AU+cz2NCg==}
/@zenuml/core@3.0.6(ts-node@10.9.1):
resolution: {integrity: sha512-azEBVrl+ClCPhII92TbzBUFcWhIjlOPdEHVzF6eZXs5Oy4JlrfldS5pAZBHCFL4riOBsjZ5sHHmQLQg9V07T4Q==}
engines: {node: '>=12.0.0'}
dependencies:
'@types/assert': 1.5.6
'@types/ramda': 0.28.25
'@vue/compat': 3.3.4(vue@3.3.4)
antlr4: 4.13.0
antlr4: 4.11.0
color-string: 1.9.1
dom-to-image-more: 2.16.0
file-saver: 2.0.5
@@ -6026,9 +6026,9 @@ packages:
engines: {node: '>=12'}
dev: true
/antlr4@4.13.0:
resolution: {integrity: sha512-zooUbt+UscjnWyOrsuY/tVFL4rwrAGwOivpQmvmUDE22hy/lUA467Rc1rcixyRwcRUIXFYBwv7+dClDSHdmmew==}
engines: {node: '>=16'}
/antlr4@4.11.0:
resolution: {integrity: sha512-GUGlpE2JUjAN+G8G5vY+nOoeyNhHsXoIJwP1XF1oRw89vifA1K46T6SEkwLwr7drihN7I/lf0DIjKc4OZvBX8w==}
engines: {node: '>=14'}
dev: false
/any-promise@1.3.0: