Macros – Import objects from a diagrams.net | draw.io XML file
A script that imports a particular diagrams.net XML file (formerly draw.io) including specific Data used to retrieve the properties required to create the Entity Relationship...
Last year, I published this post: Macros – Sample macros to import Logical and Physical Data Models from Microsoft Visio to ER/Studio Data Architect
Today, I’ll share a script that imports a particular diagrams.net XML file (formerly draw.io) including specific Data used to retrieve the properties required to create the Entity Relationship Diagram.
Firstly, I have created a new diagram using diagrams.net:
Logical Data Model: GIM
Then, in this diagram, I have added specific data to the shapes I used in order to identify these objects:
Entity’s Data
Attribute’s Data
Relationship’s Data
Even though the relationships are linked to the entities, I have decided to add Source & Target properties. This decision was made because the object dropped on the diagram can be linked to many different connection points belonging to various shapes (entity, attribute, PK, FK, etc.).
Finally, I exported my diagram as XML (no compression, current page):
XML export options
Resources
The XML file is too verbose to be nicely shared as text below. You can download it from here.
Here is the script of the macro which can use this XML file:
'#Language "WWB-COM"
''MACRO TITLE: wImportDrawIO
' MACRO VERSION: 1.1
'
' This macro imports Entities Attributes and relationships from a draw.io XML file
'with custom properties
'
' Known limitations:
'- Specific data are required to import the objects
'- Only the first page is imported
'- Compressed XML format is not supported
'
' Blog post: https://idera.com/blogs/macros-import-objects-from-a-diagrams-net-xml-file/
'
'-----------------------------------------------------------------------------------
OptionExplicit
Const TITLE$ = "wImportDrawIO"Dim MyModel As Model
Sub Main
Debug.Clear
Begin Dialog UserDialog 720,105,TITLE,.DlgFunc ' %GRID:10,7,1,1
Text 10,21,120,14,"File Path:",.txtFile,1
TextBox 150,21,450,21,.edFileName
PushButton 610,21,90,21,"Select File",.btnSelectFile
PushButton 260,70,90,21,"Import",.btnImport
CancelButton 380,70,90,21,.btnExit
End Dialog
Dim dlg As UserDialog
If Dialog(dlg) = 0 ThenExitSub
EndSub
PrivateSub DoImport(xmlFilename$)
#Region "Variables"Dim diag As Diagram
Dim MySubModel As SubModel
Dim MyEntity As Entity
Dim MyEntityDisplay As EntityDisplay
Dim MyAttribute As AttributeObj
Dim MyRelationship As Relationship
Dim MyShape As Shape
Dim MyShapeDisplay As ShapeDisplay
Dim xml$
Dim cptEntities%, cptAttributes%, cptRelationships%, cptShapes%
Dim objStream
Dim oXml As MSXML2.DOMDocument60
Dim allItems, oneItem, oneNode As IXMLDOMNode
Dim myProject$, wentity$, wtable$, wid$, wx%, wy%, ww%, wh%
Dim wattribute$, wcolumn$, wdatatype$, wnull$, wpk AsBooleanDim wname$, wsource$, wtarget$, wrolename$, wstyle$
Dim datatype$, datawidth%, nPos%, s$, nSepPos%, datascale%
Dim StartTime!
#End Region
StartTime = Timer
OnErrorGoTo Error_detected
' Disable Tree updates: it doesn't work on the DD treeview
DiagramManager.EnableScreenUpdateEx(False, False)
Set diag = DiagramManager.NewDiagram
#Region "XML"' Load XML from UTF-8 fileSet objStream = CreateObject("ADODB.Stream")
objStream.CharSet = "utf-8"
objStream.Open
objStream.LoadFromFile(xmlFilename)
xml = objStream.ReadText()
objStream.CloseSet objStream = Nothing
' Parse XMLSet oXml = New MSXML2.DOMDocument60
If oXml.loadXML xml Then
Debug.Print"XML loaded"
oXml.async = False
oXml.setProperty "SelectionLanguage", "XPath"Set allItems = oXml.selectNodes("//mxfile")
#End RegionIf allItems.length = 0 ThenMsgBox"Root node not found!", vbExclamation, TITLE
DiagramManager.EnableScreenUpdateEx(True, True)
ExitSub
ElseSet oneItem = oXml.selectSingleNode("mxfile/diagram")
If oneItem IsNothingThen
DiagramManager.EnableScreenUpdateEx(True, True)
ExitSubElse' Get the Page name
myProject = GetAttributeValue(oneItem, "name")
diag.ProjectName = myProject
diag.CopyrightYear = "" & Year(Now)
EndIfEndIf
#Region "object"' Search specific nodesSet allItems = oneItem.selectNodes("mxGraphModel/root/object")
If allItems.length = 0 Then
Debug.Print"Unknown path"MsgBox"object not found!", vbExclamation, TITLE
ElseConst UNKNOWN$ = "Unknown"Set MyModel = diag.ActiveModel
Set MySubModel = MyModel.ActiveSubModel
' Name compartments are displayed
MySubModel.ShowEntityNameCompartment = True
MySubModel.PrimaryKeyFont.Bold = True
' Initialize counters
cptEntities = 0
cptAttributes = 0
cptRelationships = 0
cptShapes = 0
' Loop all matching nodesForEach oneItem In allItems
' Check if the current XML node has an attribute "TableName"If GetAttributeValue(oneItem, "TableName") <> ""Then
' ENTITY
wentity = GetAttributeValue(oneItem, "label")
wtable = GetAttributeValue(oneItem, "TableName")
wid = GetAttributeValue(oneItem, "id")
' Required properties are availableIf wentity <> ""And wtable <> ""ThenSet oneNode = oneItem.selectSingleNode("mxCell").selectSingleNode("mxGeometry")
IfNot oneNode IsNothingThen
wx = CInt(GetAttributeValue(oneNode, "x"))
wy = CInt(GetAttributeValue(oneNode, "y"))
ww = CInt(GetAttributeValue(oneNode, "width"))
wh = CInt(GetAttributeValue(oneNode, "height"))
Else
wx = 0
wy = 0
ww = 0
wh = 0
EndIf
' Create the EntitySet MyEntity = MyModel.Entities.AddEx(wx, -wy)
Debug.Print"ENT:" & vbTab & wentity
cptEntities += 1
MyEntity.EntityName = wentity
MyEntity.TableName = wtable
Set MyEntityDisplay = MySubModel.EntityDisplays(wentity)
' Set size
MyEntityDisplay.HorizontalSize = ww
MyEntityDisplay.VerticalSize = wh
' Get object IDIf wid <> ""Then' Get the main color of the object
MyEntityDisplay.BackgroundColor = GetColor(oXml, wid)
EndIf
EndIf
ElseIf GetAttributeValue(oneItem, "ColumnName") <> ""Then
' ATTRIBUTE
wattribute = GetAttributeValue(oneItem, "label")
wcolumn = GetAttributeValue(oneItem, "ColumnName")
wdatatype = GetAttributeValue(oneItem, "Datatype")
wnull = UCase(GetAttributeValue(oneItem, "NullOption"))
wpk = UCase(GetAttributeValue(oneItem, "IsPK")) = "TRUE"
' Parse the datatype value
nPos = InStr(wdatatype, "(")
If nPos = 0 Then
datatype = wdatatype
datawidth = 0
Else
datatype = Mid(wdatatype, 1, nPos - 1)
wdatatype = Mid(wdatatype, nPos +1, Len(wdatatype))
nPos = InStr(wdatatype, ")")
s = Mid(wdatatype, 1, nPos - 1)
nSepPos = InStr(s, ",")
If nSepPos = 0 Then
datawidth = Mid(s, 1, nPos - 1)
datascale = 0
Else
datawidth = Mid(s, 1, nSepPos - 1)
datascale = Mid(s, nSepPos + 1, Len(wdatatype))
EndIfEndIf
' Create the AttributeSet MyAttribute = MyEntity.Attributes.Add(wattribute, wpk)
Debug.Print"ATT:" & vbTab & MyAttribute.AttributeName
cptAttributes += 1
MyAttribute.ColumnName = wcolumn
MyAttribute.Datatype = datatype
MyAttribute.DataLength = datawidth
MyAttribute.DataScale = datascale
MyAttribute.NullOption = wnull
ElseIf GetAttributeValue(oneItem, "RelationshipName") <> ""Then' RELATIONSHIP
wname = GetAttributeValue(oneItem, "RelationshipName")
wsource = GetAttributeValue(oneItem, "Source")
wtarget = GetAttributeValue(oneItem, "Target")
wrolename = GetAttributeValue(oneItem, "Rolename")
wstyle = GetAttributeValue(oneItem.selectSingleNode("mxCell"), "style")
' Required properties are availableIf wname <> ""And wsource <> ""And wtarget <> ""ThenIf wrolename = ""Then' Create the Relationship
' Set MyRelationship = MyModel.Relationships.Add(wsource, wtarget, GetRelationshipType(wstyle))Set MyRelationship = MyModel.Relationships.AddWithUnification(wsource, wtarget, GetRelationshipType(wstyle))
Else
' Create the Relationship with a specific Attribute nameSet MyRelationship = MyModel.Relationships.AddWithRoleName(wsource, wtarget, GetRelationshipType(wstyle), wrolename)
EndIf
IfNot MyRelationship IsNothingThen
MyRelationship.Name = wname
Debug.Print"RS: " & vbTab & wname
cptRelationships += 1
EndIf
EndIf
Else' UNKNOWN: Creating shape is possible
wname = GetAttributeValue(oneItem, "label")
' Required properties are availableIf wname <> ""ThenSet oneNode = oneItem.selectSingleNode("mxCell").selectSingleNode("mxGeometry")
IfNot oneNode IsNothingThen
wx = CInt(GetAttributeValue(oneNode, "x"))
wy = CInt(GetAttributeValue(oneNode, "y"))
ww = CInt(GetAttributeValue(oneNode, "width"))
wh = CInt(GetAttributeValue(oneNode, "height"))
Else
wx = 0
wy = 0
ww = 0
wh = 0
EndIf
If ww <> 0 And wh <> 0 Then
Set MyShape = MyModel.Shapes.AddWithPosition(wname, wname, 2, wx, -wy)
Debug.Print"SH: " & vbTab & MyShape.Name
cptShapes += 1
Set MyShapeDisplay = MySubModel.ShapeDisplays(MyShape.Name)
IfNot MyShapeDisplay IsNothingThen
DiagramManager.EnableScreenUpdateEx(True, True)
' Set size
MyShapeDisplay.HorizontalSize = ww
MyShapeDisplay.VerticalSize = wh
' Justification
MyShapeDisplay.HorizontalJustification = 2 ' Center
MyShapeDisplay.VerticalJustification = 2 ' Middle
DiagramManager.EnableScreenUpdateEx(False, False)
EndIf
EndIf
EndIf
EndIf
Next#End Region
EndIf
DiagramManager.EnableScreenUpdateEx(True, True)
MsgBox"Import completed! " & Format((Timer - StartTime) / 86400, "hh:mm:ss") & vbCrLf & vbCrLf & cptEntities & " elements created" & vbCrLf & cptAttributes & " attributes created" & vbCrLf & cptRelationships & " relationships created" & vbCrLf & cptShapes & " shapes created", vbInformation, TITLE
ElseMsgBox"XML can not be loaded!" & vbCrLf & xmlFilename, vbExclamation, TITLE
EndIf
ExitSub
Error_detected:
Debug.Print"Error occured!" & IIf(DiagramManager.GetLastErrorCode <> 0, vbCrLf & DiagramManager.GetLastErrorString(), "")
Debug.PrintResumeNext
DiagramManager.EnableScreenUpdateEx(True, True)
EndSub
PrivateFunction GetRelationshipType%(style$)
' Need to be extended with the other shapes you may useIfInStr(style, "endArrow=ERzeroToMany;startArrow=ERzeroToOne;") > 0 Then
GetRelationshipType = 3
ElseIfInStr(style, "endArrow=ERzeroToMany;startArrow=ERmandOne;") > 0 Then
GetRelationshipType = 1
Else
GetRelationshipType = 2
EndIf
EndFunction
PrivateFunction GetColor&(ByRef oXml As MSXML2.DOMDocument60, id$)
Dim selectedNodes, node As IXMLDOMNode
Dim s$
GetColor = vbWhite
' Search the nodes linked to the ObjectSet selectedNodes = oXml.selectNodes("//mxCell[@parent='" & id & "']")
' Loop through the nodesForEach node In selectedNodes
s = GetAttributeValue(node, "style")
' s = Mid(s, InStr(s, "strokeColor=") + 13)
s = Mid(s, InStr(s, "fillColor=") + 11)
s = Left(s, InStr(s, ";") - 1)
'Debug.Print s
IfLen(s) = 6 Then
GetColor = RGB(Val("&H" & Mid(s, 1, 2)), Val("&H" & Mid(s, 3, 2)), Val("&H" & Mid(s, 5, 2)))
ExitFor' Color has been found
EndIfNext node
EndFunction
PrivateFunction GetAttributeValue$(node As IXMLDOMNode, att$)
'
GetAttributeValue = ""OnErrorResumeNextIfNot node.attributes.getNamedItem(att) IsNothingThen
GetAttributeValue = Trim(node.attributes.getNamedItem(att).text)
EndIf
EndFunction
#Region "DialogFunc"Rem See DialogFunc help topic for more information.PrivateFunction DlgFunc(DlgItem$, Action%, SuppValue&) AsBooleanDim fileName$
Dim fileExt$
SelectCase Action%
Case 1 ' Dialog box initialization' DlgText("edFileName", "C:\Users\William\Documents\ERStudio Data Architect 20.1\Tests\GIM.drawio.xml")
Case 2 ' Value changing or button pressed
SelectCase DlgItem$
Case"btnSelectFile"
fileName = GetFilePath(,"draw.io XML File (*.xml)|*.xml|All Files (*.*)|*.*",,"Select File", 0)
If (filename <> "") Then
DlgText("edFileName", filename)
EndIf
DlgFunc = TrueExitFunction
Case"btnImport"
DlgEnable("btnImport", False)
DlgEnable("btnExit", False)
DlgEnable("btnSelectFile", False)
filename = DlgText("edFileName")
IfLen(filename) = 0 ThenMsgBox"You must specify a file.", vbExclamation, TITLE
DlgFunc = TrueExitFunctionElseIfNot FileExists(filename) ThenMsgBox"Specified file does not exist.", vbExclamation, TITLE
DlgFunc = TrueExitFunctionElse
fileExt = Right$(filename, Len(filename) - InStrRev(filename, "."))
If (LCase(Left(fileExt, 3)) <> "xml") ThenMsgBox("You can only select an XML file!", vbExclamation, TITLE)
ExitFunctionEndIfEndIfEndIf
DoImport(fileName)
DlgFunc = FalseExitFunction
Case"btnExit"
DlgFunc = FalseExitFunction
EndSelectCase 3 ' TextBox or ComboBox text changedCase 4 ' Focus changedCase 5 ' IdleRem DlgFunc = True ' Continue getting idle actionsCase 6 ' Function keyEndSelect
EndFunction#End Region
PrivateFunction FileExists(ByVal fileName AsString) AsBoolean
FileExists = (Dir(filename) <> "")
EndFunction
This macro utilizes a reference to the Microsoft XML 6.0 library. To add this reference to your macro, follow these steps:
Open the Macro Editor.
Right-click in the Code Window.
Select the menu item: Edit/References…
From there, you can locate and select the Microsoft XML 6.0 reference to add it to your macro environment.
The References dialog shows the current macro/module/project’s references. You need to add the Microsoft XML, v6.0 (6.0):
Microsoft XML v6.0
After adding the reference, the macro should run without raising an error for a valid data type.
The macro depends on the data specified on each objects. It does the job for the XML file I shared. Using it with your own XML exports would probably require to update the macro too. So, as usual, feel free to modify the script so that it perfectly meets your expectations, or just copy the parts of this one to your own macros.
Bonus
A short video which shows how to create a macro from a script in ER/Studio Data Architect:
William W.
Keep SQL Server Fast, Reliable and Secure - with SQL Diagnostic Manager