Last active
December 5, 2022 19:19
-
-
Save christhearchitect/ee35ab560d2d42773a5a982b5e240452 to your computer and use it in GitHub Desktop.
#jArchi
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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"); | |
} |
Author
christhearchitect
commented
Dec 5, 2022
via email
The second lib is from here: https://github.com/kawanet/from-xml
The first one is my own collection of small utilities for jArchi. Here is
the only bit that will be useful here (just copy it into a file named
utils.ajs):
// Convert an object to a multi-line string in tree format (one value per
line)
// Usage: superString(object [,lead])
// object: the object to be stringified
// lead: a string that will be prepended to every lign
// NB: the "pref" parameter is for internal use only -- do not
use
function superString(obj,lead,pref) {
if (lead === undefined) {var lead = ""};
if (pref === undefined) {var pref = ""};
var str = lead+pref;
switch (obj) {
case null:
str += "(null)";
break;
case undefined:
str += "(undefined)";
break
default:
switch (typeof obj) {
case "object":
str += "{\n";
for (x in obj) {
str += superString(obj[x],lead+"
".repeat(pref.length+1),x+": ") + "\n";
};
str += lead+" ".repeat(pref.length)+"}";
break;
case "string":
str += '"'+obj+'"';
break;
case "function":
str += '<'+obj+'>';
break;
default:
str += obj;
};
};
return str;
};
…On Mon, Dec 5, 2022 at 7:49 PM pararsma ***@***.***> wrote:
***@***.**** commented on this gist.
------------------------------
Where can I find these 2 library ???
load(*DIR* + "lib/utils.ajs");
load(*DIR* + "lib/from-xml.min.js");
—
Reply to this email directly, view it on GitHub
<https://gist.github.com/ee35ab560d2d42773a5a982b5e240452#gistcomment-4392485>
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AYADX2K5MXYBJYNIOORDZWLWLY2KXBFKMF2HI4TJMJ2XIZLTSKBKK5TBNR2WLJDHNFZXJJDOMFWWLK3UNBZGKYLEL52HS4DFQKSXMYLMOVS2I5DSOVS2I3TBNVS3W5DIOJSWCZC7OBQXE5DJMNUXAYLOORPWCY3UNF3GS5DZVRZXKYTKMVRXIX3UPFYGLK2HNFZXIQ3PNVWWK3TUUZ2G64DJMNZZDAVEOR4XAZNEM5UXG5FFOZQWY5LFVEYTCNJTG44TCOJRU52HE2LHM5SXFJTDOJSWC5DF>
.
You are receiving this email because you authored the thread.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>
.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment