Created
January 7, 2026 14:19
-
-
Save romualdrichard/4bf78da96e3c6a8ec47a49316012309b to your computer and use it in GitHub Desktop.
#jarchi create new model from views or directory of a model
This file contains hidden or 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
| /* | |
| * jArchi Script: Export Selected Views with Tree Hierarchy | |
| * | |
| * This script displays a dialog with the complete folder and view hierarchy. | |
| * You can check entire folders or individual views. | |
| * | |
| * Process: | |
| * 1. Display tree with checkboxes | |
| * 2. Save the current model with a new name (Save As) | |
| * 3. Delete non-selected views | |
| * 4. Delete concepts (elements + relationships) without visual representation | |
| * 5. Delete empty view folders | |
| * 6. Save and reload original | |
| * | |
| * IMPORTANT: The original model remains INTACT | |
| */ | |
| console.clear(); | |
| console.show(); | |
| // Import necessary Java classes | |
| var Display = Java.type("org.eclipse.swt.widgets.Display"); | |
| var Shell = Java.type("org.eclipse.swt.widgets.Shell"); | |
| var Tree = Java.type("org.eclipse.swt.widgets.Tree"); | |
| var TreeItem = Java.type("org.eclipse.swt.widgets.TreeItem"); | |
| var Button = Java.type("org.eclipse.swt.widgets.Button"); | |
| var Label = Java.type("org.eclipse.swt.widgets.Label"); | |
| var Composite = Java.type("org.eclipse.swt.widgets.Composite"); | |
| var GridLayout = Java.type("org.eclipse.swt.layout.GridLayout"); | |
| var GridData = Java.type("org.eclipse.swt.layout.GridData"); | |
| var SWT = Java.type("org.eclipse.swt.SWT"); | |
| var SelectionAdapter = Java.type("org.eclipse.swt.events.SelectionAdapter"); | |
| // Global variables | |
| var selectedViews = []; | |
| var dialogResult = false; | |
| var tree; | |
| // Function to build the tree recursively | |
| function buildTree(parentItem, folder) { | |
| // Collect subfolders and sort them by name | |
| var subfolders = []; | |
| $(folder).children("folder").each(function(subfolder) { | |
| subfolders.push(subfolder); | |
| }); | |
| // Sort folders alphabetically | |
| subfolders.sort(function(a, b) { | |
| return a.name.localeCompare(b.name); | |
| }); | |
| // Add sorted subfolders | |
| subfolders.forEach(function(subfolder) { | |
| var folderItem; | |
| if (parentItem) { | |
| folderItem = new TreeItem(parentItem, SWT.NONE); | |
| } else { | |
| folderItem = new TreeItem(tree, SWT.NONE); | |
| } | |
| folderItem.setText("📁 " + subfolder.name); | |
| folderItem.setData("type", "folder"); | |
| folderItem.setData("object", subfolder); | |
| // Recursion for subfolders | |
| buildTree(folderItem, subfolder); | |
| }); | |
| // Collect views and sort them by name | |
| var views = []; | |
| $(folder).children("view").not("folder").each(function(view) { | |
| views.push(view); | |
| }); | |
| // Sort views alphabetically | |
| views.sort(function(a, b) { | |
| return a.name.localeCompare(b.name); | |
| }); | |
| // Add sorted views | |
| views.forEach(function(view) { | |
| var viewItem; | |
| if (parentItem) { | |
| viewItem = new TreeItem(parentItem, SWT.NONE); | |
| } else { | |
| viewItem = new TreeItem(tree, SWT.NONE); | |
| } | |
| viewItem.setText("📄 " + view.name); | |
| viewItem.setData("type", "view"); | |
| viewItem.setData("object", view); | |
| }); | |
| } | |
| // Function to collect all checked views | |
| function collectCheckedViews(items, checkedViews) { | |
| for (var i = 0; i < items.length; i++) { | |
| var item = items[i]; | |
| if (item.getChecked()) { | |
| var type = item.getData("type"); | |
| var obj = item.getData("object"); | |
| var isGrayed = item.getGrayed(); | |
| console.log(" Checked item: " + item.getText() + " (type: " + type + ", grayed: " + isGrayed + ")"); | |
| if (type === "view") { | |
| // A view is checked, always add it | |
| checkedViews.push(obj); | |
| console.log(" → View added: " + obj.name + " (ID: " + obj.id + ")"); | |
| } else if (type === "folder" && !isGrayed) { | |
| // A folder is checked AND NOT grayed = all its views are selected | |
| console.log(" → Folder fully checked, collecting views..."); | |
| collectViewsFromFolder(obj, checkedViews); | |
| } else if (type === "folder" && isGrayed) { | |
| // A grayed folder = only some children checked | |
| // We need to go down the tree to find which ones | |
| console.log(" → Folder partially checked (grayed), descending to children..."); | |
| // Don't collect all views, continue recursion | |
| } | |
| } | |
| // Always traverse children to find individually checked views | |
| if (item.getItemCount() > 0) { | |
| collectCheckedViews(item.getItems(), checkedViews); | |
| } | |
| } | |
| } | |
| // Function to collect all views from a folder | |
| function collectViewsFromFolder(folder, viewsList) { | |
| console.log(" Collecting from folder: " + folder.name); | |
| var directViews = $(folder).children("view").not("folder"); | |
| console.log(" → " + directViews.size() + " direct views"); | |
| directViews.each(function(view) { | |
| viewsList.push(view); | |
| }); | |
| var subfolders = $(folder).children("folder"); | |
| console.log(" → " + subfolders.size() + " subfolders"); | |
| subfolders.each(function(subfolder) { | |
| collectViewsFromFolder(subfolder, viewsList); | |
| }); | |
| } | |
| // Function to update checkbox state | |
| function updateCheckState(item, checked) { | |
| item.setChecked(checked); | |
| item.setGrayed(false); | |
| var children = item.getItems(); | |
| for (var i = 0; i < children.length; i++) { | |
| updateCheckState(children[i], checked); | |
| } | |
| var parent = item.getParentItem(); | |
| if (parent) { | |
| updateParentCheckState(parent); | |
| } | |
| } | |
| // Function to update parent state | |
| function updateParentCheckState(parent) { | |
| var children = parent.getItems(); | |
| var checkedCount = 0; | |
| var grayedCount = 0; | |
| for (var i = 0; i < children.length; i++) { | |
| if (children[i].getChecked()) checkedCount++; | |
| if (children[i].getGrayed()) grayedCount++; | |
| } | |
| if (checkedCount === 0 && grayedCount === 0) { | |
| parent.setChecked(false); | |
| parent.setGrayed(false); | |
| } else if (checkedCount === children.length) { | |
| parent.setChecked(true); | |
| parent.setGrayed(false); | |
| } else { | |
| parent.setChecked(true); | |
| parent.setGrayed(true); | |
| } | |
| var grandParent = parent.getParentItem(); | |
| if (grandParent) { | |
| updateParentCheckState(grandParent); | |
| } | |
| } | |
| // Function to create the selection dialog | |
| function createSelectionDialog() { | |
| var display = Display.getCurrent() || Display.getDefault(); | |
| var shell = new Shell(display, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.RESIZE); | |
| shell.setText("Select Views to Export"); | |
| shell.setSize(700, 500); | |
| var layout = new GridLayout(1, false); | |
| layout.marginWidth = 10; | |
| layout.marginHeight = 10; | |
| shell.setLayout(layout); | |
| var label = new Label(shell, SWT.NONE); | |
| label.setText("Select views to export (check folders or individual views):"); | |
| var labelData = new GridData(SWT.FILL, SWT.CENTER, true, false); | |
| label.setLayoutData(labelData); | |
| tree = new Tree(shell, SWT.CHECK | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); | |
| var treeData = new GridData(SWT.FILL, SWT.FILL, true, true); | |
| tree.setLayoutData(treeData); | |
| // Find the Views folder | |
| var viewsFolders = $("folder").filter(function(f) { return f.name === "Views"; }); | |
| if (viewsFolders.size() > 0) { | |
| buildTree(null, viewsFolders.first()); | |
| } else { | |
| // Add all views at root level | |
| $("view").not("folder").each(function(view) { | |
| var viewItem = new TreeItem(tree, SWT.NONE); | |
| viewItem.setText("📄 " + view.name); | |
| viewItem.setData("type", "view"); | |
| viewItem.setData("object", view); | |
| }); | |
| } | |
| tree.addListener(SWT.Selection, function(event) { | |
| if (event.detail === SWT.CHECK) { | |
| var item = event.item; | |
| updateCheckState(item, item.getChecked()); | |
| } | |
| }); | |
| var buttonComposite = new Composite(shell, SWT.NONE); | |
| var buttonLayout = new GridLayout(5, false); | |
| buttonComposite.setLayout(buttonLayout); | |
| var buttonCompData = new GridData(SWT.FILL, SWT.CENTER, true, false); | |
| buttonComposite.setLayoutData(buttonCompData); | |
| var expandButton = new Button(buttonComposite, SWT.PUSH); | |
| expandButton.setText("Expand All"); | |
| expandButton.addSelectionListener(new SelectionAdapter() { | |
| widgetSelected: function(e) { | |
| expandAll(tree.getItems(), true); | |
| } | |
| }); | |
| var collapseButton = new Button(buttonComposite, SWT.PUSH); | |
| collapseButton.setText("Collapse All"); | |
| collapseButton.addSelectionListener(new SelectionAdapter() { | |
| widgetSelected: function(e) { | |
| expandAll(tree.getItems(), false); | |
| } | |
| }); | |
| var selectAllButton = new Button(buttonComposite, SWT.PUSH); | |
| selectAllButton.setText("Select All"); | |
| selectAllButton.addSelectionListener(new SelectionAdapter() { | |
| widgetSelected: function(e) { | |
| var items = tree.getItems(); | |
| for (var i = 0; i < items.length; i++) { | |
| updateCheckState(items[i], true); | |
| } | |
| } | |
| }); | |
| var spacer = new Label(buttonComposite, SWT.NONE); | |
| var spacerData = new GridData(SWT.FILL, SWT.CENTER, true, false); | |
| spacer.setLayoutData(spacerData); | |
| var okButton = new Button(buttonComposite, SWT.PUSH); | |
| okButton.setText("Export"); | |
| var okButtonData = new GridData(SWT.RIGHT, SWT.CENTER, false, false); | |
| okButtonData.widthHint = 100; | |
| okButton.setLayoutData(okButtonData); | |
| okButton.addSelectionListener(new SelectionAdapter() { | |
| widgetSelected: function(e) { | |
| selectedViews = []; | |
| collectCheckedViews(tree.getItems(), selectedViews); | |
| if (selectedViews.length === 0) { | |
| window.alert("No views selected.\n\nPlease check at least one view or folder."); | |
| return; | |
| } | |
| dialogResult = true; | |
| shell.close(); | |
| } | |
| }); | |
| var cancelButton = new Button(buttonComposite, SWT.PUSH); | |
| cancelButton.setText("Cancel"); | |
| var cancelButtonData = new GridData(SWT.RIGHT, SWT.CENTER, false, false); | |
| cancelButtonData.widthHint = 100; | |
| cancelButton.setLayoutData(cancelButtonData); | |
| cancelButton.addSelectionListener(new SelectionAdapter() { | |
| widgetSelected: function(e) { | |
| dialogResult = false; | |
| shell.close(); | |
| } | |
| }); | |
| shell.setDefaultButton(okButton); | |
| // Expand first level | |
| var items = tree.getItems(); | |
| for (var i = 0; i < items.length; i++) { | |
| items[i].setExpanded(true); | |
| } | |
| shell.open(); | |
| while (!shell.isDisposed()) { | |
| if (!display.readAndDispatch()) { | |
| display.sleep(); | |
| } | |
| } | |
| return dialogResult; | |
| } | |
| // Function to expand/collapse all items | |
| function expandAll(items, expand) { | |
| for (var i = 0; i < items.length; i++) { | |
| items[i].setExpanded(expand); | |
| if (items[i].getItemCount() > 0) { | |
| expandAll(items[i].getItems(), expand); | |
| } | |
| } | |
| } | |
| // Function to recursively delete empty folders | |
| function deleteEmptyFolders(folder) { | |
| var children = $(folder).children(); | |
| var hasContent = false; | |
| // First, recursively process subfolders | |
| $(folder).children("folder").each(function(subfolder) { | |
| if (deleteEmptyFolders(subfolder)) { | |
| hasContent = true; | |
| } | |
| }); | |
| // Check if there are views | |
| if ($(folder).children("view").not("folder").size() > 0) { | |
| hasContent = true; | |
| } | |
| // If folder is empty, delete it | |
| if (!hasContent) { | |
| console.log(" ✗ Empty folder deleted: " + folder.name); | |
| folder.delete(); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Main program | |
| try { | |
| console.log("============================================="); | |
| console.log(" Export Views with Tree Hierarchy"); | |
| console.log("=============================================\n"); | |
| // Check if model is set | |
| if (!model.isSet()) { | |
| console.log("Model is not selected!"); | |
| window.alert("No model is selected.\n\nPlease open an Archi model."); | |
| exit(); | |
| } | |
| console.log("Current model: " + model.name); | |
| var sourcePath = model.getPath(); | |
| if (!sourcePath) { | |
| window.alert( | |
| "The current model has not been saved yet.\n\n" + | |
| "Please save your model first (File → Save),\n" + | |
| "then run the script again." | |
| ); | |
| exit(); | |
| } | |
| console.log("Source file: " + sourcePath); | |
| // Get all views | |
| var allViews = []; | |
| $("view").not("folder").each(function(view) { | |
| allViews.push(view); | |
| }); | |
| if (allViews.length === 0) { | |
| window.alert("No views available in the model."); | |
| exit(); | |
| } | |
| console.log("Number of available views: " + allViews.length); | |
| // Display selection dialog | |
| console.log("\n--- View Selection ---"); | |
| var result = createSelectionDialog(); | |
| if (!result || selectedViews.length === 0) { | |
| console.log("Operation cancelled."); | |
| exit(); | |
| } | |
| // Deduplicate selected views | |
| var uniqueViews = []; | |
| var viewIds = {}; | |
| var selectedViewIds = []; | |
| selectedViews.forEach(function(view) { | |
| if (!viewIds[view.id]) { | |
| viewIds[view.id] = true; | |
| uniqueViews.push(view); | |
| selectedViewIds.push(view.id); | |
| } | |
| }); | |
| selectedViews = uniqueViews; | |
| console.log("\n" + selectedViews.length + " view(s) selected after deduplication:"); | |
| selectedViews.forEach(function(v) { | |
| console.log(" ✓ " + v.name + " (ID: " + v.id + ")"); | |
| }); | |
| // Ask for confirmation | |
| var confirmMsg = "You have selected " + selectedViews.length + " view(s) out of " + allViews.length + ".\n\n" + | |
| "The script will:\n" + | |
| "1. Save your model with a new name\n" + | |
| "2. Delete non-selected views\n" + | |
| "3. Delete unused concepts\n" + | |
| "4. Delete empty folders\n\n" + | |
| "Your current model will remain INTACT.\n\n" + | |
| "Continue?"; | |
| var confirm = window.confirm(confirmMsg); | |
| if (!confirm) { | |
| console.log("\nOperation cancelled."); | |
| exit(); | |
| } | |
| // Ask for save path | |
| console.log("\n--- Choose Destination File ---"); | |
| var savePath = window.promptSaveFile({ | |
| title: "Save New Model", | |
| filterExtensions: ["*.archimate"], | |
| fileName: "Export_" + model.name + ".archimate" | |
| }); | |
| if (!savePath) { | |
| console.log("Save cancelled."); | |
| exit(); | |
| } | |
| console.log("Destination file: " + savePath); | |
| // Save current model with new name (equivalent to "Save As") | |
| console.log("\n--- Saving Model to New File (Save As) ---"); | |
| model.save(savePath); | |
| console.log("✓ Model saved as: " + savePath); | |
| console.log("✓ Model remains open and active"); | |
| // Now "model" points to the same model, but saved under the new name | |
| // STEP 1: Delete non-selected views | |
| console.log("\n--- Step 1: Deleting Non-Selected Views ---"); | |
| var viewsToDelete = []; | |
| $("view").not("folder").each(function(view) { | |
| var isSelected = false; | |
| for (var i = 0; i < selectedViewIds.length; i++) { | |
| if (selectedViewIds[i] === view.id) { | |
| isSelected = true; | |
| break; | |
| } | |
| } | |
| if (!isSelected) { | |
| viewsToDelete.push(view); | |
| } | |
| }); | |
| console.log("Deleting " + viewsToDelete.length + " views..."); | |
| viewsToDelete.forEach(function(view) { | |
| console.log(" ✗ " + view.name); | |
| view.delete(); | |
| }); | |
| // STEP 2: Delete all concepts (elements + relationships) without visual representation | |
| console.log("\n--- Step 2: Deleting Concepts Without Representation ---"); | |
| var allConcepts = $("concept"); | |
| var totalConcepts = allConcepts.size(); | |
| console.log("Total concepts to process: " + totalConcepts); | |
| var processed = 0; | |
| var deleted = 0; | |
| var lastPercent = 0; | |
| allConcepts.each(function(c) { | |
| if ($(c).objectRefs().isEmpty()) { | |
| c.delete(); | |
| deleted++; | |
| } | |
| processed++; | |
| // Show progress as percentage every 5% | |
| var percent = Math.floor((processed / totalConcepts) * 100); | |
| if (percent >= lastPercent + 5 || processed === totalConcepts) { | |
| console.log(" Progress: " + percent + "% (" + processed + "/" + totalConcepts + ") - Deleted so far: " + deleted); | |
| lastPercent = percent; | |
| } | |
| }); | |
| console.log("✓ Completed: " + deleted + " concepts deleted out of " + totalConcepts); | |
| var deletedCount = deleted; | |
| // STEP 3: Delete empty folders | |
| console.log("\n--- Step 3: Deleting Empty Folders ---"); | |
| var viewsFolders = $("folder").filter(function(f) { return f.name === "Views"; }); | |
| if (viewsFolders.size() > 0) { | |
| deleteEmptyFolders(viewsFolders.first()); | |
| } | |
| // Count remaining elements | |
| var finalElementCount = $("element").size(); | |
| var finalRelCount = $("relationship").size(); | |
| var finalViewCount = $("view").not("folder").size(); | |
| // Save the new model | |
| console.log("\n--- Saving New Model ---"); | |
| model.save(); // model now points to the new model | |
| console.log("✓ New model saved"); | |
| // Reload original model | |
| console.log("\n--- Reloading Original Model ---"); | |
| var originalModel = $.model.load(sourcePath); | |
| originalModel.setAsCurrent(); | |
| console.log("✓ Original model reloaded and set as current"); | |
| // Summary | |
| console.log("\n============================================="); | |
| console.log(" ✓ EXPORT COMPLETED SUCCESSFULLY!"); | |
| console.log("============================================="); | |
| console.log("\nFile created: " + savePath); | |
| console.log("\nNew model contents:"); | |
| console.log(" • Views: " + finalViewCount); | |
| console.log(" • Elements: " + finalElementCount); | |
| console.log(" • Relationships: " + finalRelCount); | |
| console.log("\nDeleted:"); | |
| console.log(" • Views: " + viewsToDelete.length); | |
| console.log(" • Concepts: " + deletedCount); | |
| console.log("\n✅ Your original model is intact and still open."); | |
| console.log("\nTo open the new model:"); | |
| console.log("File → Open → " + savePath); | |
| var successMsg = "✓ EXPORT COMPLETED SUCCESSFULLY!\n\n" + | |
| "File created:\n" + savePath + "\n\n" + | |
| "Contents:\n" + | |
| " • Views: " + finalViewCount + "\n" + | |
| " • Elements: " + finalElementCount + "\n" + | |
| " • Relationships: " + finalRelCount + "\n\n" + | |
| "✅ Your original model is intact\n" + | |
| " and still open.\n\n" + | |
| "To open the new model:\n" + | |
| "File → Open → " + savePath; | |
| window.alert(successMsg); | |
| } catch(error) { | |
| console.log("\n============================================="); | |
| console.log(" ❌ ERROR"); | |
| console.log("============================================="); | |
| console.log("\nError message:"); | |
| console.log(error); | |
| if (error.stack) { | |
| console.log("\nTechnical details:"); | |
| console.log(error.stack); | |
| } | |
| window.alert( | |
| "❌ Error during export\n\n" + | |
| error + | |
| "\n\nCheck the jArchi console for details." | |
| ); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment