MSXML2: как обновить (+вставить и удалить) узел с помощью XPATH

Мне нужна помощь в обновлении определенного узла файла XML. Я использую MSXML2.DOMDocument60. Этот код демонстрирует идею:

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

Вот файл test.xml, который я использую в своем примере. Это упрощенная версия реального файла:

<?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: Ниже приведен код этот ответ, но я не понимаю, как изменение xmlRoot может повлиять на xmlDoc - это byRef? (см. примечания в коде)

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

Я чувствую, что ответ прямо перед моими глазами, но я не вижу его


person Combinatix    schedule 17.01.2021    source источник
comment
Разве вы не можете просто напрямую назначить новый xml целевому узлу? У вас есть ссылка на него, когда вы устанавливаете его в переменной.   -  person QHarr    schedule 18.01.2021
comment
Отвечает ли это на ваш вопрос? Могу ли я обновить значение узла XML, используя переменная в VBA   -  person June7    schedule 18.01.2021
comment
Просмотрите stackoverflow.com/questions/5506548/ и stackoverflow.com/questions/38104423/   -  person June7    schedule 18.01.2021
comment
@June7 В этих статьях используется цикл For Each, а это не совсем то, что мне нужно. Я считаю, что должна быть возможность вставлять/редактировать/удалять узел напрямую с помощью XPATH. Я расширил свой вопрос - см. РЕДАКТИРОВАТЬ   -  person Combinatix    schedule 18.01.2021
comment
Просмотрите этот forums.asp.net/t/   -  person June7    schedule 18.01.2021


Ответы (2)


Простой xml-файл:

<?xml version="1.0"?>
<!-- This file represents a fragment of a bookstore inventory database -->
<bookstore specialty="novel">
  <book>
    <Title>Beginning XML</Title>
    <Publisher>Wrox</Publisher>
  </book>
  <book>
    <Title>Professional XML</Title>
    <Publisher>Wrox</Publisher>
  </book>
  <book>
    <Title>Programming ADO</Title>
    <author>
      <first-name>Mary</first-name>
      <last-name>Jones</last-name>      
    </author>
    <datePublished>1/1/2000</datePublished>
    <Publisher>Microsoft Press</Publisher>
  </book>
</bookstore>

Следующее помогло мне отредактировать один узел.

Dim oDoc As MSXML2.DOMDocument60, sNode As MSXML2.IXMLDOMNode
Set oDoc = New MSXML2.DOMDocument60
oDoc.Load "path\filename"
Set sNode = oDoc.SelectSingleNode("//book[3]/Title") 'selection criteria using XPath syntax
sNode.Text = "something"
oDoc.Save("path\filename") 'If same path\filename is used, it will overwrite.
person June7    schedule 18.01.2021
comment
Хорошо, но почему обновление sNode изменяет oDoc? Что связывает эти объекты? Это по ссылке? Извините, если это глупый вопрос - person Combinatix; 18.01.2021
comment
oDoc является родителем sNode, как определено: Set sNode = oDoc. .... sNode является частью oDoc. Вот почему oDoc должен быть установлен и загружен первым. Это лучшее, что я могу объяснить. - person June7; 18.01.2021
comment
Другими словами: sNode — это ссылка на часть oDoc, выбранную xPath, таким образом, доступ к свойствам sNode фактически изменяет oDoc. Верный? (Я не являюсь носителем английского языка, поэтому это займет некоторое время;) - person Combinatix; 18.01.2021
comment
Это, кажется, покрывает это. - person June7; 18.01.2021

Вся проблема была в том, что я думал, что объекты oDoc и sNode не зависят друг от друга. Я не понимал, что sNode является активной ссылкой на узел oDoc. Благодаря ответу от 7 июня я понял, как это работает, и затем мне потребовалось всего несколько минут, чтобы найти ответ на все 3 вопроса, которые я изначально имел в виду:

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
person Community    schedule 18.01.2021
comment
Можно пометить ответ как принятый, даже если он ваш. - person June7; 20.01.2021