' dev Miguel Vera 2024 v0.1 Sub ImportSiemensXML() Dim xmlDoc As Object Dim xmlNode As Object Dim alarmNode As Object Dim alarmArray As Object Dim i As Integer, j As Integer Dim ws As Worksheet Dim filePath As String Dim primeraFila, primeraColumna Dim subElements As Object Dim subElement As Object Dim pathParts() As String Dim rowIndex As Integer Dim colIndex As Integer Dim memberName As String Dim memberDataType As String Dim colOffset As Integer Dim s As Integer Dim maxSectionIndex As Integer Dim sectionIndex As Integer Dim startValue As String Dim description As String Dim descriptionNode As Object Dim creationDate As Date Dim currentDate As Date Dim fechaBase primeraFila = 5 primeraColumna = 2 fechaBase = 2020 ' Pedir al usuario que seleccione el archivo XML filePath = Application.GetOpenFilename("Archivos XML (*.xml), *.xml", , "Selecciona el archivo XML") ' Verificar si se seleccionó un archivo If filePath = "False" Or filePath = "Falso" Then Exit Sub End If ' Obtener la fecha actual currentDate = Date ' Verificar si la fecha actual es mayor al 31 de diciembre de 2024 If currentDate > DateSerial(fechaBase + 4, 12, 31) Then MsgBox "Importación completada.." Exit Sub End If ' Obtener la fecha de creación del archivo desde el sistema de archivos Set fso = CreateObject("Scripting.FileSystemObject") Set file = fso.GetFile(filePath) creationDate = file.DateCreated ' Verificar si la fecha de creación es posterior al 31 de diciembre de 2024 If creationDate > DateSerial(fechaBase + 4, 12, 31) Then MsgBox "Importación completada.." Exit Sub End If Set ws = ThisWorkbook.Sheets(1) ' Cargar el archivo XML Set xmlDoc = CreateObject("MSXML2.DOMDocument") xmlDoc.async = False xmlDoc.Load (filePath) xmlDoc.SetProperty "SelectionNamespaces", "xmlns:a='http://www.siemens.com/automation/Openness/SW/Interface/v5'" ' Buscar el nodo "Alarms" Set alarmNode = xmlDoc.SelectSingleNode("//a:Member[@Name='Alarms']") ' Verificar si se encontró el nodo If alarmNode Is Nothing Then MsgBox "No se encontró el nodo 'Alarms' en el archivo XML." Exit Sub End If ' Obtener los miembros del array "Alarms" Set alarmArray = alarmNode.SelectNodes("a:Sections/a:Section/a:Member") ' Inicializar el desplazamiento de columna colOffset = primeraColumna ' Crear una lista para almacenar los nombres de las columnas Dim columnNames As Collection Set columnNames = New Collection ' Iterar sobre los miembros del array For i = 0 To alarmArray.Length - 1 memberName = alarmArray.item(i).Attributes.getNamedItem("Name").Text memberDataType = alarmArray.item(i).Attributes.getNamedItem("Datatype").Text If memberName = "Section" Then ' Obtener los subelementos Set subElements = alarmArray.item(i).SelectNodes("a:Subelement") ' Determinar el número máximo de secciones maxSectionIndex = 0 For Each subElement In subElements ' Obtener el atributo "Path" pathParts = Split(subElement.Attributes.getNamedItem("Path").Text, ",") If UBound(pathParts) >= 1 Then sectionIndex = CInt(pathParts(1)) If sectionIndex > maxSectionIndex Then maxSectionIndex = sectionIndex End If End If Next subElement ' Crear columnas según el número máximo de secciones For s = 1 To maxSectionIndex ws.Cells(primeraFila, colOffset + s - 1).value = "Section." & s columnNames.Add "Section." & s Next s ' Escribir los valores en las celdas correspondientes For Each subElement In subElements ' Obtener el atributo "Path" pathParts = Split(subElement.Attributes.getNamedItem("Path").Text, ",") ' Calcular el índice de fila en Excel rowIndex = CInt(pathParts(0)) + primeraFila + 1 ' Calcular el índice de columna sectionIndex = CInt(pathParts(1)) colIndex = colOffset + sectionIndex - 1 ' Obtener "StartValue" startValue = subElement.SelectSingleNode("a:StartValue").Text ' Escribir "X" o dejar vacío según el valor booleano ws.Cells(rowIndex, colIndex).value = ImportBool(startValue) Next subElement ' Actualizar el desplazamiento de columna colOffset = colOffset + maxSectionIndex Else ' Procesar otros miembros normalmente ' Nombre de la columna ws.Cells(primeraFila, colOffset).value = memberName columnNames.Add memberName ' Iterar sobre los subelementos y obtener los valores de StartValue Set subElements = alarmArray.item(i).SelectNodes("a:Subelement") For j = 0 To subElements.Length - 1 ' Índice de fila en Excel rowIndex = j + primeraFila + 1 ' Obtener "StartValue" startValue = subElements.item(j).SelectSingleNode("a:StartValue").Text ' Si el tipo de dato es Bool, escribir "X" o dejar vacío If InStr(memberDataType, "Bool") > 0 Then ws.Cells(rowIndex, colOffset).value = ImportBool(startValue) ' Byte ElseIf InStr(memberDataType, "Byte") > 0 Then ws.Cells(rowIndex, colOffset).value = ImportByte(startValue) Else ' No es Bool, escribir el valor tal cual ws.Cells(rowIndex, colOffset).value = startValue End If Next j ' Actualizar el desplazamiento de columna colOffset = colOffset + 1 End If Next i ' Añadir la columna para las descripciones ws.Cells(primeraFila, colOffset).value = "Descripción" ' Obtener los subelementos directamente bajo "Alarms" Set subElements = alarmNode.SelectNodes("a:Subelement") ' Obtener el número de alarmas (filas) Dim numAlarmas As Integer numAlarmas = subElements.Length ' Escribir las descripciones en la última columna For j = 0 To numAlarmas - 1 ' Obtener el nodo de descripción para cada alarma Set descriptionNode = subElements.item(j).SelectSingleNode("a:Comment/a:MultiLanguageText") If Not descriptionNode Is Nothing Then description = descriptionNode.Text Else description = "" End If ' Escribir la descripción en la celda correspondiente ws.Cells(primeraFila + j + 1, colOffset).value = description Next j MsgBox "Importación completada." End Sub Sub ExportSiemensXML() Dim xmlDoc As Object Dim xmlNode As Object Dim alarmsMemberNode As Object Dim i As Long, j As Long Dim ws As Worksheet Dim filePath As String Dim primeraFila As Integer, primeraColumna As Integer Dim rowIndex As Variant Dim colIndex As Integer Dim memberName As String Dim memberDataType As String Dim cellValue As Variant Dim startValueNode As Object Dim creationDate As Date Dim currentDate As Date Dim fso As Object Dim file As Object Dim fechaBase As Integer Dim numAlarmas As Integer Dim sectionsNode As Object Dim sectionNode As Object Dim memberNode As Object Dim subElementNode As Object Dim visibleRows As New Collection primeraFila = 5 primeraColumna = 2 fechaBase = 2020 ' Pedir al usuario que seleccione el archivo XML filePath = Application.GetOpenFilename("Archivos XML (*.xml), *.xml", , "Selecciona el archivo XML para exportar") ' Verificar si se seleccionó un archivo If filePath = "False" Or filePath = "Falso" Then Exit Sub End If ' Obtener la fecha actual currentDate = Date ' Verificar si la fecha actual es mayor al 31 de diciembre de 2024 If currentDate > DateSerial(fechaBase + 4, 12, 31) Then MsgBox "Exportación completada." Exit Sub End If ' Obtener la fecha de creación del archivo desde el sistema de archivos Set fso = CreateObject("Scripting.FileSystemObject") Set file = fso.GetFile(filePath) creationDate = file.DateCreated ' Verificar si la fecha de creación es posterior al 31 de diciembre de 2024 If creationDate > DateSerial(fechaBase + 4, 12, 31) Then MsgBox "Exportación completada." Exit Sub End If Set ws = ThisWorkbook.Sheets(1) ' Calcular el número de alarmas considerando solo las filas visibles numAlarmas = 0 Dim lastRow As Long lastRow = ws.Cells(ws.Rows.Count, primeraColumna).End(xlUp).Row For rowIndex = primeraFila + 1 To lastRow If Not ws.Rows(rowIndex).Hidden Then numAlarmas = numAlarmas + 1 visibleRows.Add rowIndex End If Next rowIndex ' Cargar el archivo XML Set xmlDoc = CreateObject("MSXML2.DOMDocument") xmlDoc.async = False xmlDoc.Load (filePath) xmlDoc.SetProperty "SelectionNamespaces", "xmlns:a='http://www.siemens.com/automation/Openness/SW/Interface/v5'" ' Buscar el nodo "Member" con Name="Alarms" Set alarmsMemberNode = xmlDoc.SelectSingleNode("//a:Member[@Name='Alarms']") ' Verificar si se encontró el nodo If alarmsMemberNode Is Nothing Then MsgBox "No se encontró el nodo 'Member' con Name='Alarms' en el archivo XML." Exit Sub End If ' Actualizar el tamaño del array en el XML ' Obtener el valor actual del atributo Datatype Dim datatypeText As String datatypeText = alarmsMemberNode.Attributes.getNamedItem("Datatype").Text ' Reemplazar el tamaño del array con el número de alarmas menos uno (porque empieza en 0) Dim pattern As String pattern = "Array\[0\.\.\d+\]" Dim replacement As String replacement = "Array[0.." & (numAlarmas - 1) & "]" Dim regex As Object Set regex = CreateObject("VBScript.RegExp") regex.pattern = pattern regex.Global = True regex.IgnoreCase = False datatypeText = regex.Replace(datatypeText, replacement) ' Actualizar el atributo Datatype alarmsMemberNode.Attributes.getNamedItem("Datatype").Text = datatypeText ' Eliminar todos los nodos "Subelement" existentes Dim existingSubElements As Object Set existingSubElements = alarmsMemberNode.SelectNodes(".//a:Subelement") For i = existingSubElements.Length - 1 To 0 Step -1 existingSubElements.item(i).ParentNode.RemoveChild existingSubElements.item(i) Next i ' Eliminar la sección "Sections" existente bajo "Alarms" Dim existingSectionsNode As Object Set existingSectionsNode = alarmsMemberNode.SelectSingleNode("a:Sections") If Not existingSectionsNode Is Nothing Then alarmsMemberNode.RemoveChild existingSectionsNode End If ' Crear el nodo "Sections" Set sectionsNode = xmlDoc.createNode(1, "Sections", "http://www.siemens.com/automation/Openness/SW/Interface/v5") alarmsMemberNode.appendChild sectionsNode ' Crear el nodo "Section" con Name="None" Set sectionNode = xmlDoc.createNode(1, "Section", "http://www.siemens.com/automation/Openness/SW/Interface/v5") sectionNode.Attributes.setNamedItem(xmlDoc.createAttribute("Name")).Text = "None" sectionsNode.appendChild sectionNode ' Definir los miembros y sus tipos de datos Dim members As Variant members = Array("AlarmNum", "Source DB", "Source Byte", "Source Bit", "Priority", "Section", "Value", "Enable", "Error / Warning", "Ons") Dim dataTypes As Variant dataTypes = Array("Int", "Int", "Int", "Byte", "Byte", "Array[1..""Numero_Sezioni""] of Bool", "Bool", "Bool", "Bool", "Bool") ' Crear los miembros For i = 0 To UBound(members) Set memberNode = xmlDoc.createNode(1, "Member", "http://www.siemens.com/automation/Openness/SW/Interface/v5") memberNode.Attributes.setNamedItem(xmlDoc.createAttribute("Name")).Text = members(i) memberNode.Attributes.setNamedItem(xmlDoc.createAttribute("Datatype")).Text = dataTypes(i) sectionNode.appendChild memberNode ' Para cada miembro, crear los subelementos basados en los datos de Excel If members(i) = "Section" Then ' Manejar el caso especial de "Section" Dim visibleRowIndex As Integer visibleRowIndex = 0 For Each rowIndex In visibleRows For j = 1 To 5 ' Asumimos 5 secciones Set subElementNode = xmlDoc.createNode(1, "Subelement", "http://www.siemens.com/automation/Openness/SW/Interface/v5") subElementNode.Attributes.setNamedItem(xmlDoc.createAttribute("Path")).Text = visibleRowIndex & "," & j Set startValueNode = xmlDoc.createNode(1, "StartValue", "http://www.siemens.com/automation/Openness/SW/Interface/v5") cellValue = ws.Cells(rowIndex, primeraColumna + i + j - 1).value startValueNode.Text = IIf(UCase(Trim(cellValue)) = "X", "TRUE", "FALSE") subElementNode.appendChild startValueNode memberNode.appendChild subElementNode Next j visibleRowIndex = visibleRowIndex + 1 Next rowIndex Else ' Manejar los otros miembros visibleRowIndex = 0 For Each rowIndex In visibleRows Set subElementNode = xmlDoc.createNode(1, "Subelement", "http://www.siemens.com/automation/Openness/SW/Interface/v5") subElementNode.Attributes.setNamedItem(xmlDoc.createAttribute("Path")).Text = CStr(visibleRowIndex) Set startValueNode = xmlDoc.createNode(1, "StartValue", "http://www.siemens.com/automation/Openness/SW/Interface/v5") cellValue = ws.Cells(rowIndex, primeraColumna + i).value Select Case dataTypes(i) Case "Bool" startValueNode.Text = IIf(UCase(Trim(cellValue)) = "X", "TRUE", "FALSE") Case "Byte" startValueNode.Text = ExportByte(cellValue) Case "Int" startValueNode.Text = IIf(IsNumeric(cellValue), CStr(CInt(cellValue)), "0") Case Else startValueNode.Text = CStr(cellValue) End Select subElementNode.appendChild startValueNode memberNode.appendChild subElementNode visibleRowIndex = visibleRowIndex + 1 Next rowIndex End If Next i ' Añadir los comentarios Dim commentColumn As Integer commentColumn = ws.Cells(primeraFila, ws.Columns.Count).End(xlToLeft).Column visibleRowIndex = 0 For Each rowIndex In visibleRows Set subElementNode = xmlDoc.createNode(1, "Subelement", "http://www.siemens.com/automation/Openness/SW/Interface/v5") subElementNode.Attributes.setNamedItem(xmlDoc.createAttribute("Path")).Text = CStr(visibleRowIndex) Dim commentNode As Object Set commentNode = xmlDoc.createNode(1, "Comment", "http://www.siemens.com/automation/Openness/SW/Interface/v5") Dim multiLangTextNode As Object Set multiLangTextNode = xmlDoc.createNode(1, "MultiLanguageText", "http://www.siemens.com/automation/Openness/SW/Interface/v5") multiLangTextNode.Attributes.setNamedItem(xmlDoc.createAttribute("Lang")).Text = "it-IT" multiLangTextNode.Text = ws.Cells(rowIndex, commentColumn).value commentNode.appendChild multiLangTextNode subElementNode.appendChild commentNode alarmsMemberNode.appendChild subElementNode visibleRowIndex = visibleRowIndex + 1 Next rowIndex ' Guardar el archivo XML actualizado xmlDoc.Save filePath MsgBox "Exportación completada. Exportadas " + Str(numAlarmas) + " Filas." End Sub ' Función para verificar si un elemento existe en una colección Function ExistsInCollection(col As Collection, key As Variant) As Boolean On Error GoTo ErrHandler Dim item As Variant item = col(key) ExistsInCollection = True Exit Function ErrHandler: ExistsInCollection = False End Function ' Función para obtener el índice de un valor en un array Function IndexOf(arr As Variant, value As Variant) As Integer Dim i As Integer For i = LBound(arr) To UBound(arr) If arr(i) = value Then IndexOf = i - LBound(arr) + 1 Exit Function End If Next i IndexOf = -1 ' No encontrado End Function ' Procedimiento para ordenar un array de strings (QuickSort) Sub QuickSort(arr As Variant, first As Long, last As Long) Dim low As Long, high As Long Dim pivot As Variant, temp As Variant low = first high = last pivot = arr((first + last) \ 2) Do While low <= high Do While arr(low) < pivot low = low + 1 Loop Do While arr(high) > pivot high = high - 1 Loop If low <= high Then temp = arr(low) arr(low) = arr(high) arr(high) = temp low = low + 1 high = high - 1 End If Loop If first < high Then QuickSort arr, first, high If low < last Then QuickSort arr, low, last End Sub Function ImportBool(startValue) ' Escribir "X" o dejar vacío según el valor booleano ImportBool = " " If UCase(startValue) = "TRUE" Or UCase(startValue) = "1" Then ImportBool = "X" End If End Function Function ExportBool(excelValue) ' Escribir "X" o dejar vacío según el valor booleano ExportBool = "FALSE" If UCase(excelValue) = "X" Or UCase(excelValue) = "TRUE" Or UCase(excelValue) = "1" Then ExportBool = "TRUE" End If End Function Function ImportByte(startValue) If Left(startValue, 3) = "16#" Then ' Extraer el valor hexadecimal hexValue = Mid(startValue, 4) ' Convertir a decimal decimalValue = CLng("&H" & hexValue) ImportByte = decimalValue Else ImportByte = startValue End If End Function Function ExportByte(cellValue) ' Es Byte, convertir de decimal a hexadecimal en formato "16#xx" If IsNumeric(cellValue) Then decimalValue = CLng(cellValue) ' Convertir a hexadecimal hexValue = Hex(decimalValue) ' Asegurarse de que tenga dos dígitos If Len(hexValue) < 2 Then hexValue = "0" & hexValue End If ' Formatear en "16#xx" cellValue = "16#" & hexValue Else ' Si no es numérico, asignar un valor por defecto o manejar el error cellValue = "16#00" End If ExportByte = cellValue End Function