Skip to content

Instantly share code, notes, and snippets.

@smileham
Last active June 6, 2025 10:59
Show Gist options
  • Save smileham/1e57a5946235e780dee5a824f664aa3d to your computer and use it in GitHub Desktop.
Save smileham/1e57a5946235e780dee5a824f664aa3d to your computer and use it in GitHub Desktop.
Import from CSV #jarchi
/*
* 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: Fix for "empty" rows
* Version 2.2: Fix for BOM
* Version 2.3: Enhanced error detection
*
* (c) 2025 Steven Mileham
*
*/
function parseCSV(filePath) {
const Papa = require(__DIR__ + "lib/papaparse.min.js");
console.log("> Loaded Papa Parse");
let FileReader = Java.type("java.io.FileReader");
const Types = Java.type("java.nio.charset.StandardCharsets");
let theCSVFile = new FileReader(filePath,Types.UTF_8);
let theCSV ="";
let data = theCSVFile.read();
console.log("> Please Wait...");
while(data != -1) {
if (data!=65279) {
let theCharacter = String.fromCharCode(data);
theCSV+=theCharacter;
}
data = theCSVFile.read();
}
theCSVFile.close();
console.log("> File Loaded");
return Papa.parse(theCSV);
}
var debug = false;
console.show();
console.clear();
console.log("> Import CSV");
const filePath = window.promptOpenFile({ title: "Open CSV", filterExtensions: ["*.CSV"], fileName: "default.csv" });
if (filePath) {
theDataFile = parseCSV(filePath);
theData = theDataFile.data;
if (theDataFile.errors.length > 0) {
let errorMsg = "Errors found in CSV file:\n";
theDataFile.errors.forEach(function(error) {
errorMsg += `- Row ${error.row}: ${error.message}\n`;
});
console.error(errorMsg);
}
else {
theDataHeaders = theData[0];
const commonProperties = ["UID","Name", "Documentation","Type","Specialization"];
let missingColumns = commonProperties.filter(col => !theDataHeaders.includes(col));
if (missingColumns.length > 0) {
let errorMsg = `The CSV is missing the following required columns: ${missingColumns.join(", ")}`;
console.error(errorMsg);
}
else {
let newGroupStart = 48;
let nameGroupStart = 180;
let uidGroupStart = 312;
const maxPerRow = 10;
let newGroupCount=0;
let nameGroupCount=0;
let uidGroupCount=0;
let newGroupRow=1;
let nameGroupRow=1;
let uidGroupRow=1;
const theDate = new Date();
let theView = model.createArchimateView("CSVImport-"+theDate.toString());
let templateObject = theView.createObject("note", 0, 0, -1, -1);
let templateHeight = templateObject.bounds.height;
let templateWidth = templateObject.bounds.width
templateObject.delete();
let titleNote = theView.createObject("note", 12, 12, maxPerRow*templateWidth, 24);
titleNote.text = "Import from:"+filePath+" on "+theDate.toString();
let newGroup = theView.createObject("diagram-model-group", 12, newGroupStart, maxPerRow*templateWidth, templateWidth);
newGroup.name = "New Elements";
let nameGroup = theView.createObject("diagram-model-group", 12, nameGroupStart, maxPerRow*templateWidth, templateWidth);
nameGroup.name = "Matched by Name";
let uidGroup = theView.createObject("diagram-model-group", 12, uidGroupStart, maxPerRow*templateWidth, templateWidth);
uidGroup.name = "Matched by UID";
const commonProperties = ["UID","Name", "Documentation","Type","Specialization"];
for (let i=1; i<theData.length; i++) {
let matchType = 3;
let theConcept = null;
let theObject = [];
for (let j=0; j<theDataHeaders.length; j++) {
theObject[theDataHeaders[j]]=theData[i][j];
}
debug? console.log(theObject):true;
if (theObject.Name!=null && theObject.Name.trim()!="" && theObject.Name.trim()!="null") {
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 (let 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 (let 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;
}
let 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");
}
@rich-biker
Copy link

Hi Smileham. Thanks for this excellent script. I'm just wondering why you declared the commonProperties list as you don't reference it anywhere? Am I missing something?

@smileham
Copy link
Author

(Better late than never)
You're right, I think at some point I was looping through these to "ignore" them when writing the properties out, and then ended up moving to a case statement.

@rmurray68
Copy link

When doing an import from a CSV file, I keep getting Missing UID message. It then creates a random one in Archi. I am looking for a way to create pre-defined UID during initial import for consistent update and sync from other sources. Can you share a sample CSV that is formatted correctly?

@smileham
Copy link
Author

smileham commented Jun 6, 2020

Check out https://gist.github.com/smileham/1932ae02fc481e339629dff4e3fbd7f0 for example CSVs, also you can run the Export to CSV script on a view to generate your own https://gist.github.com/smileham/15c445b17a92bd6f5dc1508e573bcd8a

@rmurray68
Copy link

Thank you. I think I figured it out. The "Missing UID" is because it does not already exist in Archi. And from reading on the scripting language, I do not see any arguments on the Model Create Element to allow for specifying a UID. It appears you would need to do an initial load of the elements using the import feature in Archi first and the CSV format it needs. That does pre-create them. Then use them with your script to populate the properties. Thanks for your script. I think it will help me to structure things.

@degjerken
Copy link

Hi Smileham,
Tested out your script - very nice functionality :-). Thanks!
Have however found a small issue with the importing as it turns out to not being able to handle characters such as 'æ', 'ø', 'å', etc. Do you know of any way to address this issue? The export function treats these letters just fine. Has it perhaps something to do with the papaparser?

@smileham
Copy link
Author

Well spotted, I've updated it to load the file using UTF-8, try it now! @degjerken

@degjerken
Copy link

Thanks a lot, @smileham! This update made my day ;-)

@JoCriSem
Copy link

Hi Smileham, trying your export and import scripts and for some reason the relationships are not made while importing.
To test I just exported my view and reimported it in an empty model. All elements and their properties are exported/imported well, just the relationships not.

@Phillipus
Copy link

jArchi 1.6 enables support for CommonJS modules. To get this script to work replace the line:

load(__DIR__ + "lib/papaparse.min.js");

with:

const Papa = require(__DIR__ + "lib/papaparse.min.js");

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