114
114

Reputation: 926

XML tree in local window does not match XML in file

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.

enter image description here

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:

enter image description here

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

Answers (1)

Blackhawk
Blackhawk

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

Related Questions