Reputation: 99
I have an XML file defining the processes and some other parameters, which will be loaded by a CAN controller at start up. I will use this as a template for differrent controllers, so I need to manipulate the items in some sections of the XML. In particular I will be defining different Subcomponents and modifying some of the existing ones. And I have to use Excel VBA to handle all this stuff: eventually there will be tenths if not hundreds of controller with different definitions.
I've managed to get around the process of cloning and appending new nodes, but it looks like my brain is refusing to grasp the concepts behind the XML parsing, editing etc.
My problem at hand now is how to find and replace (or not if one or more attributes already satisfy some conditions: path on disk mostly) a child node of the Subcomponents tree. Here the template:
<?xml version="1.0" ?>
<Application Name="CONTROLLER_A" Type="Application">
<InstanceHelp> This is a template for creating a controller applications.</InstanceHelp>
<Handle>12</Handle>
<!-- NetworkInterfaces -->
<NetworkInterface Name="ETH0" MAC="" IPAddress="192.168.0.21" SubnetMask="255.255.255.0"></NetworkInterface>
<CNTMessages>
</CNTMessages>
<!-- Console configuration
Mode: VGA, VESA_8, VESA_16, VESA_24, VESA_32, Text, None. Use VESA_32 for graphical displays, Text or None for alphanumeric displays.
Background: Black, White, LightGray, DarkGray, Brown
Resolution: 320x240, 640x480, 800x600, 1024x768, 1280x1024
HFreqMin: Minimum horizontal frequency [KHz], limited by monitor.
HFreqMax: Maximum horizontal frequency [KHz], limited by monitor.
VFreqMin: Minimum vertical frequency [Hz], limited by monitor.
HFreqMax: Maximum vertical frequency [Hz], limited by monitor.
VerticalRefresh: Desired vertical refresh rate [Hz], actual refresh may deviate some from specified value.
MouseSpeed: Mouse speed multiplier.
-->
<Console Mode="VESA_32" Background="#f0f0f0" Resolution="1024x768" HFreqMin="24.0" HFreqMax="62.0" VFreqMin="50.0" VFreqMax="77.0" VerticalRefresh="75" Mouse="PS2" MouseSpeed="1"></Console>
<Components>
</Components>
<!-- Custom application values -->
<Subcomponents>
<!-- I/o servers, -->
<Subcomponent Name="Main" Type="Entry" src="Components\CNT\Main.xml"></Subcomponent>
<Subcomponent Name="Messenger" Type="Messenger" src="Components\CNT\Messenger.xml"></Subcomponent>
<Subcomponent Name="WebServer" Type="WebServer" src="Components\CNT\WebServer.xml"></Subcomponent>
<Subcomponent Name="SerialStringCom1" Type="SerialStringDispatcher" src="Components\SerialStringCom1.xml"></Subcomponent>
<Subcomponent Name="SerialStringCom2" Type="SerialStringDispatcher" src="Components\SerialStringCom2.xml"></Subcomponent>
<Subcomponent Name="SerialStringCom3" Type="SerialStringDispatcher" src="Components\SerialStringCom3.xml"></Subcomponent>
<Subcomponent Name="NmeaLog" Type="SimpleNMEA" src="Components\NmeaLog.xml"></Subcomponent>
<Subcomponent Name="NmeaAlarm" Type="SimpleNMEA" src="Components\NmeaAlarm.xml"></Subcomponent>
<Subcomponent Name="NmeaCon" Type="SimpleNMEA" src="Components\NmeaCon.xml"></Subcomponent>
<Subcomponent Name="CANIO" Type="CANIO" src="Components/CANIO.xml"></Subcomponent>
<Subcomponent Name="MessengerIOServer" Type="MessengerIOServer" src="Components\CNT\MessengerIOServer.xml"></Subcomponent>
<Subcomponent Name="CodeSys" Type="CoDeSys" src="Components\CodeSys.xml"></Subcomponent>
</Subcomponents>
<Signals>
<!-- Sysinfo signals -->
<Signal Name="CPULoad" Value="1.0" Type="double" RouteType="Variable" Routing="No routing" Description="CPU load [busy/idle, 0..1]."></Signal>
<Signal Name="MemTotal" Value="0" Type="double" RouteType="Variable" Routing="No routing" Description="Memory total [kb]."></Signal>
<Signal Name="MemUsed" Value="0" Type="double" RouteType="Variable" Routing="No routing" Description="Memory used [kb]."></Signal>
<Signal Name="MemUsedRelative" Value="0" Type="double" RouteType="Variable" Routing="No routing" Description="Memory used relative [used/total, 0..1]."></Signal>
</Signals>
<Alarms>
<Alarm Name="Broken signal routing" Level="Error" Enabled="1" Text="Something is wrong with the routing for one or more of the signals." Description="Something is wrong with the routing for one or more of the signals."></Alarm>
<Alarm Name="Signal not updated" Level="Error" Enabled="1" Text="The signal is not updated (time stamp too old). Probably lost connection with i/o." Description="The signal is not updated (time stamp too old). Probably lost connection with i/o.."></Alarm>
</Alarms>
</Application>
In particular: say that I want to delete:
<Subcomponent Name="NmeaCon" Type="SimpleNMEA" src="Components\NmeaCon.xml"></Subcomponent>
And also to change:
<Subcomponent Name="CANIO" Type="CANIO" src="Components/CANIO.xml"></Subcomponent>
to:
<Subcomponent Name="CANOPEN" Type="CANOPEN" src="Components/Canopen/CANOPEN.xml"></Subcomponent>
For removing I tried:
Set appXML = New MSXML2.DOMDocument
With appXML
If Not .LoadXML("ControllerApp.xml") Then Exit Sub
Set xmlSubTree = .SelectSingleNode("/Application/Subcomponents/Subcomponent").ParentNode
dumString = "//Subcomponent[@Name='SerialStringCom1']"
Set oldNode = xmlSubTree.SelectSingleNode(dumString)
' checking oldNode here gives all the expected attributes values (text)
' but it fails on the next statement: Object does not support this property or method:
oldNode.ParentNode.RemoveChild(oldNode)
End With
Upvotes: 0
Views: 1107
Reputation: 22167
It is very easy to achieve by using XSLT. You just need to call XSLT from VBA.
XSLT will process your input XML and generate a new XML file:
XML + XSLT => new XML
XSLT
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity template -->
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<!-- remove Subcomponent template -->
<xsl:template match='Subcomponent[@Name="NmeaCon" and @Type="SimpleNMEA" and @src="Components\NmeaCon.xml"]'>
</xsl:template>
<!-- modify Subcomponent template -->
<xsl:template match='Subcomponent[@Name="CANIO" and @Type="CANIO" and src="Components/CANIO.xml"]'>
<Subcomponent Name="CANOPEN" Type="CANOPEN" src="Components/Canopen/CANOPEN.xml"></Subcomponent>
</xsl:template>
</xsl:stylesheet>
VBA
Private Sub Transform(sourceFile, stylesheetFile, resultFile)
Dim source As New MSXML2.DOMDocument60
Dim stylesheet As New MSXML2.DOMDocument60
Dim result As New MSXML2.DOMDocument60
' Load data.
source.async = False
source.Load sourceFile
' Load style sheet.
stylesheet.async = False
stylesheet.Load stylesheetFile
If (source.parseError.ErrorCode <> 0) Then
MsgBox ("Error loading source document: " & source.parseError.reason)
Else
If (stylesheet.parseError.ErrorCode <> 0) Then
MsgBox ("Error loading stylesheet document: " & stylesheet.parseError.reason)
Else
' Do the transform.
source.transformNodeToObject stylesheet, result
result.Save resultFile
End If
End If
End Sub
Upvotes: 1