Update: 08/06/2022
I’ve updated the script as hosted on GitHub Gists to now support Specializations, as well as introduce the funcationality to create a new view including the new/matched Elements from the import.
A couple of weeks back, I posted my jArchi script for exporting a view from Archi into CSV format to load into your spreadsheet application of choice. This week, I’ll go through the other side of the equation, once you’ve dumped your view to Excel, made your changes, how you can go about getting those amendments back into Archi.
Quick warning, this shouldn’t hose your model, but please backup your model first, just in case!
Here is the Import from CSV jArchi script from GitHub Gists.

This script requires PapaParse. Download the papaparse package, and extract the papaparse.min.js script into a “lib” folder in the same location as the Import from CSV.ajs script in your jArchi script folder.
This script will:
- Prompt for a CSV file (in the format generated by the Export to CSV script)
- Load the CSV and parse using PapaParse
- For each row in the CSV
- Attempt to match the Archi Concept in the row using the UID in the CSV.
- If there is no UID, attempt to match the Archi Concept using the Object Name
- If there are no matches, or more that one match was found, create a new Archi Concept using the common properties, Name, Documentation and Type, else update the matched concept with the matched details.
- Loop through all other columns in the CSV.
- If the row has a value in this column, update the property on the Archi Concept with the value.
- If the row doesn’t have a value in the column, remove the property from the Archi Concept
| /* | |
| * 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"); | |
| } |
Share this:
- Click to share on X (Opens in new window) X
- Click to share on Facebook (Opens in new window) Facebook
- Click to print (Opens in new window) Print
- Click to share on LinkedIn (Opens in new window) LinkedIn
- Click to share on Pinterest (Opens in new window) Pinterest
- More
- Click to share on Reddit (Opens in new window) Reddit
- Click to share on Tumblr (Opens in new window) Tumblr
- Click to share on Pocket (Opens in new window) Pocket
- Click to share on Telegram (Opens in new window) Telegram
- Click to share on WhatsApp (Opens in new window) WhatsApp
- Click to email a link to a friend (Opens in new window) Email

i have two csv files. 1 contains the list of elements, the other the a table of 2 elements and the name of a relationship. all the elements are data objects. who can I get this info into archi
LikeLike