Archi, jArchi (CSV Import/Export) and Specialisations (oh my)

  1. Specializations
  2. Import/Export jArchi Scripts
    1. Export to CSV
    2. Import from CSV

Specializations

Archi 4.9 introduced support for the concept of “ArchiMate Specializations“, this is a feature that the ArchiMate “language” has long supported, but had been manually applied in Archi before the new release. You can see my usage of Specialization in my post on the Technology Layer.

Here, there are three Specializations of the Artifact Element:

  • Software
  • Operating System
  • Database

From an Archi point of view, this was managed via naming the Element with the “<<>>” notation:

Specialization in Element name

However, with the new support for Specializations in Archi, we can now achieve this with a Specialization of “Software”:

Specialization held in specific field

This makes our naming clearer, and allows us to filter our model based on the Specialization when looking for those types:

You can tell from the list of Specializations in my model, that I’m a fan…

Archi 4.7 introduced the concept of “Labels” to the tool, this allows us to “override” the name of an element where appropriate. By applying a label expression to the Object on our View, we can now highlight the Specialization in use.

Label Expression = ${name}${if:${specialization}: «${specialization}»}

We can take this a step further, by using the Archi support of Specialization images. By applying an image to each of our Specializations, they will show in the top right of the Element when viewed in the diagram, this can be seen in the example below, showing the “Folder”, “CSV”, “Script” and “Software” Specializations moving from the “name” of the element, to Specialization with an image provided (icons from IconMonstr)

Import/Export jArchi Scripts

So far so good… but I also wrote a number of scripts using jArchi to import and export views from my model to/from CSV format. These scripts have now been updated to support the Specialization concept.

Export to CSV

The “Export to CSV” script hosted on Github Gists has been updated to also output the Specialization of an Element to a column in the file.

/*
* Export To CSV
*
* Requires jArchi – https://www.archimatetool.com/blog/2018/07/02/jarchi/
* Requires PapaParse – https://www.papaparse.com/
* Works with Import from CSV script – https://gist.github.com/smileham/1e57a5946235e780dee5a824f664aa3d
*
* Version 1: Export to CSV
* Version 1.1: Avoid duplicate concepts exported from diagram
* Version 1.2: Fix missing properties
* Version 2: Updated to export Relationships to additional CSV
* Version 2.1: Added error check for View.
* Version 2.2: Added support for Specialization
*
* (c) 2018 Steven Mileham
*
*/
var debug = false;
// Show output in the console
console.show();
console.clear();
console.log("> Starting CSV Export");
load(__DIR__ + "lib/papaparse.min.js");
var propertiesList = [];
var conceptHashMap = [];
// Set up some conceptHeaders
var conceptHeaders = [
"Name",
"Documentation",
"UID",
"Type",
"Specialization"
];
var relationshipHeaders = [
"Relationship ID",
"From Name",
"From Type",
"Relationship Type",
"Specialization",
"To Name",
"To Type",
"Relationship Name",
"Relationship Documentation"
];
var typeMappings = {
"access-relationship":"Accesses",
"composition-relationship":"Comprises",
"flow-relationship":"Flows to",
"realization-relationship":"Realises",
"assignment-relationship":"Assigned to",
"serving-relationship":"Serves/Used By",
"association-relationship":"Associated to",
"aggregation-relationship":"Aggregates"
}
var current_row = 1;
var theData = new Array();
var theRelationshipData = new Array();
var theView = $(selection).filter("archimate-diagram-model").first();
debug? console.log(theView):true;
if (theView) {
// Loop through all elements and set cells to elememt info
$(theView).find().not("relationship").each(function(e) {
var theConcept = e.concept;
try {
if (e.name!="") {
if (!conceptHashMap[theConcept.id]) {
conceptHashMap[theConcept.id]=true;
var theProperties = theConcept.prop();
for (var i=0; i<theProperties.length; i++){
var found = false;
for (var j=0; j<propertiesList.length; j++) {
if (propertiesList[j]==theProperties[i]) {
found=true;
}
}
if (!found) {
propertiesList.push(theProperties[i]);
conceptHeaders.push(theProperties[i]);
}
}
var theObject = new Object;
theObject["Name"]=theConcept.name;
theObject["Documentation"]=theConcept.documentation;
theObject["UID"]=theConcept.id;
theObject["Type"]=theConcept.type;
theObject["Specialization"]=theConcept.specialization;
for (var i=0; i<propertiesList.length; i++){
if (theConcept.prop(propertiesList[i])) {
theObject[propertiesList[i]]=""+theConcept.prop(propertiesList[i]);
}
}
debug? console.log("> theObject"):true;
debug? console.log(theObject):true;
theData.push(theObject);
// Get Relationships
$(e).outRels().each(function (r) {
var theRelationshipRow = new Object;
theRelationshipRow["Relationship ID"]=r.id;
theRelationshipRow["From Name"]=r.source.name;
theRelationshipRow["From Type"]=r.source.type;
theRelationshipRow["Relationship Type"]=!typeMappings[r.type]?r.type:typeMappings[r.type];
theRelationshipRow["Specialization"]=r.specialization;
theRelationshipRow["To Name"]=r.target.name;
theRelationshipRow["To Type"]=r.target.type;
theRelationshipRow["Relationship Name"]=r.name;
theRelationshipRow["Relationship Documentation"]=r.documentation;
theRelationshipData.push(theRelationshipRow);
});
current_row++;
}
else {
console.log("Duplicate Concept: ",theConcept.name);
}
}
}
catch (error) {
console.log("> Ignoring: "+e);
}
});
// Open a dialog to let the user choose where to save the generated file
var defaultFileName = model.name ? model.name + "-" + theView.name + ".csv" : "Exported Model.csv"; // Default file name
var exportFile = window.promptSaveFile({ title: "Export to CSV", filterExtensions: [ "*.csv" ], fileName: defaultFileName } );
debug? console.log("> conceptHeaders"+conceptHeaders):true;
debug? console.log("> TheData"+theData.length):true;
debug? console.log(theData):true;
debug? console.log(theRelationshipData):true;
var theCSV = Papa.unparse({fields:conceptHeaders, data:theData});
var theRelationshipsCSV = Papa.unparse({fields:relationshipHeaders, data:theRelationshipData});
if(exportFile != null) {
debug? console.log("> TheCSV"):true;
debug? console.log(theCSV):true;
$.fs.writeFile(exportFile, theCSV);
$.fs.writeFile(exportFile.substring(0,exportFile.length-4) +"-relationship.csv", theRelationshipsCSV);
console.log("> Export done");
}
else {
console.log("> Export cancelled");
}
}
else {
console.log("> Please Select a View");
}

