Search code examples
xmlvbams-accessmsxml2

MSXML2: How to update (+insert and delete) node using XPATH


I need help with updating a particular node of the XML file. I use the MSXML2.DOMDocument60. This code demonstrates the idea:

Option Explicit

Sub UpdateXML()
    'Load the XML file into oDoc
    Dim oDoc As MSXML2.DOMDocument60
    Set oDoc = New MSXML2.DOMDocument60
    If Not oDoc.Load("C:\1\test.xml") Then
        Debug.Print oDoc.parseError
        Exit Sub
    End If
    
    Dim xPath As String
    'Lets say I want to update this node:
    xPath = "/root/devices/device[@name='DB']/package[@name='DIL8']/technologies"
    
    Dim sNode As IXMLDOMNode
    'I know how to select it
    Set sNode = oDoc.selectSingleNode(xPath)
    
    If Not sNode Is Nothing Then
        Debug.Print sNode.XML
        
        'This function returns the node with new data (function follows)
        Debug.Print getTechnologies.XML
        
        'Here I need to insert the data returned by getTechnologies()
        'into the correct place of the oDoc (specified by xPath)
        
        'this does not work
        oDoc.selectSingleNode(xPath) = getTechnologies
        '??? odoc.replaceChild ??? I'm lost here
         
        'It would be great to see the example how to insert and delete
        'the node <technologies> of the oDoc using xPath... if it is possible of course.
        
    End If
    
    'Save modified data into new file
    oDoc.Save "C:\1\final.xml"
    
    'Final file should now contain "newValue" within the
    '/root/devices/device[@name='DB']/package[@name='DIL8']/technologies
End Sub

Function getTechnologies() As IXMLDOMNode
    'This is just a simplified example to demonstrate the function
    'that returns the IXMLDOMNode object
    'In real, this function pulls data from a database
    
    Dim oNode As MSXML2.DOMDocument60
    Set oNode = New MSXML2.DOMDocument60
    Dim sXml As String
    sXml = "<technologies>" & vbCrLf & _
           "    <property name='prop1' value='newValue'/>" & vbCrLf & _
           "    <property name='prop2' value='newValue'/>" & vbCrLf & _
           "    <property name='prop3' value='newValue'/>" & vbCrLf & _
           "    <property name='prop4' value='newValue'/>" & vbCrLf & _
           "</technologies>"
    If Not oNode.loadXML(sXml) Then
        Debug.Print oNode.parseError
    Else
        Set getTechnologies = oNode.selectSingleNode("/technologies")
    End If
End Function

Here's the file test.xml that I use in my example. It is a simplified version of the real file:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <library>
        <items>
            <item name="foo"/>
            <item name="bar"/>
            <item name="foo2"/>
            <item name="bar2"/>
        </items>
    </library>
    <devices>
        <device name="DB">
            <package name="DIL4">
                <something>Another tree could be here</something>
                <technologies>
                    <property name="prop1" value="oldValue"/>
                    <property name="prop2" value="oldValue"/>
                    <property name="prop3" value="oldValue"/>
                    <property name="prop4" value="oldValue"/>
                </technologies>
            </package>
            <package name="DIL8">
                <technologies>
                    <property name="prop1" value="oldValue"/>
                    <property name="prop2" value="oldValue"/>
                    <property name="prop3" value="oldValue"/>
                    <property name="prop4" value="oldValue"/>
                </technologies>
                <something>The order is not guaranteed</something>
            </package>
            <package name="DIL16">
                <technologies>
                    <property name="prop1" value="oldValue"/>
                    <property name="prop2" value="oldValue"/>
                    <property name="prop3" value="oldValue"/>
                    <property name="prop4" value="oldValue"/>
                </technologies>
            </package>
        </device>
        <device name="NPN">
            <package name="SOT23">
                <technologies>
                    <property name="prop1" value="oldValue"/>
                    <property name="prop2" value="oldValue"/>
                    <property name="prop3" value="oldValue"/>
                    <property name="prop4" value="oldValue"/>
                </technologies>
            </package>
        </device>
    </devices>
</root>

EDIT: Below is the code of this answer but I don't understand how modifying the xmlRoot can afect the xmlDoc - is it byRef? (see the notes in the code)

Sub XMLTest()
Dim myVar As String, pathToXML As String
Dim xmlDoc As Object, xmlRoot As Object
    Set xmlDoc = CreateObject("MSXML2.DOMDocument")
    pathToXML = "N:\example.xml" '<--- change the path
    Call xmlDoc.Load(pathToXML)
    Set xmlRoot = xmlDoc.getElementsByTagName("RefTest").Item(0)
    myVar = "foobar" '<--- your value

    'Here the xmlRoot object is updated
    xmlRoot.selectSingleNode("iRef5").Text = myVar
    
    'Here the xmlDoc is saved
    Call xmlDoc.Save(pathToXML)
End Sub

I feel like the answer is right in front of my eyes, but I can't see it


Solution

  • The whole problem was that I thought that the oDoc and sNode objects were independent of each other. I didn't realize that sNode is an active reference to the oDoc node. Thanks to the June7 answer, I understood how it works and then it only took a moment to find the answer to all 3 questions that I originally had in mind:

    Sub UpdateXML()
        'Load the XML file into oDoc
        Dim oDoc As MSXML2.DOMDocument60
        Set oDoc = New MSXML2.DOMDocument60
        If Not oDoc.Load("C:\1\test.xml") Then
            Debug.Print oDoc.parseError
            Exit Sub
        End If
    
        Dim xPath As String
        'Lets say I want to update this node:
        xPath = "/root/devices/device[@name='DB']/package[@name='DIL8']/technologies"
    
        Dim sNode As IXMLDOMNode         'Reference to oDoc node
        Set sNode = oDoc.selectSingleNode(xPath)
        If Not sNode Is Nothing Then     'If node is found then
        
            Dim nTech As IXMLDOMNode
            Set nTech = getTechnologies  'Get node from function
        
            'To update selected node
            sNode.parentNode.replaceChild nTech, sNode
            
            'To remove node (Reference to sNode must be set again after replaceChild method)
            Set sNode = oDoc.selectSingleNode(xPath)
            sNode.parentNode.removeChild sNode
            
            'To insert new node into a particular node
            xPath = "/root/devices/device[@name='DB']/package[@name='DIL8']"
            Set sNode = oDoc.selectSingleNode(xPath)
            sNode.appendChild nTech
    
        End If
        'Save modified data into new file
        oDoc.Save "C:\1\final.xml"
    End Sub