UNPKG

yuml-diagram

Version:

UML diagramming package based on the yUML syntax

248 lines (213 loc) 8.21 kB
require('../utils/yuml2dot-utils.js')(); /* Syntax as specified in yuml.me Start (start) End (end) Activity (Find Products) Flow (start)->(Find Products) Multiple Assoc. (start)->(Find Products)->(end) Decisions (start)-><d1> Decisions w/Label (start)-><d1>[logged in]->(Show Dashboard), <d1>[not logged in]->(Show Login Page) Parallel (Action1)->|a|,(Action 2)->|a| Note (Action1)-(note: A note message here) Object Node [Object] Comment // Comments */ module.exports = function(specLines, options) { function parseYumlExpr(specLine) { var exprs = []; var parts = this.splitYumlExpr(specLine, "[(<|"); // yUML syntax allows any character in decision labels. // The following variable serves as flag to avoid parsing // brackets characters inside labels. var isDecisionLabel = false; var decisionLabelBuffer = ""; for (var i=0; i<parts.length; i++) { var part = parts[i].trim(); if (part.length == 0) continue; if (part.match(/->$/)) // arrow { part = decisionLabelBuffer; exprs.push(["edge", "none", "vee", part, "solid"]); isDecisionLabel = false; decisionLabelBuffer = ""; } else if (part.match(/^\(.*\)$/)) // activity { part = part.substr(1, part.length-2); var ret = extractBgAndNote(part, true); exprs.push([ret.isNote ? "note" : "record", ret.part, 'rounded', ret.bg, ret.fontcolor]); } else if (part.match(/^<.*>$/)) // decision { part = part.substr(1, part.length-2); exprs.push(["diamond", part]); } else if (/^\[.*\]$/.test(part)) // object node { if (i == 0 || exprs[exprs.length-1][0] == "edge") { exprs.push(["record", part.substr(1, part.length - 2).trim()]); } else { decisionLabelBuffer = part.substr(1, part.length - 2).trim(); isDecisionLabel = true; } } else if (part.match(/^\|.*\|$/)) // bar { part = part.substr(1, part.length-2); exprs.push(["mrecord", part]); } else if (part == '-') // connector for notes { exprs.push(["edge", "none", "none", "", "solid"]); } else throw("Invalid expression"); } return exprs; } function composeDotExpr(specLines, options) { var uids = {}; var len = 0; var elements = []; var headports = { LR: "w", RL: "e", TB: "n" }; for (var i=0; i<specLines.length; i++) { var elem = parseYumlExpr(specLines[i]); for (var k=0; k<elem.length; k++) { if (elem[k][0] == "note" || elem[k][0] == "record") { var label = elem[k][1]; if (uids.hasOwnProperty(recordName(label))) continue; var uid = 'A' + (len++).toString(); uids[recordName(label)] = uid; if (elem[k][0]=="record" && (label=="start" || label=="end")) { var node = { shape: label=="start" ? "circle" : "doublecircle", height: 0.3, width: 0.3, margin: "0,0", label: "" } } else { var node = { shape: elem[k][0], height: 0.5, fontsize: 10, margin: "0.20,0.05", label: escape_label(label), style: elem[k][2] } if (elem[k][3]) { node.style += ",filled"; node.fillcolor = elem[k][3]; } if (elem[k][4]) node.fontcolor = elem[k][4]; } elements.push([uid, node]); } else if (elem[k][0] == "diamond") { var label = elem[k][1]; if (uids.hasOwnProperty(recordName(label))) continue; var uid = 'A' + (len++).toString(); uids[recordName(label)] = uid; var node = { shape: "diamond", height: 0.5, width: 0.5, margin: "0,0", label: "" } elements.push([uid, node]); } else if (elem[k][0] == "mrecord") { var label = elem[k][1]; if (uids.hasOwnProperty(recordName(label))) continue; var uid = 'A' + (len++).toString(); uids[recordName(label)] = uid; var node = { shape: "record", height: options.dir == "TB" ? 0.05 : 0.5, width: options.dir == "TB" ? 0.5 : 0.05, margin: "0,0", style: "filled", label: "", fontsize: 1, penwidth: 4 } elements.push([uid, node]); } } for (var k=1; k<(elem.length-1); k++) { if (elem[k][0] == "edge" && elem[k-1][0] != "edge" && elem[k+1][0] != "edge") { var style = (elem[k-1][0] == 'note' || elem[k+1][0] == 'note') ? "dashed" : elem[k][4]; var edge = { shape: "edge", dir: "both", style: style, arrowtail: elem[k][1], arrowhead: elem[k][2], labeldistance: 1, fontsize: 10 } if (elem[k][3].length > 0) edge.label = elem[k][3]; var uid1 = uids[recordName(elem[k-1][1])]; var uid2 = uids[recordName(elem[k+1][1])]; if (elem[k+1][0] == "mrecord") { var facet = addBarFacet(elements, uid2); uid2 += ":" + facet + ":" + headports[options.dir]; } elements.push([uid1, uid2, edge]); } } } var dot = " ranksep = " + 0.5 + "\r\n"; dot += " rankdir = " + options.dir + "\r\n"; dot += serializeDotElements(elements); dot += '}\r\n'; return dot; } function addBarFacet(elements, name) { for (var i=0; i<elements.length; i++) { if (elements[i].length == 2 && elements[i][0] == name) { var node = elements[i][1]; var facetNum = 1; if (node.label.length > 0) { facetNum = node.label.split("|").length + 1; node.label += "|<f" + facetNum + ">"; } else node.label = "<f1>"; return "f" + facetNum; } } return null; } return composeDotExpr(specLines, options); }