xml - VBA:查找和替换 XML 节点
问题描述
我有一个定义进程和一些其他参数的 XML 文件,它们将在启动时由 CAN 控制器加载。我将使用它作为不同控制器的模板,因此我需要操作 XML 某些部分中的项目。特别是,我将定义不同的子组件并修改一些现有的子组件。而且我必须使用 Excel VBA 来处理所有这些东西:最终会有十分之一,如果不是数百个具有不同定义的控制器。
我设法绕过了克隆和附加新节点的过程,但看起来我的大脑拒绝掌握 XML 解析、编辑等背后的概念。
我现在手头的问题是如何查找和替换子组件树的子节点(或者如果一个或多个属性已经满足某些条件:主要是磁盘上的路径)。这里的模板:
<?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>
特别是:说我要删除:
<Subcomponent Name="NmeaCon" Type="SimpleNMEA" src="Components\NmeaCon.xml"></Subcomponent>
还要改变:
<Subcomponent Name="CANIO" Type="CANIO" src="Components/CANIO.xml"></Subcomponent>
进入:
<Subcomponent Name="CANOPEN" Type="CANOPEN" src="Components/Canopen/CANOPEN.xml"></Subcomponent>
编辑
对于删除我已经尝试过:
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
编辑2:对,我以为我很笨,但看起来我太乐观了,自我假设:我远比那笨。该死的 VBA 不能返回 void 的东西(不是每次我必须回到 Office mumbo jumbo 时我都偶然发现这个)所以:
' you got to catch the returned object :o you dumb fool
Set zombie = oldNode.ParentNode.RemoveChild(oldNode)
奇迹般有效。我向模组道歉......
解决方案
使用 XSLT 很容易实现。您只需要从 VBA 调用 XSLT。
XSLT 将处理您的输入 XML 并生成一个新的 XML 文件:
XML + XSLT => 新 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
推荐阅读
- css - 仅在第一个和最后一个输入块上的边界半径
- cmake - 我可以使用 CMake 生成 Apple App Clip 项目吗?
- python - 在 ipywidgets 中显示图例的颜色框
- linux - 在带有wireguard的docker容器中,为什么我只能从docker主机访问nginx而不能远程访问?
- reactjs - Formik Yuk - 删除所需的消息
- c# - 为什么我的任务不会在 C# 控制台应用程序中运行?
- c++ - 我正在尝试从我的静态库中读取网站上的文本
- swift - 如何通过 performSelector 传递块参数?
- sql - 即使数据不存在,如何从 ZEND SQL 获取表字段列表
- javascript - 将引导轮播项目设置为具有最大高度的元素