Reputation: 926
I have an XML program found here that takes a specific child node of one tag and place it as a child of another tag. Part of the code involves telling VBA how to traverse the tree, which makes perfect sense. The problem is that when I actually open up the tree structure in the 'Locals' window in the Visual Basic editor I find myself unable to access the part of the tree that seems clearly visible from the actual XML file. The XML I am working with is similar to the following block:
<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>
The inclusion of both Items and Contents is just to try to clarify that there are multiple blocks below the block. Here 'entity' is Customer, Item is Transaction, and Contents is Indicators.
For example, the node to be copied is a child of 'Customer'. Customer has several siblings, call them Sibling1
= Transactions
= Items
,Sibling2
= Indicators
= Contents
, and Sibling3
= Validation
(not present in the example XML). When I open up the locals window I start at the child node of Customer. I would like to access the children of Sibling1. To do this I use the route Customer.ParentNode.NextSibling.ChildNodes
and the code runs fine. However if I change this to Customer.ParentNode.NextSibling.NextSibling.ChildNodes
VBA suggests that Sibling3 is in fact Sibling2. That is, following the path shows Validation
and not Indicators
. However, looking at the actual XML file it does seem clear that Indicators comes after Transactions
. Is there any clear reason why this might be?
EDIT: Attached below is a picture of the tree structure I am referencing along with the code referencing it.
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
where customer
is an XML node and as Transactions is its first sibling. In reference to the example XML Customer is entity and Transactions is Items.
Suppose I instead want to reach the second sibling. I would expect that the path would instead be (considering the example code above) `Customer.ParentNode.NextSibling.NextSibling.ChildNodes. However, if I try this I receive an "object not found" error and the tree looks like this:
NOTE: In both of the images above I have removed information from the middle column just so that the values for some nodes remain confidential.
The core problem is that 'Validation' is not actually the next sibling of Transactions. Indicators is the next sibling of transactions, and takes the role of <Contents>
in the example XML.
Again in the interest of making this post self-contained, the code I am using is as follows:
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
Upvotes: 0
Views: 355
Reputation: 6120
Assuming your xml looks something like this (xml namespace and stuff omitted for clarity):
<root>
<Customer>
<child1>asdf</child1>
<child2>sdfg</child2>
...
</Customer>
<Sibling1>
...
</Sibling1>
<Sibling2>
...
</Sibling2>
<Sibling3>
...
</Sibling3>
</root>
Then Customer.NextSibling
would be Sibling1, Customer.NextSibling.NextSibling
would be Sibling2 and so on.
The only reason to use ParentNode
to get to Sibling1 is if your current node is child1
of Customer
. Then you could refer to the children of Sibling1
with child1.ParentNode.NextSibling.ChildNodes
.
Upvotes: 1