Skip to content

Instantly share code, notes, and snippets.

@christhearchitect
Last active December 5, 2022 19:19
Show Gist options
  • Select an option

  • Save christhearchitect/ee35ab560d2d42773a5a982b5e240452 to your computer and use it in GitHub Desktop.

Select an option

Save christhearchitect/ee35ab560d2d42773a5a982b5e240452 to your computer and use it in GitHub Desktop.
#jArchi
/*
* This script attemps to import a draw.io (aka diagrams.net) diagram into an Archi model by
* empirically mapping the observable attributes of the XML elements exported from draw.io to Archimate
* elements and relationships. Please note that this mapping is fragile, because draw.io doesn't seem
* to generate the XML attributes in a very consistent way; also, we must unfortunately use element color
* as a key differentiator, to compensate for the lack of usable archimate information in the XML file.
* All of this means that minor changes in the draw.io XML format can break the mapping...
* Notes:
* -- To generate the draw.io file, export it as an *uncompressed* XML file.
* -- When running the script, make sure you have set the current model to import it in (e.g. by selecting a view in the model)
* -- In draw.io, make sure that the relationships you want to import are *really* connected to their source and target elements
* (in Archi, we need both end-elements to create a relationship)
* -- The script generates a view containing the newly imported objects, but this is pretty much experimental
* (the layout for the elements is the same as in draw.io, but the relations just have the default routing provided by Archi)
* -- The current code does very little error/exception handling
*
* NB: all of this is pretty much a work in progress. Possible future improvements:
* -- More test cases!
* -- Better error/exception handling
* -- Create new objects in a dedicated folder ?
* -- ..
*
* Author: christhearchitect
* Version: 0.2
*
*/
console.clear();
console.log("ImportDrawio\n------------\n");
// Feature toggles
var debug = true;
var forceImport = false; // if true, the script will import/create new model objects even if identically named objects already exist (which will create doubles/triples/etc)
var addSourceProp = true; // if true, a "source" property is added to the new model objects, pointing to the source file and cell ID
// Parameters
var importedViewName = "_Imported from draw.io"; // name for the view containing the newly imported objects
debug?console.log("//","Loading libraries"):null;
load(__DIR__ + "lib/utils.ajs");
load(__DIR__ + "lib/from-xml.min.js");
// the fromXML function creates a JSON object tree with the following naming conventions:
// -- the key of an XML tag is the tag name, without adornments
// -- the key of an XML attribute is prefixed with "@"
// -- the key for content (between XML tags) is "#"
var businessColor = "#ffff99";
var applicationColor = "#99ffff";
var technologyColor = "#AFFFAF";
var physicalColor = "#AFFFAF"; // Currently, this is the same as technologyColor
var motivationColor = "#CCCCFF";
var strategyColor = "#F5DEAA";
var implementationColor = "#FFE0E0";
var implementationColorL = "#E0FFE0";
var groupingColor = ""; // Not used at this time...
var locationColor = "#FFB973";
var junctionAndColor = "#000000";
var junctionOrColor = "#ffffff";
// The following tables use the minimum number of attributes needed to distinguish between
// the different draw.io drawing elements and map them to Archi.
// Draw.io format: https://jgraph.github.io/mxgraph/docs/js-api/files/model/mxCell-js.html
// The Archi types are taken from https://github.com/archimatetool/archi-scripting-plugin/wiki/jArchi-Collection
var archiElemMap = [
// tags/shape/appType/
// Archi type archiType/techType fillColor
// --------------------------- ------------------ ----------------
["application-collaboration", "collab", applicationColor],
["application-component", "comp", applicationColor],
["application-event", "event", applicationColor],
["application-function", "func", applicationColor],
["application-interaction", "interaction", applicationColor],
["application-interface", "interface", applicationColor],
["application-process", "proc", applicationColor],
["application-service", "serv", applicationColor],
["artifact", "artifact", technologyColor],
["assessment", "assess", motivationColor],
["business-actor", "actor", businessColor],
["business-collaboration", "collab", businessColor],
["business-event", "event", businessColor],
["business-function", "func", businessColor],
["business-interaction", "interaction", businessColor],
["business-interface", "interface", businessColor],
["business-object", "businessObject", businessColor],
["business-process", "proc", businessColor],
["business-role", "role", businessColor],
["business-service", "serv", businessColor],
// Not (yet) supported:
// ["canvas-model-block", "", ""],
// ["canvas-model-image", "", ""],
// ["canvas-model-sticky" "", ""],
["capability", "capability", strategyColor],
["communication-network", "commNetw", technologyColor],
["communication-network", "netw", technologyColor],
["constraint", "constraint", motivationColor],
["contract", "contract", businessColor],
["course-of-action", "course", strategyColor],
["data-object", "businessObject", applicationColor],
["deliverable", "deliverable", implementationColor],
["device", "device", technologyColor],
// No direct equivalent in draw.io:
// ["diagram-model-connection", "", ""],
// ["diagram-model-group", "", ""],
// ["diagram-model-image", "", ""],
// ["diagram-model-note", "", ""],
// ["diagram-model-reference", "", ""],
["distribution-network", "distribution", physicalColor],
["driver", "driver", motivationColor],
["equipment", "equipment", physicalColor],
["facility", "facility", physicalColor],
["gap", "gap", implementationColorL],
["goal", "goal", motivationColor],
["grouping", "folder", groupingColor],
["implementation-event", "event", implementationColor],
["junction_and", "ellipse", junctionAndColor],
["junction_or", "ellipse", junctionOrColor],
["location", "location", locationColor],
["material", "material", physicalColor],
["meaning", "cloud", motivationColor],
["node", "node", technologyColor],
["outcome", "outcome", motivationColor],
["path", "path", physicalColor],
["plateau", "plateau", implementationColorL],
["principle", "principle", motivationColor],
["product", "product", businessColor],
["representation", "representation", businessColor],
["requirement", "requirement", motivationColor],
["resource", "resource", strategyColor],
// Not (yet) supported:
// ["sketch-model-actor", "", ""],
// ["sketch-model-sticky", "", ""],
["stakeholder", "role", motivationColor],
["system-software", "sysSw", technologyColor],
["technology-collaboration", "collab", technologyColor],
["technology-event", "event", technologyColor],
["technology-function", "func", technologyColor],
["technology-interaction", "interaction", technologyColor],
["technology-interface", "interface", technologyColor],
["technology-process", "proc", technologyColor],
["technology-service", "serv", technologyColor],
["value", "ellipse", motivationColor],
["value-stream", "NOTSUPPORTED", motivationColor], // not supported by draw.io at this time...
["work-package", "rounded", implementationColor]
];
var archiRelMap = [
// Archi type startArrow endArrow endFill dashed dashPattern
// ---------------------------- ---------- -------- ------- ------ -----------
["access-relationship", "", "none", "", "1", "1"],
["access-relationship_read", "open", "", "", "1", "1"],
["access-relationship_write", "", "open", "0", "1", "1"],
["access-relationship_readwrite", "open", "open", "0", "1", "1"],
["aggregation-relationship", "", "diamondThin", "0", "", "" ],
["assignment-relationship", "oval", "block", "1", "", "" ],
["association-relationship", "", "none", "", "", "" ],
["composition-relationship", "", "diamondThin", "1", "", "" ],
["flow-relationship", "", "block", "1", "1", "6"],
["influence-relationship", "", "open", "0", "1", "6"],
["realization-relationship", "", "block", "0", "1", "" ],
["serving-relationship", "", "open", "1", "", "" ],
["specialization-relationship", "", "block", "0", "", "" ],
["triggering-relationship", "", "block", "1", "0", "" ]
]
var diagCells = [];
var currentElement = {}; // used by the matchElement filter function
var currentRelationship = {}; // used by the matchRelationship filter function
var foundElemCount = 0;
var newElemCount = 0;
var foundRelCount = 0;
var newRelCount = 0;
// Parse a style string (= series of key-value pairs separated by ";") into an object
function parseStyle (s) {
var style = {};
var stylePair = s.split(";");
for (p in stylePair) {
var kv = stylePair[p].split("=");
if (kv[0]!="") {// the last pair is usually empty (style string ends with ";")
if (kv.length>1) {
style[kv[0]] = kv[1];
} else { // some "pairs" (e.g. edgeLabel) are just keys without value: we collect those into a "tags" property
style["tags"] = (style["tags"]===undefined ? "": style["tags"]) + "#"+kv[0];
};
};
};
return style;
};
// Expand all style strings into objets
/* function expandStyles (elem) {
for (k in elem) {
if (k=="@style") {
elem[k] = parseStyle(elem[k]);
} else if (typeof elem[k]=="object") {
expandStyles(elem[k]);
};
};
}; */
// Collect all the useful "mxCell" elements from the draw.io data
function cellsFromDrawio (elem,inarr) {
var cells = [];
for (k in elem) {
if (typeof elem[k]=="object") {
if (inarr=="mxCell" || k=="mxCell") {
if (elem[k]["@style"] !== undefined) {
var newCell = {
id: elem[k]["@id"],
value: elem[k]["@value"],
style: parseStyle(elem[k]["@style"]),
parent: elem[k]["@parent"],
source: elem[k]["@source"],
target: elem[k]["@target"]
};
if (elem[k].mxGeometry !== undefined) {
newCell.x = elem[k].mxGeometry["@x"];
newCell.y = elem[k].mxGeometry["@y"];
newCell.width = elem[k].mxGeometry["@width"];
newCell.height = elem[k].mxGeometry["@height"];
};
cells.push(newCell);
};
}
cells=cells.concat(cellsFromDrawio(elem[k],elem[k].length>0?k:undefined));
}
}
return cells
};
function matchProp (p,s) {
if (p===undefined || p===null) {
return s=="";
} else {
return p.indexOf(s)>-1;
};
};
function matchElement (eprops) {
// debug?console.log("// ",eprops,currentElement.value,currentElement.style.shape,currentElement.style.appType,currentElement.style.techType,currentElement.style.fillColor):null;
return (
matchProp(currentElement.style.tags,eprops[1]) ||
matchProp(currentElement.style.shape,eprops[1]) ||
matchProp(currentElement.style.appType,eprops[1]) ||
matchProp(currentElement.style.archiType,eprops[1]) ||
matchProp(currentElement.style.techType,eprops[1])
) && matchProp(currentElement.style.fillColor,eprops[2]);
};
function matchRelationship (rprops) {
//debug?console.log("// ",rprops,currentRelationship.value,currentRelationship.style.shape,currentRelationship.style.appType,currentRelationship.style.techType,currentRelationship.style.fillColor):null;
return (
matchProp(currentRelationship.style.startArrow, rprops[1]) &&
matchProp(currentRelationship.style.endArrow, rprops[2]) &&
matchProp(currentRelationship.style.endFill, rprops[3]) &&
matchProp(currentRelationship.style.dashed, rprops[4]) &&
matchProp(currentRelationship.style.dashPattern, rprops[5])
);
};
function createArchiElements(cells) {
// Scan all cells and try to match them to Archimate elements using the archiElemMap data
// Every time we find a matching cell, try to create a corresponding object model in Archi
debug?console.log("//","Pass 1: Elements ("+cells.length+")"):null;
for (c in cells) {
currentElement = cells[c];
debug?console.log("// ",c,":",currentElement.value,currentElement.style.shape,currentElement.style.appType,currentElement.style.techType,currentElement.style.fillColor):null;
if (currentElement.style!==undefined) {
var curElemMatch = archiElemMap.filter(matchElement);
debug?console.log(superString(curElemMatch,"// ")):null;
if (curElemMatch.length > 0) {
// We found a cell that matches at least one Archimate element - now create the corresponding model object in Archi
currentElement.type = curElemMatch[0][0];
foundElemCount++;
var archiType = curElemMatch[0][0].split("_"); // junctions can be either "junction_and" or "junction_or", so we need to extract both parts
var objElem = $("."+currentElement.value).filter("element").filter(archiType[0]); // Find out whether the element already exists in the model
if (objElem.length == 0 || forceImport) {
// Element does not exist in model yet, or forced import is required --> create it
debug?console.log("// --> creating new object model:",archiType[0]+":"+currentElement.value):null;
var newElem = model.createElement(archiType[0],currentElement.value);
if (archiType[0]=="junction") {
newElem.setJunctionType(archiType[1]);
};
if (addSourceProp) {
newElem.prop("source","Imported from '"+fileName+"', source ID = "+currentElement.id);
};
currentElement.model = newElem; // reference for use when we create relationships involving this element
newElemCount++;
} else {
// element already exist: add the reference
console.log("> Info: element "+currentElement.type+":'"+currentElement.value+"' ("+currentElement.id+") already exists in the model -- not imported");
currentElement.model = objElem[0];
};
};
};
};
};
function findCells(key,val) {
return diagCells.filter(function f(c) { return c[key] == val });
};
function createArchiRelationships(cells) {
// Scan all cells and try to match them to Archimate relationships using the archiRelMap data
// Every time we find a matching cell, try to create a corresponding object model in Archi
debug?console.log("//","Pass 2: Relationships ("+cells.length+")"):null;
for (c in cells) {
currentRelationship = cells[c];
debug?console.log("// ",c,":", currentRelationship.id):null;
if (currentRelationship.style!==undefined) {
var curRelMatch = archiRelMap.filter(matchRelationship);
debug?console.log(superString(curRelMatch,"// ")):null;
if (Object.keys(curRelMatch).length > 0) {
// We found a cell that matches at least one Archimate element - now create the corresponding model object in Archi
currentRelationship.type = curRelMatch[0][0];
foundRelCount++;
var archiType = curRelMatch[0][0].split("_"); // access relationships can be either "*_read", "*_write" or "*_readwrite", so we need to extract both parts
var curSource = findCells("id",currentRelationship.source)[0];
var curTarget = findCells("id",currentRelationship.target)[0];
if (curSource!==undefined && curTarget!==undefined) {
debug?console.log("// ",curRelMatch[0][0]+":"+currentRelationship.id,"FROM",curSource.value,"TO",curTarget.value):null;
if (currentRelationship.value=="") {
// Instead of the value, attempt to find a label attached to the relationship
var label = findCells("parent",currentRelationship.id).filter(function f(c) {return c.style.tags.indexOf("#edgeLabel")>-1});
currentRelationship.value = label===undefined?"":label[0].value;
};
var objRel = $(archiType[0]).filter("."+currentRelationship.value);
if (objRel.sourceEnds("."+curSource.value).length==0 || objRel.targetEnds("."+curTarget.value)==0 || forceImport) {
// Relationship does not exist in model yet, or forced import is required --> create it
try { // trying to create an invalid relationship will raise an exception
var newRel = model.createRelationship(archiType[0],currentRelationship.value,curSource.model,curTarget.model);
if (archiType[0]=="access-relationship") {
newElem.setAccessType(archiType[1]);
};
if (addSourceProp) {
newRel.prop("source","Imported from '"+fileName+"', source ID = "+currentRelationship.id);
};
currentRelationship.model = newRel; // reference for later use
newRelCount++;
} catch(err) {
console.log("> Error: "+currentRelationship.type+":'"+currentRelationship.value+"' ("+currentRelationship.id+") from '"+curSource.value+"' to '"+curTarget.value+"' is not allowed -- not imported");
};
} else {
// Relationship may already exist (test is not perfect...)
console.log("> Info: "+currentRelationship.type+":'"+currentRelationship.value+"' ("+currentRelationship.id+") from '"+curSource.value+"' to '"+curTarget.value+"' may already exist in the model -- not imported");
};
} else {
console.log("> Error: relationship",currentRelationship.id,"is missing a",((curSource===undefined?"SOURCE and ":"")+(curTarget===undefined?"TARGET and ":"")).slice(0,-5));
};
} else {
};
};
};
};
var filePath = window.promptOpenFile({ title: "Open draw.io file", filterExtensions: ["*.drawio.xml"], fileName: "" });
if (filePath) {
console.log("> Loading draw.io file");
var fileName = filePath.replace(/^.*(\\|\/|\:)/, '');
var FileReader = Java.type("java.io.FileReader");
var Types = Java.type("java.nio.charset.StandardCharsets");
var theDrawioFile = new FileReader(filePath,Types.UTF_8);
var theDrawio ="";
var data = theDrawioFile.read();
while(data != -1) {
var theCharacter = String.fromCharCode(data);
theDrawio+=theCharacter;
data = theDrawioFile.read();
}
theDrawioFile.close();
// Convert draw.io XML data to JSON
var objDrawio = fromXML(theDrawio);
debug?console.log("//",superString(objDrawio,"// "),"\n"):null;
console.log("> Extracting shapes from draw.io data");
diagCells = cellsFromDrawio(objDrawio);
debug?console.log("//",superString(diagCells,"// "),"\n"):null;
// Model objects are created in two passes: first elements, then relationships
// This ensures that all source and target elements exist before the relationships are created
console.log("> Matching draw.io shapes to Archimate elements and creating model objects in Archi (this may take a while...)");
createArchiElements(diagCells);
console.log("> Matching draw.io connectors to Archimate relationships and creating model objects in Archi (this may take a while...)");
createArchiRelationships(diagCells);
console.log("\n> Elements found:",foundElemCount,"\n> Elements created:",newElemCount);
console.log("> Relationships found:",foundRelCount,"\n> Relationships created:",newRelCount);
// Create a new view and populate it with all the newly created objects
var importedView = model.createArchimateView(importedViewName);
console.log("\n> Creating view with newly imported objects: ",importedView);
for (c in diagCells) {
var curCell = diagCells[c];
if (curCell.type !== undefined) {
debug?console.log("// Adding "+curCell.type+":'"+curCell.value+"' to view"):null;
if (curCell.type.indexOf("relationship")==-1) {
// this is an element
var newElem = importedView.add(curCell.model,curCell.x,curCell.y,curCell.width,curCell.height);
curCell.diag=newElem;
} else {
// this is a relationship
// assumption: source and target elements appear in XML before any relationship that reference them
var newRel = importedView.add(curCell.model,findCells("id",curCell.source)[0].diag,findCells("id",curCell.target)[0].diag);
};
};
};
} else {
console.log("> Cancelled");
}
@pararsma

pararsma commented Dec 5, 2022

Copy link
Copy Markdown

Where can I find these 2 library ???

load(DIR + "lib/utils.ajs");
load(DIR + "lib/from-xml.min.js");

Thank in advance.

@christhearchitect

christhearchitect commented Dec 5, 2022 via email

Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment