我有一个XML程序found here,它接受一个标记的特定子节点,并将其作为另一个标记的子节点。部分代码涉及告诉VBA如何遍历树,这非常有意义。问题是,当我在Visual Basic编辑器的“Locals”窗口中实际打开树结构时,我发现自己无法访问从实际XML文件中看起来清晰可见的树部分。我正在使用的XML类似于以下块:
<Root>
<Results xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Reference>{REFERENCE-HERE}</Reference>
<FillerTags>Filler</FillerTags>
<entity>
<entityName>ABC</entityName>
<entityId>012345</entityId>
</entity>
<Items>
<Item>
<entityId>012345</entityId>
<FillerTagsAgain1>Filler2</FillerTagsAgain1>
<FillerTagsAgain2>Filler2</FillerTagsAgain2>
<FillerTagsAgain3>Filler2</FillerTagsAgain3>
</Item>
<Item>
<entityId>012345</entityId>
<FillerTagsAgain1>Filler2</FillerTagsAgain1>
<FillerTagsAgain2>Filler2</FillerTagsAgain2>
<FillerTagsAgain3>Filler2</FillerTagsAgain3>
</AnotherItem>
</Items>
<Contents>
<MoreFiller>asdf</MoreFiller>
</Contents>
</Results>
<Results>
<entity>
<entityName>DEF</entityName>
<entityId>678910</entityId>
</entity>
<Items>
<Item>
<entityId>678910</entityId>
<FillerTagsAgain1>Filler2</FillerTagsAgain1>
<FillerTagsAgain2>Filler2</FillerTagsAgain2>
<FillerTagsAgain3>Filler2</FillerTagsAgain3>
</Item>
<Item>
<entityId>678910</entityId>
<FillerTagsAgain1>Filler2</FillerTagsAgain1>
<FillerTagsAgain2>Filler2</FillerTagsAgain2>
<FillerTagsAgain3>Filler2</FillerTagsAgain3>
</Item>
</Items>
<Contents>
<MoreFiller>asdf</MoreFiller>
</Contents>
</Results>
</Root>
包含项目和内容只是为了澄清块下面有多个块。这里'entity'是Customer,Item是Transaction,Contents是Indicators。
例如,要复制的节点是“客户”的子节点。客户有几个兄弟姐妹,称他们为Sibling1
= Transactions
= Items
,Sibling2
= Indicators
= Contents
,Sibling3
= Validation
(不在示例XML中)。当我打开本地窗口时,我从Customer的子节点开始。我想访问Sibling1的孩子们。为此,我使用路由Customer.ParentNode.NextSibling.ChildNodes
,代码运行正常。但是,如果我将其更改为Customer.ParentNode.NextSibling.NextSibling.ChildNodes
VBA表明Sibling3实际上是Sibling2。也就是说,路径显示Validation
而非Indicators
。但是,查看实际的XML文件,似乎很明显指标在Transactions
之后。有没有明确的理由可能会出现这种情况?
编辑:下面是我引用的树结构图片以及引用它的代码。
For Each Customer In DOM.DocumentElement.getElementsByTagName("CustomerId")
'Since Indicators is Customer nextSibling, and Customer is parent of CustomerId,'
' we can iterate the collection if its childNodes like this:'
For Each itm In Customer.ParentNode.NextSibling.ChildNodes
If itm.HasChildNodes Then
'# Insert this node before the first child node of Indicators'
itm.InsertBefore Customer.CloneNode(True), itm.FirstChild
Else
'# Append this node to the Indicators'
itm.appendChild Customer.CloneNode(True)
End If
Next
Next
其中customer
是XML节点,而Transactions是它的第一个兄弟。参考示例XML Customer是实体,Transactions是Items。
假设我想要到达第二个兄弟姐妹。我希望路径将是(考虑上面的示例代码)`Customer.ParentNode.NextSibling.NextSibling.ChildNodes。但是,如果我尝试这个,我收到一个“找不到对象”错误,树看起来像这样:
注意:在上面的两张图片中,我已从中间列中删除了信息,以便某些节点的值保密。
核心问题是'验证'实际上不是交易的下一个兄弟。指标是交易的下一个兄弟,在示例XML中扮演<Contents>
的角色。
再次为了使这个帖子自成一体,我使用的代码如下:
Option Explicit
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Sub ParseResults()
'Most up-to-date copy of XML Parsing Macro
'Requires reference to Microsoft XML, v6.0
'Requires referenc to Microsoft Scripting Runtime
Dim xmlFilePath$, newFilePath$
Dim DOM As MSXML2.DOMDocument
Dim Customer As IXMLDOMNode
Dim fso As Scripting.FileSystemObject
Dim itm As IXMLDOMNode
'# Define the file you are going to load as XML
xmlFilePath = "C:\FAKEPATH\FAKEFILE.xml"
'# Define an output path for where to put the modified XML
newFilePath = "C:\FAKEPATH\FAKEFILE.xml"
'# Create our DOM object
Set DOM = CreateObject("MSXML2.DOMDocument.6.0")
'# Load the XML file
DOM.Load xmlFilePath
'# Wait until the Document has loaded
Do
Sleep 250
Loop Until DOM.readyState = 4
'##### NO LONGER USED:'
'# Get the CustomerId node'
'Set Customer = DOM.DocumentElement.getElementsByTagName("CustomerId")(0)'
'# Instead of getting the first Transaction like we did before, we can iterate the collection'
' of nodes with the CustomerId tag like so:'
For Each Customer In DOM.DocumentElement.getElementsByTagName("CustomerId")
'Since Transaction is Customer nextSibling, and Customer is parent of CustomerId,'
' we can iterate the collection if its childNodes like this:'
For Each itm In Customer.ParentNode.NextSibling.ChildNodes
If itm.HasChildNodes Then
'# Insert this node before the first child node of Transaction'
itm.InsertBefore Customer.CloneNode(True), itm.FirstChild
Else
itm.appendChild Customer.CloneNode(True)
'# Append this node to the Transaction'
End If
Next
Next
'##### This function call is no longer needed
'AppendCustomer DOM, "Transaction", Customer'
'##### This function call is no longer needed
'AppendCustomer DOM, "Transaction", Customer'
'## Create an FSO to write the new file
Set fso = CreateObject("Scripting.FileSystemObject")
'## Attempt to write the new/modified XML to file
On Error Resume Next
'MsgBox DOM.XML
fso.CreateTextFile(newFilePath, True, False).Write DOM.XML
If Err Then
'## Print the new XML in the Immediate window
Debug.Print DOM.XML
MsgBox "Unable to write to " & newFilePath & " please review XML in the Immediate window in VBE.", vbInformation
Err.Clear
End If
On Error GoTo 0
'Cleanup
Set DOM = Nothing
Set fso = Nothing
Set Customer = Nothing
End Sub
Sub AppendCustomer(DOM As Object, Transaction As String, copyNode As Object)
'## This subroutine will append child node to ALL XML Nodes matching specific string tag.
Dim TransactionColl As IXMLDOMNodeList
Dim itm As IXMLDOMNode
'# Get a collection of all elements matching the tagName
Set TransactionColl = DOM.DocumentElement.getElementsByTagName(Transaction)
'# Iterate over the collection, appending the copied node
For Each itm In TransactionColl
If itm.HasChildNodes Then
'# Insert this node before the first child node of Transaction
itm.InsertBefore copyNode.CloneNode(True), itm.FirstChild
Else
'# Append this node to the Transaction
itm.appendChild copyNode.CloneNode(True)
End If
Next
Set itm = Nothing
Set TransactionColl = Nothing
End Sub
答案 0 :(得分:1)
假设您的xml看起来像这样(为了清楚起见,省略了xml命名空间和内容):
<root>
<Customer>
<child1>asdf</child1>
<child2>sdfg</child2>
...
</Customer>
<Sibling1>
...
</Sibling1>
<Sibling2>
...
</Sibling2>
<Sibling3>
...
</Sibling3>
</root>
然后Customer.NextSibling
将是Sibling1,Customer.NextSibling.NextSibling
将是Sibling2,依此类推。
使用ParentNode
访问Sibling1的唯一原因是当前节点是child1
Customer
。然后,您可以使用Sibling1
引用child1.ParentNode.NextSibling.ChildNodes
的孩子。