Import from CSV

The “Import from CSV” script hosted on Github Gists has been updated read the Specialization column, it will then match to an existing Specialization for the given Element type (if found), and if not, create that Specialization in the model.

The script has also been updated to add each Element imported to a new View in the root of the model. Each Element will be added to a Diagram Group to highlight whether it was matched via UID, name, or whether it was created as a new Element.

/*
* Import from CSV
*
* Requires jArchi – https://www.archimatetool.com/blog/2018/07/02/jarchi/
* Requires PapaParse – https://www.papaparse.com/
* Works with Export to CSV Script – https://gist.github.com/smileham/15c445b17a92bd6f5dc1508e573bcd8a
*
* Version 1: Import from CSV
* Version 1.1: Force character encoding to use UTF-8
* Version 2: Support for Specialization and creates "CSVImport-timestamp" view
* Version 2.1: Added formatting for CSVImport view, including groups to highlight match/new components.
*
* (c) 2018 Steven Mileham
*
*/
var debug = true;
console.show();
console.clear();
console.log("> Import CSV");
load(__DIR__ + "lib/papaparse.min.js");
console.log("> Loaded Papa Parse");
var filePath = window.promptOpenFile({ title: "Open CSV", filterExtensions: ["*.CSV"], fileName: "default.csv" });
if (filePath) {
var FileReader = Java.type("java.io.FileReader");
var Types = Java.type("java.nio.charset.StandardCharsets");
var theCSVFile = new FileReader(filePath,Types.UTF_8);
var theCSV ="";
var data = theCSVFile.read();
console.log("> Please Wait…");
while(data != -1) {
var theCharacter = String.fromCharCode(data);
theCSV+=theCharacter;
data = theCSVFile.read();
}
theCSVFile.close();
console.log("> File Loaded");
theDataFile = Papa.parse(theCSV);
theData = theDataFile.data;
theDataHeaders = theData[0];
var newGroupStart = 48;
var nameGroupStart = 180;
var uidGroupStart = 312;
var maxPerRow = 10;
var newGroupCount=0;
var nameGroupCount=0;
var uidGroupCount=0;
var newGroupRow=1;
var nameGroupRow=1;
var uidGroupRow=1;
var theDate = new Date();
var theView = model.createArchimateView("CSVImport-"+theDate.toString());
var templateObject = theView.createObject("note", 0, 0, -1, -1);
var templateHeight = templateObject.bounds.height;
var templateWidth = templateObject.bounds.width
templateObject.delete();
var titleNote = theView.createObject("note", 12, 12, maxPerRow*templateWidth, 24);
titleNote.text = "Import from:"+filePath+" on "+theDate.toString();
var newGroup = theView.createObject("diagram-model-group", 12, newGroupStart, maxPerRow*templateWidth, templateWidth);
newGroup.name = "New Elements";
var nameGroup = theView.createObject("diagram-model-group", 12, nameGroupStart, maxPerRow*templateWidth, templateWidth);
nameGroup.name = "Matched by Name";
var uidGroup = theView.createObject("diagram-model-group", 12, uidGroupStart, maxPerRow*templateWidth, templateWidth);
uidGroup.name = "Matched by UID";
var commonProperties = ["UID","Name", "Documentation","Type","Specialization"];
for (var i=1; i<theData.length; i++) {
var matchType = 3;
var theConcept = null;
var theObject = [];
for (var j=0; j<theDataHeaders.length; j++) {
theObject[theDataHeaders[j]]=theData[i][j];
}
theConcept = $("#"+theObject.UID).first();
if (!theConcept) {
debug? console.log("> Missing UID, checking Name"):true;
theConcept = $("."+theObject.Name).first();
matchType=2;
if (!theConcept || theConcept.length>1) {
debug? console.log("> Creating Concept"):true;
theConcept = model.createElement(theObject.Type,theObject.Name);
matchType=1;
}
}
debug? console.log(theConcept):true;
theConcept.name=theObject.Name;
theConcept.documentation=theObject.Documentation;
theConcept.type=theObject.Type;
if (theObject.Specialization!="")
{
theSpecializations = model.specializations;
for (var j=0; j<theSpecializations.length; j++) {
debug? console.log("> Checking Spec " + theSpecializations[j]):true;
if (theSpecializations[j].name==theObject.Specialization && theSpecializations[j].type == theObject.Type)
{
debug? console.log("> Setting Spec " + theConcept.specialization):true;
theConcept.specialization=theObject.Specialization;
}
}
if (theConcept.specialization==null)
{
debug? console.log("> Creating Spec " + theObject.Specialization):true;
model.createSpecialization(theObject.Specialization, theObject.Type);
theConcept.specialization=theObject.Specialization;
}
}
for (var j=0; j<theDataHeaders.length; j++) {
switch (theDataHeaders[j]) {
case "UID":
case "Name":
case "Documentation":
case "Type":
case "Specialization":
break;
default:
if (theObject[theDataHeaders[j]]) {
theConcept.prop(theDataHeaders[j],theObject[theDataHeaders[j]]);
}
else {
theConcept.removeProp(theDataHeaders[j]);
}
}
}
newGroup.bounds = {x:newGroup.bounds.x, y:newGroup.bounds.y, width:maxPerRow*templateWidth,height:(templateHeight+12)*newGroupRow+24};
nameGroup.bounds = {x:nameGroup.bounds.x, y:newGroup.bounds.y+newGroup.bounds.height+12, width:maxPerRow*templateWidth,height:(templateHeight+12)*nameGroupRow+24};
uidGroup.bounds = {x:uidGroup.bounds.x, y:nameGroup.bounds.y+nameGroup.bounds.height+12, width:maxPerRow*templateWidth,height:(templateHeight+12)*uidGroupRow+24};
switch (matchType) {
case 3:
uidGroupCount++;
if (uidGroupCount>=maxPerRow)
{
uidGroupRow++;
uidGroupCount=1;
}
theGroupStart = uidGroup.bounds.y;
theGroupCount = uidGroupCount;
theGroupRow = uidGroupRow;
break;
case 2:
debug? console.log("> Name Group Width " +nameGroup.bounds.width):true;
nameGroupCount++;
if (nameGroupCount>=maxPerRow)
{
nameGroupRow++;
nameGroupCount=1;
}
theGroupStart = nameGroup.bounds.y;
theGroupCount = nameGroupCount;
theGroupRow = nameGroupRow;
debug? console.log("> NEW Name Group Width " +nameGroup.bounds.width):true;
break;
case 1:
newGroupCount++;
if (newGroupCount>=maxPerRow)
{
newGroupRow++;
newGroupCount=1;
}
theGroupStart = newGroup.bounds.y;
theGroupCount = newGroupCount;
theGroupRow = newGroupRow;
break;
}
var theViewObject = theView.add(theConcept, (theGroupCount*templateWidth)-(templateWidth-12)+(theGroupCount*12), theGroupStart+12+((theGroupRow-1)*templateHeight)+(theGroupRow*12), -1, -1, true);
}
console.log("> Parsing Complete")
}
else {
console.log("> Cancelled");
}

2 comments

Leave a Reply to JArchi Scripting: Import From CSV – General Geekery Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.