mermaid
Version:
Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.
4 lines • 101 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../../src/diagrams/er/parser/erDiagram.jison", "../../../src/diagrams/er/erDb.js", "../../../src/diagrams/er/erRenderer.js", "../../../src/diagrams/er/erMarkers.js", "../../../src/diagrams/er/styles.js", "../../../src/diagrams/er/erDiagram.ts"],
"sourcesContent": ["/* parser generated by jison 0.4.18 */\n/*\n Returns a Parser object of the following structure:\n\n Parser: {\n yy: {}\n }\n\n Parser.prototype: {\n yy: {},\n trace: function(),\n symbols_: {associative list: name ==> number},\n terminals_: {associative list: number ==> name},\n productions_: [...],\n performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),\n table: [...],\n defaultActions: {...},\n parseError: function(str, hash),\n parse: function(input),\n\n lexer: {\n EOF: 1,\n parseError: function(str, hash),\n setInput: function(input),\n input: function(),\n unput: function(str),\n more: function(),\n less: function(n),\n pastInput: function(),\n upcomingInput: function(),\n showPosition: function(),\n test_match: function(regex_match_array, rule_index),\n next: function(),\n lex: function(),\n begin: function(condition),\n popState: function(),\n _currentRules: function(),\n topState: function(),\n pushState: function(condition),\n\n options: {\n ranges: boolean (optional: true ==> token location info will include a .range[] member)\n flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)\n backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)\n },\n\n performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),\n rules: [...],\n conditions: {associative list: name ==> set},\n }\n }\n\n\n token location info (@$, _$, etc.): {\n first_line: n,\n last_line: n,\n first_column: n,\n last_column: n,\n range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based)\n }\n\n\n the parseError function receives a 'hash' object with these members for lexer and parser errors: {\n text: (matched text)\n token: (the produced terminal token, if any)\n line: (yylineno)\n }\n while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {\n loc: (yylloc)\n expected: (string describing the set of expected tokens)\n recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)\n }\n*/\nvar parser = (function(){\nvar o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[6,8,10,20,22,24,26,27,28],$V1=[1,10],$V2=[1,11],$V3=[1,12],$V4=[1,13],$V5=[1,14],$V6=[1,15],$V7=[1,21],$V8=[1,22],$V9=[1,23],$Va=[1,24],$Vb=[1,25],$Vc=[6,8,10,13,15,18,19,20,22,24,26,27,28,41,42,43,44,45],$Vd=[1,34],$Ve=[27,28,46,47],$Vf=[41,42,43,44,45],$Vg=[17,34],$Vh=[1,54],$Vi=[1,53],$Vj=[17,34,36,38];\nvar parser = {trace: function trace () { },\nyy: {},\nsymbols_: {\"error\":2,\"start\":3,\"ER_DIAGRAM\":4,\"document\":5,\"EOF\":6,\"line\":7,\"SPACE\":8,\"statement\":9,\"NEWLINE\":10,\"entityName\":11,\"relSpec\":12,\":\":13,\"role\":14,\"BLOCK_START\":15,\"attributes\":16,\"BLOCK_STOP\":17,\"SQS\":18,\"SQE\":19,\"title\":20,\"title_value\":21,\"acc_title\":22,\"acc_title_value\":23,\"acc_descr\":24,\"acc_descr_value\":25,\"acc_descr_multiline_value\":26,\"ALPHANUM\":27,\"ENTITY_NAME\":28,\"attribute\":29,\"attributeType\":30,\"attributeName\":31,\"attributeKeyTypeList\":32,\"attributeComment\":33,\"ATTRIBUTE_WORD\":34,\"attributeKeyType\":35,\"COMMA\":36,\"ATTRIBUTE_KEY\":37,\"COMMENT\":38,\"cardinality\":39,\"relType\":40,\"ZERO_OR_ONE\":41,\"ZERO_OR_MORE\":42,\"ONE_OR_MORE\":43,\"ONLY_ONE\":44,\"MD_PARENT\":45,\"NON_IDENTIFYING\":46,\"IDENTIFYING\":47,\"WORD\":48,\"$accept\":0,\"$end\":1},\nterminals_: {2:\"error\",4:\"ER_DIAGRAM\",6:\"EOF\",8:\"SPACE\",10:\"NEWLINE\",13:\":\",15:\"BLOCK_START\",17:\"BLOCK_STOP\",18:\"SQS\",19:\"SQE\",20:\"title\",21:\"title_value\",22:\"acc_title\",23:\"acc_title_value\",24:\"acc_descr\",25:\"acc_descr_value\",26:\"acc_descr_multiline_value\",27:\"ALPHANUM\",28:\"ENTITY_NAME\",34:\"ATTRIBUTE_WORD\",36:\"COMMA\",37:\"ATTRIBUTE_KEY\",38:\"COMMENT\",41:\"ZERO_OR_ONE\",42:\"ZERO_OR_MORE\",43:\"ONE_OR_MORE\",44:\"ONLY_ONE\",45:\"MD_PARENT\",46:\"NON_IDENTIFYING\",47:\"IDENTIFYING\",48:\"WORD\"},\nproductions_: [0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,5],[9,4],[9,3],[9,1],[9,7],[9,6],[9,4],[9,2],[9,2],[9,2],[9,1],[11,1],[11,1],[16,1],[16,2],[29,2],[29,3],[29,3],[29,4],[30,1],[31,1],[32,1],[32,3],[35,1],[33,1],[12,3],[39,1],[39,1],[39,1],[39,1],[39,1],[40,1],[40,1],[14,1],[14,1],[14,1]],\nperformAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {\n/* this == yyval */\n\nvar $0 = $$.length - 1;\nswitch (yystate) {\ncase 1:\n /*console.log('finished parsing');*/ \nbreak;\ncase 2:\n this.$ = [] \nbreak;\ncase 3:\n$$[$0-1].push($$[$0]);this.$ = $$[$0-1]\nbreak;\ncase 4: case 5:\n this.$ = $$[$0] \nbreak;\ncase 6: case 7:\n this.$=[];\nbreak;\ncase 8:\n\n yy.addEntity($$[$0-4]);\n yy.addEntity($$[$0-2]);\n yy.addRelationship($$[$0-4], $$[$0], $$[$0-2], $$[$0-3]);\n \nbreak;\ncase 9:\n\n yy.addEntity($$[$0-3]);\n yy.addAttributes($$[$0-3], $$[$0-1]);\n \nbreak;\ncase 10:\n yy.addEntity($$[$0-2]); \nbreak;\ncase 11:\n yy.addEntity($$[$0]); \nbreak;\ncase 12:\n\n yy.addEntity($$[$0-6], $$[$0-4]);\n yy.addAttributes($$[$0-6], $$[$0-1]);\n \nbreak;\ncase 13:\n yy.addEntity($$[$0-5], $$[$0-3]); \nbreak;\ncase 14:\n yy.addEntity($$[$0-3], $$[$0-1]); \nbreak;\ncase 15: case 16:\n this.$=$$[$0].trim();yy.setAccTitle(this.$); \nbreak;\ncase 17: case 18:\n this.$=$$[$0].trim();yy.setAccDescription(this.$); \nbreak;\ncase 19: case 43:\n this.$ = $$[$0]; \nbreak;\ncase 20: case 41: case 42:\n this.$ = $$[$0].replace(/\"/g, ''); \nbreak;\ncase 21: case 29:\n this.$ = [$$[$0]]; \nbreak;\ncase 22:\n $$[$0].push($$[$0-1]); this.$=$$[$0]; \nbreak;\ncase 23:\n this.$ = { attributeType: $$[$0-1], attributeName: $$[$0] }; \nbreak;\ncase 24:\n this.$ = { attributeType: $$[$0-2], attributeName: $$[$0-1], attributeKeyTypeList: $$[$0] }; \nbreak;\ncase 25:\n this.$ = { attributeType: $$[$0-2], attributeName: $$[$0-1], attributeComment: $$[$0] }; \nbreak;\ncase 26:\n this.$ = { attributeType: $$[$0-3], attributeName: $$[$0-2], attributeKeyTypeList: $$[$0-1], attributeComment: $$[$0] }; \nbreak;\ncase 27: case 28: case 31:\n this.$=$$[$0]; \nbreak;\ncase 30:\n $$[$0-2].push($$[$0]); this.$ = $$[$0-2]; \nbreak;\ncase 32:\n this.$=$$[$0].replace(/\"/g, ''); \nbreak;\ncase 33:\n\n this.$ = { cardA: $$[$0], relType: $$[$0-1], cardB: $$[$0-2] };\n /*console.log('relSpec: ' + $$[$0] + $$[$0-1] + $$[$0-2]);*/\n \nbreak;\ncase 34:\n this.$ = yy.Cardinality.ZERO_OR_ONE; \nbreak;\ncase 35:\n this.$ = yy.Cardinality.ZERO_OR_MORE; \nbreak;\ncase 36:\n this.$ = yy.Cardinality.ONE_OR_MORE; \nbreak;\ncase 37:\n this.$ = yy.Cardinality.ONLY_ONE; \nbreak;\ncase 38:\n this.$ = yy.Cardinality.MD_PARENT; \nbreak;\ncase 39:\n this.$ = yy.Identification.NON_IDENTIFYING; \nbreak;\ncase 40:\n this.$ = yy.Identification.IDENTIFYING; \nbreak;\n}\n},\ntable: [{3:1,4:[1,2]},{1:[3]},o($V0,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:9,20:$V1,22:$V2,24:$V3,26:$V4,27:$V5,28:$V6},o($V0,[2,7],{1:[2,1]}),o($V0,[2,3]),{9:16,11:9,20:$V1,22:$V2,24:$V3,26:$V4,27:$V5,28:$V6},o($V0,[2,5]),o($V0,[2,6]),o($V0,[2,11],{12:17,39:20,15:[1,18],18:[1,19],41:$V7,42:$V8,43:$V9,44:$Va,45:$Vb}),{21:[1,26]},{23:[1,27]},{25:[1,28]},o($V0,[2,18]),o($Vc,[2,19]),o($Vc,[2,20]),o($V0,[2,4]),{11:29,27:$V5,28:$V6},{16:30,17:[1,31],29:32,30:33,34:$Vd},{11:35,27:$V5,28:$V6},{40:36,46:[1,37],47:[1,38]},o($Ve,[2,34]),o($Ve,[2,35]),o($Ve,[2,36]),o($Ve,[2,37]),o($Ve,[2,38]),o($V0,[2,15]),o($V0,[2,16]),o($V0,[2,17]),{13:[1,39]},{17:[1,40]},o($V0,[2,10]),{16:41,17:[2,21],29:32,30:33,34:$Vd},{31:42,34:[1,43]},{34:[2,27]},{19:[1,44]},{39:45,41:$V7,42:$V8,43:$V9,44:$Va,45:$Vb},o($Vf,[2,39]),o($Vf,[2,40]),{14:46,27:[1,49],28:[1,48],48:[1,47]},o($V0,[2,9]),{17:[2,22]},o($Vg,[2,23],{32:50,33:51,35:52,37:$Vh,38:$Vi}),o([17,34,37,38],[2,28]),o($V0,[2,14],{15:[1,55]}),o([27,28],[2,33]),o($V0,[2,8]),o($V0,[2,41]),o($V0,[2,42]),o($V0,[2,43]),o($Vg,[2,24],{33:56,36:[1,57],38:$Vi}),o($Vg,[2,25]),o($Vj,[2,29]),o($Vg,[2,32]),o($Vj,[2,31]),{16:58,17:[1,59],29:32,30:33,34:$Vd},o($Vg,[2,26]),{35:60,37:$Vh},{17:[1,61]},o($V0,[2,13]),o($Vj,[2,30]),o($V0,[2,12])],\ndefaultActions: {34:[2,27],41:[2,22]},\nparseError: function parseError (str, hash) {\n if (hash.recoverable) {\n this.trace(str);\n } else {\n var error = new Error(str);\n error.hash = hash;\n throw error;\n }\n},\nparse: function parse(input) {\n var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;\n var args = lstack.slice.call(arguments, 1);\n var lexer = Object.create(this.lexer);\n var sharedState = { yy: {} };\n for (var k in this.yy) {\n if (Object.prototype.hasOwnProperty.call(this.yy, k)) {\n sharedState.yy[k] = this.yy[k];\n }\n }\n lexer.setInput(input, sharedState.yy);\n sharedState.yy.lexer = lexer;\n sharedState.yy.parser = this;\n if (typeof lexer.yylloc == 'undefined') {\n lexer.yylloc = {};\n }\n var yyloc = lexer.yylloc;\n lstack.push(yyloc);\n var ranges = lexer.options && lexer.options.ranges;\n if (typeof sharedState.yy.parseError === 'function') {\n this.parseError = sharedState.yy.parseError;\n } else {\n this.parseError = Object.getPrototypeOf(this).parseError;\n }\n function popStack(n) {\n stack.length = stack.length - 2 * n;\n vstack.length = vstack.length - n;\n lstack.length = lstack.length - n;\n }\n function lex() {\n var token;\n token = tstack.pop() || lexer.lex() || EOF;\n if (typeof token !== 'number') {\n if (token instanceof Array) {\n tstack = token;\n token = tstack.pop();\n }\n token = self.symbols_[token] || token;\n }\n return token;\n }\n var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;\n while (true) {\n state = stack[stack.length - 1];\n if (this.defaultActions[state]) {\n action = this.defaultActions[state];\n } else {\n if (symbol === null || typeof symbol == 'undefined') {\n symbol = lex();\n }\n action = table[state] && table[state][symbol];\n }\n if (typeof action === 'undefined' || !action.length || !action[0]) {\n var errStr = '';\n expected = [];\n for (p in table[state]) {\n if (this.terminals_[p] && p > TERROR) {\n expected.push('\\'' + this.terminals_[p] + '\\'');\n }\n }\n if (lexer.showPosition) {\n errStr = 'Parse error on line ' + (yylineno + 1) + ':\\n' + lexer.showPosition() + '\\nExpecting ' + expected.join(', ') + ', got \\'' + (this.terminals_[symbol] || symbol) + '\\'';\n } else {\n errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\\'' + (this.terminals_[symbol] || symbol) + '\\'');\n }\n this.parseError(errStr, {\n text: lexer.match,\n token: this.terminals_[symbol] || symbol,\n line: lexer.yylineno,\n loc: yyloc,\n expected: expected\n });\n }\n if (action[0] instanceof Array && action.length > 1) {\n throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);\n }\n switch (action[0]) {\n case 1:\n stack.push(symbol);\n vstack.push(lexer.yytext);\n lstack.push(lexer.yylloc);\n stack.push(action[1]);\n symbol = null;\n if (!preErrorSymbol) {\n yyleng = lexer.yyleng;\n yytext = lexer.yytext;\n yylineno = lexer.yylineno;\n yyloc = lexer.yylloc;\n if (recovering > 0) {\n recovering--;\n }\n } else {\n symbol = preErrorSymbol;\n preErrorSymbol = null;\n }\n break;\n case 2:\n len = this.productions_[action[1]][1];\n yyval.$ = vstack[vstack.length - len];\n yyval._$ = {\n first_line: lstack[lstack.length - (len || 1)].first_line,\n last_line: lstack[lstack.length - 1].last_line,\n first_column: lstack[lstack.length - (len || 1)].first_column,\n last_column: lstack[lstack.length - 1].last_column\n };\n if (ranges) {\n yyval._$.range = [\n lstack[lstack.length - (len || 1)].range[0],\n lstack[lstack.length - 1].range[1]\n ];\n }\n r = this.performAction.apply(yyval, [\n yytext,\n yyleng,\n yylineno,\n sharedState.yy,\n action[1],\n vstack,\n lstack\n ].concat(args));\n if (typeof r !== 'undefined') {\n return r;\n }\n if (len) {\n stack = stack.slice(0, -1 * len * 2);\n vstack = vstack.slice(0, -1 * len);\n lstack = lstack.slice(0, -1 * len);\n }\n stack.push(this.productions_[action[1]][0]);\n vstack.push(yyval.$);\n lstack.push(yyval._$);\n newState = table[stack[stack.length - 2]][stack[stack.length - 1]];\n stack.push(newState);\n break;\n case 3:\n return true;\n }\n }\n return true;\n}};\n\n/* generated by jison-lex 0.3.4 */\nvar lexer = (function(){\nvar lexer = ({\n\nEOF:1,\n\nparseError:function parseError(str, hash) {\n if (this.yy.parser) {\n this.yy.parser.parseError(str, hash);\n } else {\n throw new Error(str);\n }\n },\n\n// resets the lexer, sets new input\nsetInput:function (input, yy) {\n this.yy = yy || this.yy || {};\n this._input = input;\n this._more = this._backtrack = this.done = false;\n this.yylineno = this.yyleng = 0;\n this.yytext = this.matched = this.match = '';\n this.conditionStack = ['INITIAL'];\n this.yylloc = {\n first_line: 1,\n first_column: 0,\n last_line: 1,\n last_column: 0\n };\n if (this.options.ranges) {\n this.yylloc.range = [0,0];\n }\n this.offset = 0;\n return this;\n },\n\n// consumes and returns one char from the input\ninput:function () {\n var ch = this._input[0];\n this.yytext += ch;\n this.yyleng++;\n this.offset++;\n this.match += ch;\n this.matched += ch;\n var lines = ch.match(/(?:\\r\\n?|\\n).*/g);\n if (lines) {\n this.yylineno++;\n this.yylloc.last_line++;\n } else {\n this.yylloc.last_column++;\n }\n if (this.options.ranges) {\n this.yylloc.range[1]++;\n }\n\n this._input = this._input.slice(1);\n return ch;\n },\n\n// unshifts one char (or a string) into the input\nunput:function (ch) {\n var len = ch.length;\n var lines = ch.split(/(?:\\r\\n?|\\n)/g);\n\n this._input = ch + this._input;\n this.yytext = this.yytext.substr(0, this.yytext.length - len);\n //this.yyleng -= len;\n this.offset -= len;\n var oldLines = this.match.split(/(?:\\r\\n?|\\n)/g);\n this.match = this.match.substr(0, this.match.length - 1);\n this.matched = this.matched.substr(0, this.matched.length - 1);\n\n if (lines.length - 1) {\n this.yylineno -= lines.length - 1;\n }\n var r = this.yylloc.range;\n\n this.yylloc = {\n first_line: this.yylloc.first_line,\n last_line: this.yylineno + 1,\n first_column: this.yylloc.first_column,\n last_column: lines ?\n (lines.length === oldLines.length ? this.yylloc.first_column : 0)\n + oldLines[oldLines.length - lines.length].length - lines[0].length :\n this.yylloc.first_column - len\n };\n\n if (this.options.ranges) {\n this.yylloc.range = [r[0], r[0] + this.yyleng - len];\n }\n this.yyleng = this.yytext.length;\n return this;\n },\n\n// When called from action, caches matched text and appends it on next action\nmore:function () {\n this._more = true;\n return this;\n },\n\n// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.\nreject:function () {\n if (this.options.backtrack_lexer) {\n this._backtrack = true;\n } else {\n return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\\n' + this.showPosition(), {\n text: \"\",\n token: null,\n line: this.yylineno\n });\n\n }\n return this;\n },\n\n// retain first n characters of the match\nless:function (n) {\n this.unput(this.match.slice(n));\n },\n\n// displays already matched input, i.e. for error messages\npastInput:function () {\n var past = this.matched.substr(0, this.matched.length - this.match.length);\n return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\\n/g, \"\");\n },\n\n// displays upcoming input, i.e. for error messages\nupcomingInput:function () {\n var next = this.match;\n if (next.length < 20) {\n next += this._input.substr(0, 20-next.length);\n }\n return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\\n/g, \"\");\n },\n\n// displays the character position where the lexing error occurred, i.e. for error messages\nshowPosition:function () {\n var pre = this.pastInput();\n var c = new Array(pre.length + 1).join(\"-\");\n return pre + this.upcomingInput() + \"\\n\" + c + \"^\";\n },\n\n// test the lexed token: return FALSE when not a match, otherwise return token\ntest_match:function(match, indexed_rule) {\n var token,\n lines,\n backup;\n\n if (this.options.backtrack_lexer) {\n // save context\n backup = {\n yylineno: this.yylineno,\n yylloc: {\n first_line: this.yylloc.first_line,\n last_line: this.last_line,\n first_column: this.yylloc.first_column,\n last_column: this.yylloc.last_column\n },\n yytext: this.yytext,\n match: this.match,\n matches: this.matches,\n matched: this.matched,\n yyleng: this.yyleng,\n offset: this.offset,\n _more: this._more,\n _input: this._input,\n yy: this.yy,\n conditionStack: this.conditionStack.slice(0),\n done: this.done\n };\n if (this.options.ranges) {\n backup.yylloc.range = this.yylloc.range.slice(0);\n }\n }\n\n lines = match[0].match(/(?:\\r\\n?|\\n).*/g);\n if (lines) {\n this.yylineno += lines.length;\n }\n this.yylloc = {\n first_line: this.yylloc.last_line,\n last_line: this.yylineno + 1,\n first_column: this.yylloc.last_column,\n last_column: lines ?\n lines[lines.length - 1].length - lines[lines.length - 1].match(/\\r?\\n?/)[0].length :\n this.yylloc.last_column + match[0].length\n };\n this.yytext += match[0];\n this.match += match[0];\n this.matches = match;\n this.yyleng = this.yytext.length;\n if (this.options.ranges) {\n this.yylloc.range = [this.offset, this.offset += this.yyleng];\n }\n this._more = false;\n this._backtrack = false;\n this._input = this._input.slice(match[0].length);\n this.matched += match[0];\n token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);\n if (this.done && this._input) {\n this.done = false;\n }\n if (token) {\n return token;\n } else if (this._backtrack) {\n // recover context\n for (var k in backup) {\n this[k] = backup[k];\n }\n return false; // rule action called reject() implying the next rule should be tested instead.\n }\n return false;\n },\n\n// return next match in input\nnext:function () {\n if (this.done) {\n return this.EOF;\n }\n if (!this._input) {\n this.done = true;\n }\n\n var token,\n match,\n tempMatch,\n index;\n if (!this._more) {\n this.yytext = '';\n this.match = '';\n }\n var rules = this._currentRules();\n for (var i = 0; i < rules.length; i++) {\n tempMatch = this._input.match(this.rules[rules[i]]);\n if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {\n match = tempMatch;\n index = i;\n if (this.options.backtrack_lexer) {\n token = this.test_match(tempMatch, rules[i]);\n if (token !== false) {\n return token;\n } else if (this._backtrack) {\n match = false;\n continue; // rule action called reject() implying a rule MISmatch.\n } else {\n // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)\n return false;\n }\n } else if (!this.options.flex) {\n break;\n }\n }\n }\n if (match) {\n token = this.test_match(match, rules[index]);\n if (token !== false) {\n return token;\n }\n // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)\n return false;\n }\n if (this._input === \"\") {\n return this.EOF;\n } else {\n return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\\n' + this.showPosition(), {\n text: \"\",\n token: null,\n line: this.yylineno\n });\n }\n },\n\n// return next match that has a token\nlex:function lex () {\n var r = this.next();\n if (r) {\n return r;\n } else {\n return this.lex();\n }\n },\n\n// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)\nbegin:function begin (condition) {\n this.conditionStack.push(condition);\n },\n\n// pop the previously active lexer condition state off the condition stack\npopState:function popState () {\n var n = this.conditionStack.length - 1;\n if (n > 0) {\n return this.conditionStack.pop();\n } else {\n return this.conditionStack[0];\n }\n },\n\n// produce the lexer rule set which is active for the currently active lexer condition state\n_currentRules:function _currentRules () {\n if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {\n return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;\n } else {\n return this.conditions[\"INITIAL\"].rules;\n }\n },\n\n// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available\ntopState:function topState (n) {\n n = this.conditionStack.length - 1 - Math.abs(n || 0);\n if (n >= 0) {\n return this.conditionStack[n];\n } else {\n return \"INITIAL\";\n }\n },\n\n// alias for begin(condition)\npushState:function pushState (condition) {\n this.begin(condition);\n },\n\n// return the number of states currently on the stack\nstateStackSize:function stateStackSize() {\n return this.conditionStack.length;\n },\noptions: {\"case-insensitive\":true},\nperformAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {\nvar YYSTATE=YY_START;\nswitch($avoiding_name_collisions) {\ncase 0: this.begin(\"acc_title\");return 22; \nbreak;\ncase 1: this.popState(); return \"acc_title_value\"; \nbreak;\ncase 2: this.begin(\"acc_descr\");return 24; \nbreak;\ncase 3: this.popState(); return \"acc_descr_value\"; \nbreak;\ncase 4: this.begin(\"acc_descr_multiline\");\nbreak;\ncase 5: this.popState(); \nbreak;\ncase 6:return \"acc_descr_multiline_value\";\nbreak;\ncase 7:return 10;\nbreak;\ncase 8:/* skip whitespace */\nbreak;\ncase 9:return 8;\nbreak;\ncase 10:return 28;\nbreak;\ncase 11:return 48;\nbreak;\ncase 12:return 4;\nbreak;\ncase 13: this.begin(\"block\"); return 15; \nbreak;\ncase 14:return 36;\nbreak;\ncase 15:/* skip whitespace in block */\nbreak;\ncase 16:return 37\nbreak;\ncase 17:return 34;\nbreak;\ncase 18:return 34\nbreak;\ncase 19:return 38;\nbreak;\ncase 20:/* nothing */\nbreak;\ncase 21: this.popState(); return 17; \nbreak;\ncase 22:return yy_.yytext[0];\nbreak;\ncase 23:return 18;\nbreak;\ncase 24:return 19;\nbreak;\ncase 25:return 41;\nbreak;\ncase 26:return 43;\nbreak;\ncase 27:return 43;\nbreak;\ncase 28:return 43;\nbreak;\ncase 29:return 41;\nbreak;\ncase 30:return 41;\nbreak;\ncase 31:return 42;\nbreak;\ncase 32:return 42;\nbreak;\ncase 33:return 42;\nbreak;\ncase 34:return 42;\nbreak;\ncase 35:return 42;\nbreak;\ncase 36:return 43;\nbreak;\ncase 37:return 42;\nbreak;\ncase 38:return 43;\nbreak;\ncase 39:return 44;\nbreak;\ncase 40:return 44;\nbreak;\ncase 41:return 44;\nbreak;\ncase 42:return 44;\nbreak;\ncase 43:return 41;\nbreak;\ncase 44:return 42;\nbreak;\ncase 45:return 43;\nbreak;\ncase 46:return 45;\nbreak;\ncase 47:return 46;\nbreak;\ncase 48:return 47;\nbreak;\ncase 49:return 47;\nbreak;\ncase 50:return 46;\nbreak;\ncase 51:return 46;\nbreak;\ncase 52:return 46;\nbreak;\ncase 53:return 27;\nbreak;\ncase 54:return yy_.yytext[0];\nbreak;\ncase 55:return 6;\nbreak;\n}\n},\nrules: [/^(?:accTitle\\s*:\\s*)/i,/^(?:(?!\\n||)*[^\\n]*)/i,/^(?:accDescr\\s*:\\s*)/i,/^(?:(?!\\n||)*[^\\n]*)/i,/^(?:accDescr\\s*\\{\\s*)/i,/^(?:[\\}])/i,/^(?:[^\\}]*)/i,/^(?:[\\n]+)/i,/^(?:\\s+)/i,/^(?:[\\s]+)/i,/^(?:\"[^\"%\\r\\n\\v\\b\\\\]+\")/i,/^(?:\"[^\"]*\")/i,/^(?:erDiagram\\b)/i,/^(?:\\{)/i,/^(?:,)/i,/^(?:\\s+)/i,/^(?:\\b((?:PK)|(?:FK)|(?:UK))\\b)/i,/^(?:(.*?)[~](.*?)*[~])/i,/^(?:[\\*A-Za-z_][A-Za-z0-9\\-_\\[\\]\\(\\)]*)/i,/^(?:\"[^\"]*\")/i,/^(?:[\\n]+)/i,/^(?:\\})/i,/^(?:.)/i,/^(?:\\[)/i,/^(?:\\])/i,/^(?:one or zero\\b)/i,/^(?:one or more\\b)/i,/^(?:one or many\\b)/i,/^(?:1\\+)/i,/^(?:\\|o\\b)/i,/^(?:zero or one\\b)/i,/^(?:zero or more\\b)/i,/^(?:zero or many\\b)/i,/^(?:0\\+)/i,/^(?:\\}o\\b)/i,/^(?:many\\(0\\))/i,/^(?:many\\(1\\))/i,/^(?:many\\b)/i,/^(?:\\}\\|)/i,/^(?:one\\b)/i,/^(?:only one\\b)/i,/^(?:1\\b)/i,/^(?:\\|\\|)/i,/^(?:o\\|)/i,/^(?:o\\{)/i,/^(?:\\|\\{)/i,/^(?:\\s*u\\b)/i,/^(?:\\.\\.)/i,/^(?:--)/i,/^(?:to\\b)/i,/^(?:optionally to\\b)/i,/^(?:\\.-)/i,/^(?:-\\.)/i,/^(?:[A-Za-z_][A-Za-z0-9\\-_]*)/i,/^(?:.)/i,/^(?:$)/i],\nconditions: {\"acc_descr_multiline\":{\"rules\":[5,6],\"inclusive\":false},\"acc_descr\":{\"rules\":[3],\"inclusive\":false},\"acc_title\":{\"rules\":[1],\"inclusive\":false},\"block\":{\"rules\":[14,15,16,17,18,19,20,21,22],\"inclusive\":false},\"INITIAL\":{\"rules\":[0,2,4,7,8,9,10,11,12,13,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55],\"inclusive\":true}}\n});\nreturn lexer;\n})();\nparser.lexer = lexer;\nfunction Parser () {\n this.yy = {};\n}\nParser.prototype = parser;parser.Parser = Parser;\nreturn new Parser;\n})(); \n\tparser.parser = parser;\n\texport { parser };\n\texport default parser;\n\t", "import { log } from '../../logger.js';\nimport { getConfig } from '../../diagram-api/diagramAPI.js';\n\nimport {\n setAccTitle,\n getAccTitle,\n getAccDescription,\n setAccDescription,\n clear as commonClear,\n setDiagramTitle,\n getDiagramTitle,\n} from '../common/commonDb.js';\n\nlet entities = new Map();\nlet relationships = [];\n\nconst Cardinality = {\n ZERO_OR_ONE: 'ZERO_OR_ONE',\n ZERO_OR_MORE: 'ZERO_OR_MORE',\n ONE_OR_MORE: 'ONE_OR_MORE',\n ONLY_ONE: 'ONLY_ONE',\n MD_PARENT: 'MD_PARENT',\n};\n\nconst Identification = {\n NON_IDENTIFYING: 'NON_IDENTIFYING',\n IDENTIFYING: 'IDENTIFYING',\n};\n/**\n * Add entity\n * @param {string} name - The name of the entity\n * @param {string | undefined} alias - The alias of the entity\n */\nconst addEntity = function (name, alias = undefined) {\n if (!entities.has(name)) {\n entities.set(name, { attributes: [], alias });\n log.info('Added new entity :', name);\n } else if (!entities.get(name).alias && alias) {\n entities.get(name).alias = alias;\n log.info(`Add alias '${alias}' to entity '${name}'`);\n }\n\n return entities.get(name);\n};\n\nconst getEntities = () => entities;\n\nconst addAttributes = function (entityName, attribs) {\n let entity = addEntity(entityName); // May do nothing (if entity has already been added)\n\n // Process attribs in reverse order due to effect of recursive construction (last attribute is first)\n let i;\n for (i = attribs.length - 1; i >= 0; i--) {\n entity.attributes.push(attribs[i]);\n log.debug('Added attribute ', attribs[i].attributeName);\n }\n};\n\n/**\n * Add a relationship\n *\n * @param entA The first entity in the relationship\n * @param rolA The role played by the first entity in relation to the second\n * @param entB The second entity in the relationship\n * @param rSpec The details of the relationship between the two entities\n */\nconst addRelationship = function (entA, rolA, entB, rSpec) {\n let rel = {\n entityA: entA,\n roleA: rolA,\n entityB: entB,\n relSpec: rSpec,\n };\n\n relationships.push(rel);\n log.debug('Added new relationship :', rel);\n};\n\nconst getRelationships = () => relationships;\n\nconst clear = function () {\n entities = new Map();\n relationships = [];\n commonClear();\n};\n\nexport default {\n Cardinality,\n Identification,\n getConfig: () => getConfig().er,\n addEntity,\n addAttributes,\n getEntities,\n addRelationship,\n getRelationships,\n clear,\n setAccTitle,\n getAccTitle,\n setAccDescription,\n getAccDescription,\n setDiagramTitle,\n getDiagramTitle,\n};\n", "import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';\nimport { line, curveBasis, select } from 'd3';\nimport { layout as dagreLayout } from 'dagre-d3-es/src/dagre/index.js';\nimport { getConfig } from '../../diagram-api/diagramAPI.js';\nimport { log } from '../../logger.js';\nimport utils from '../../utils.js';\nimport erMarkers from './erMarkers.js';\nimport { configureSvgSize } from '../../setupGraphViewbox.js';\nimport { parseGenericTypes } from '../common/common.js';\nimport { v5 as uuid5 } from 'uuid';\n\n/** Regex used to remove chars from the entity name so the result can be used in an id */\nconst BAD_ID_CHARS_REGEXP = /[^\\dA-Za-z](\\W)*/g;\n\n// Configuration\nlet conf = {};\n\n// Map so we can look up the id of an entity based on the name\nlet entityNameIds = new Map();\n\n/**\n * Allows the top-level API module to inject config specific to this renderer, storing it in the\n * local conf object. Note that generic config still needs to be retrieved using getConfig()\n * imported from the config module\n *\n * @param cnf\n */\nexport const setConf = function (cnf) {\n const keys = Object.keys(cnf);\n for (const key of keys) {\n conf[key] = cnf[key];\n }\n};\n\n/**\n * Draw attributes for an entity\n *\n * @param groupNode The svg group node for the entity\n * @param entityTextNode The svg node for the entity label text\n * @param attributes An array of attributes defined for the entity (each attribute has a type and a\n * name)\n * @returns {object} The bounding box of the entity, after attributes have been added. The bounding\n * box has a .width and .height\n */\nconst drawAttributes = (groupNode, entityTextNode, attributes) => {\n const heightPadding = conf.entityPadding / 3; // Padding internal to attribute boxes\n const widthPadding = conf.entityPadding / 3; // Ditto\n const attrFontSize = conf.fontSize * 0.85;\n const labelBBox = entityTextNode.node().getBBox();\n const attributeNodes = []; // Intermediate storage for attribute nodes created so that we can do a second pass\n let hasKeyType = false;\n let hasComment = false;\n let maxTypeWidth = 0;\n let maxNameWidth = 0;\n let maxKeyWidth = 0;\n let maxCommentWidth = 0;\n let cumulativeHeight = labelBBox.height + heightPadding * 2;\n let attrNum = 1;\n\n // Check to see if any of the attributes has a key or a comment\n attributes.forEach((item) => {\n if (item.attributeKeyTypeList !== undefined && item.attributeKeyTypeList.length > 0) {\n hasKeyType = true;\n }\n\n if (item.attributeComment !== undefined) {\n hasComment = true;\n }\n });\n\n attributes.forEach((item) => {\n const attrPrefix = `${entityTextNode.node().id}-attr-${attrNum}`;\n let nodeHeight = 0;\n\n const attributeType = parseGenericTypes(item.attributeType);\n\n // Add a text node for the attribute type\n const typeNode = groupNode\n .append('text')\n .classed('er entityLabel', true)\n .attr('id', `${attrPrefix}-type`)\n .attr('x', 0)\n .attr('y', 0)\n .style('dominant-baseline', 'middle')\n .style('text-anchor', 'left')\n .style('font-family', getConfig().fontFamily)\n .style('font-size', attrFontSize + 'px')\n .text(attributeType);\n\n // Add a text node for the attribute name\n const nameNode = groupNode\n .append('text')\n .classed('er entityLabel', true)\n .attr('id', `${attrPrefix}-name`)\n .attr('x', 0)\n .attr('y', 0)\n .style('dominant-baseline', 'middle')\n .style('text-anchor', 'left')\n .style('font-family', getConfig().fontFamily)\n .style('font-size', attrFontSize + 'px')\n .text(item.attributeName);\n\n const attributeNode = {};\n attributeNode.tn = typeNode;\n attributeNode.nn = nameNode;\n\n const typeBBox = typeNode.node().getBBox();\n const nameBBox = nameNode.node().getBBox();\n maxTypeWidth = Math.max(maxTypeWidth, typeBBox.width);\n maxNameWidth = Math.max(maxNameWidth, nameBBox.width);\n\n nodeHeight = Math.max(typeBBox.height, nameBBox.height);\n\n if (hasKeyType) {\n const keyTypeNodeText =\n item.attributeKeyTypeList !== undefined ? item.attributeKeyTypeList.join(',') : '';\n\n const keyTypeNode = groupNode\n .append('text')\n .classed('er entityLabel', true)\n .attr('id', `${attrPrefix}-key`)\n .attr('x', 0)\n .attr('y', 0)\n .style('dominant-baseline', 'middle')\n .style('text-anchor', 'left')\n .style('font-family', getConfig().fontFamily)\n .style('font-size', attrFontSize + 'px')\n .text(keyTypeNodeText);\n\n attributeNode.kn = keyTypeNode;\n const keyTypeBBox = keyTypeNode.node().getBBox();\n maxKeyWidth = Math.max(maxKeyWidth, keyTypeBBox.width);\n nodeHeight = Math.max(nodeHeight, keyTypeBBox.height);\n }\n\n if (hasComment) {\n const commentNode = groupNode\n .append('text')\n .classed('er entityLabel', true)\n .attr('id', `${attrPrefix}-comment`)\n .attr('x', 0)\n .attr('y', 0)\n .style('dominant-baseline', 'middle')\n .style('text-anchor', 'left')\n .style('font-family', getConfig().fontFamily)\n .style('font-size', attrFontSize + 'px')\n .text(item.attributeComment || '');\n\n attributeNode.cn = commentNode;\n const commentNodeBBox = commentNode.node().getBBox();\n maxCommentWidth = Math.max(maxCommentWidth, commentNodeBBox.width);\n nodeHeight = Math.max(nodeHeight, commentNodeBBox.height);\n }\n\n attributeNode.height = nodeHeight;\n // Keep a reference to the nodes so that we can iterate through them later\n attributeNodes.push(attributeNode);\n cumulativeHeight += nodeHeight + heightPadding * 2;\n attrNum += 1;\n });\n\n let widthPaddingFactor = 4;\n if (hasKeyType) {\n widthPaddingFactor += 2;\n }\n if (hasComment) {\n widthPaddingFactor += 2;\n }\n\n const maxWidth = maxTypeWidth + maxNameWidth + maxKeyWidth + maxCommentWidth;\n\n // Calculate the new bounding box of the overall entity, now that attributes have been added\n const bBox = {\n width: Math.max(\n conf.minEntityWidth,\n Math.max(\n labelBBox.width + conf.entityPadding * 2,\n maxWidth + widthPadding * widthPaddingFactor\n )\n ),\n height:\n attributes.length > 0\n ? cumulativeHeight\n : Math.max(conf.minEntityHeight, labelBBox.height + conf.entityPadding * 2),\n };\n\n if (attributes.length > 0) {\n // There might be some spare width for padding out attributes if the entity name is very long\n const spareColumnWidth = Math.max(\n 0,\n (bBox.width - maxWidth - widthPadding * widthPaddingFactor) / (widthPaddingFactor / 2)\n );\n\n // Position the entity label near the top of the entity bounding box\n entityTextNode.attr(\n 'transform',\n 'translate(' + bBox.width / 2 + ',' + (heightPadding + labelBBox.height / 2) + ')'\n );\n\n // Add rectangular boxes for the attribute types/names\n let heightOffset = labelBBox.height + heightPadding * 2; // Start at the bottom of the entity label\n let attribStyle = 'attributeBoxOdd'; // We will flip the style on alternate rows to achieve a banded effect\n\n attributeNodes.forEach((attributeNode) => {\n // Calculate the alignment y coordinate for the type/name of the attribute\n const alignY = heightOffset + heightPadding + attributeNode.height / 2;\n\n // Position the type attribute\n attributeNode.tn.attr('transform', 'translate(' + widthPadding + ',' + alignY + ')');\n\n // TODO Handle spareWidth in attr('width')\n // Insert a rectangle for the type\n const typeRect = groupNode\n .insert('rect', '#' + attributeNode.tn.node().id)\n .classed(`er ${attribStyle}`, true)\n .attr('x', 0)\n .attr('y', heightOffset)\n .attr('width', maxTypeWidth + widthPadding * 2 + spareColumnWidth)\n .attr('height', attributeNode.height + heightPadding * 2);\n\n const nameXOffset = parseFloat(typeRect.attr('x')) + parseFloat(typeRect.attr('width'));\n\n // Position the name attribute\n attributeNode.nn.attr(\n 'transform',\n 'translate(' + (nameXOffset + widthPadding) + ',' + alignY + ')'\n );\n\n // Insert a rectangle for the name\n const nameRect = groupNode\n .insert('rect', '#' + attributeNode.nn.node().id)\n .classed(`er ${attribStyle}`, true)\n .attr('x', nameXOffset)\n .attr('y', heightOffset)\n .attr('width', maxNameWidth + widthPadding * 2 + spareColumnWidth)\n .attr('height', attributeNode.height + heightPadding * 2);\n\n let keyTypeAndCommentXOffset =\n parseFloat(nameRect.attr('x')) + parseFloat(nameRect.attr('width'));\n\n if (hasKeyType) {\n // Position the key type attribute\n attributeNode.kn.attr(\n 'transform',\n 'translate(' + (keyTypeAndCommentXOffset + widthPadding) + ',' + alignY + ')'\n );\n\n // Insert a rectangle for the key type\n const keyTypeRect = groupNode\n .insert('rect', '#' + attributeNode.kn.node().id)\n .classed(`er ${attribStyle}`, true)\n .attr('x', keyTypeAndCommentXOffset)\n .attr('y', heightOffset)\n .attr('width', maxKeyWidth + widthPadding * 2 + spareColumnWidth)\n .attr('height', attributeNode.height + heightPadding * 2);\n\n keyTypeAndCommentXOffset =\n parseFloat(keyTypeRect.attr('x')) + parseFloat(keyTypeRect.attr('width'));\n }\n\n if (hasComment) {\n // Position the comment attribute\n attributeNode.cn.attr(\n 'transform',\n 'translate(' + (keyTypeAndCommentXOffset + widthPadding) + ',' + alignY + ')'\n );\n\n // Insert a rectangle for the comment\n groupNode\n .insert('rect', '#' + attributeNode.cn.node().id)\n .classed(`er ${attribStyle}`, 'true')\n .attr('x', keyTypeAndCommentXOffset)\n .attr('y', heightOffset)\n .attr('width', maxCommentWidth + widthPadding * 2 + spareColumnWidth)\n .attr('height', attributeNode.height + heightPadding * 2);\n }\n\n // Increment the height offset to move to the next row\n heightOffset += attributeNode.height + heightPadding * 2;\n\n // Flip the attribute style for row banding\n attribStyle = attribStyle === 'attributeBoxOdd' ? 'attributeBoxEven' : 'attributeBoxOdd';\n });\n } else {\n // Ensure the entity box is a decent size without any attributes\n bBox.height = Math.max(conf.minEntityHeight, cumulativeHeight);\n\n // Position the entity label in the middle of the box\n entityTextNode.attr('transform', 'translate(' + bBox.width / 2 + ',' + bBox.height / 2 + ')');\n }\n\n return bBox;\n};\n\n/**\n * Use D3 to construct the svg elements for the entities\n *\n * @param svgNode The svg node that contains the diagram\n * @param {Map<string, object>} entities The entities to be drawn\n * @param graph The graph that contains the vertex and edge definitions post-layout\n * @returns {object} The first entity that was inserted\n */\nconst drawEntities = function (svgNode, entities, graph) {\n const keys = [...entities.keys()];\n let firstOne;\n\n keys.forEach(function (entityName) {\n const entityId = generateId(entityName, 'entity');\n entityNameIds.set(entityName, entityId);\n\n // Create a group for each entity\n const groupNode = svgNode.append('g').attr('id', entityId);\n\n firstOne = firstOne === undefined ? entityId : firstOne;\n\n // Label the entity - this is done first so that we can get the bounding box\n // which then determines the size of the rectangle\n const textId = 'text-' + entityId;\n const textNode = groupNode\n .append('text')\n .classed('er entityLabel', true)\n .attr('id', textId)\n .attr('x', 0)\n .attr('y', 0)\n .style('dominant-baseline', 'middle')\n .style('text-anchor', 'middle')\n .style('font-family', getConfig().fontFamily)\n .style('font-size', conf.fontSize + 'px')\n .text(entities.get(entityName).alias ?? entityName);\n\n const { width: entityWidth, height: entityHeight } = drawAttributes(\n groupNode,\n textNode,\n entities.get(entityName).attributes\n );\n\n // Draw the rectangle - insert it before the text so that the text is not obscured\n const rectNode = groupNode\n .insert('rect', '#' + textId)\n .classed('er entityBox', true)\n .attr('x', 0)\n .attr('y', 0)\n .attr('width', entityWidth)\n .attr('height', entityHeight);\n\n const rectBBox = rectNode.node().getBBox();\n\n // Add the entity to the graph using the entityId\n graph.setNode(entityId, {\n width: rectBBox.width,\n height: rectBBox.height,\n shape: 'rect',\n id: entityId,\n });\n });\n return firstOne;\n}; // drawEntities\n\nconst adjustEntities = function (svgNode, graph) {\n graph.nodes().forEach(function (v) {\n if (v !== undefined && graph.node(v) !== undefined) {\n svgNode\n .select('#' + v)\n .attr(\n 'transform',\n 'translate(' +\n (graph.node(v).x - graph.node(v).width / 2) +\n ',' +\n (graph.node(v).y - graph.node(v).height / 2) +\n ' )'\n );\n }\n });\n};\n\n/**\n * Construct a name for an edge based on the names of the 2 entities and the role (relationship)\n * between them. Remove any spaces from it\n *\n * @param rel - A (parsed) relationship (e.g. one of the objects in the list returned by\n * erDb.getRelationships)\n * @returns {string}\n */\nconst getEdgeName = function (rel) {\n return (rel.entityA + rel.roleA + rel.entityB).replace(/\\s/g, '');\n};\n\n/**\n * Add each relationship to the graph\n *\n * @param relationships The relationships to be added\n * @param g The graph\n * @returns {Array} The array of relationships\n */\nconst addRelationships = function (relationships, g) {\n relationships.forEach(function (r) {\n g.setEdge(\n entityNameIds.get(r.entityA),\n entityNameIds.get(r.entityB),\n { relationship: r },\n getEdgeName(r)\n );\n });\n return relationships;\n}; // addRelationships\n\nlet relCnt = 0;\n/**\n * Draw a relationship using edge information from the graph\n *\n * @param svg The svg node\n * @param rel The relationship to draw in the svg\n * @param g The graph containing the edge information\n * @param insert The insertion point in the svg DOM (because relationships have markers that need to\n * sit 'behind' opaque entity boxes)\n * @param diagObj\n */\nconst drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {\n relCnt++;\n\n // Find the edge relating to this relationship\n const edge = g.edge(\n entityNameIds.get(rel.entityA),\n entityNameIds.get(rel.entityB),\n getEdgeName(rel)\n );\n\n // Get a function that will generate the line path\n const lineFunction = line()\n .x(function (d) {\n return d.x;\n })\n .y(function (d) {\n return d.y;\n })\n .curve(curveBasis);\n\n // Insert the line at the right place\n const svgPath = svg\n .insert('path', '#' + insert)\n .classed('er relationshipLine', true)\n .attr('d', lineFunction(edge.points))\n .style('stroke', conf.stroke)\n .style('fill', 'none');\n\n // ...and with dashes if necessary\n if (rel.relSpec.relType === diagObj.db.Identification.NON_IDENTIFYING) {\n svgPath.attr('stroke-dasharray', '8,8');\n }\n\n // TODO: Understand this better\n let url = '';\n if (conf.arrowMarkerAbsolute) {\n url =\n window.location.protocol +\n '//' +\n window.location.host +\n window.location.pathname +\n window.location.search;\n url = url.replace(/\\(/g, '\\\\(');\n url = url.replace(/\\)/g, '\\\\)');\n }\n\n // Decide which start and end markers it needs. It may be possible to be more concise here\n // by reversing a start marker to make an end marker...but this will do for now\n\n // Note that the 'A' entity's marker is at the end of the relationship and the 'B' entity's marker is at the start\n switch (rel.relSpec.cardA) {\n case diagObj.db.Cardinality.ZERO_OR_ONE:\n svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_END + ')');\n break;\n case diagObj.db.Cardinality.ZERO_OR_MORE:\n svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_END + ')');\n break;\n case diagObj.db.Cardinality.ONE_OR_MORE:\n svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')');\n break;\n case diagObj.db.Cardinality.ONLY_ONE:\n svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')');\n break;\n case diagObj.db.Cardinality.MD_PARENT:\n svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.MD_PARENT_END + ')');\n break;\n }\n\n switch (rel.relSpec.cardB) {\n case diagObj.db.Cardinality.ZERO_OR_ONE:\n svgPath.attr(\n 'marker-start',\n 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')'\n );\n break;\n case diagObj.db.Cardinality.ZERO_OR_MORE:\n svgPath.attr(\n 'marker-start',\n 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')'\n );\n break;\n case diagObj.db.Cardinality.ONE_OR_MORE:\n svgPath.attr(\n 'marker