S Meaden
S Meaden

Reputation: 8260

With Microsoft Xml Parser MSXML2 how to retrieve value of an attribute using XPath?

This is another go at the question how to get the value of an attribute. I know that there are restrictions when using Microsoft's MSXML2 SelectNodes and SelectSingleNode methods in that they must return a node set.

I had thought that using text() at the end of an expression did not return a node set but then I discovered the type IXMLDOMText which can be seen to be a child node of a IXMLDOMAttribute type which made me wonder if one really could use just XPath to dig out an attribute's value.

IXMLDOMText is a node and not a literal so (in theory) it could participate in a node set, i.e. a node set can comprise of one IXMLDOMText.

Here is some experimental code. It runs in Excel VBA, you'll need to set a Tools->Reference

You can see at the bottom with my attempts commented out what I have tried. Any clever people know the answer?

I want to be able to call the .xml method on the node and get the value of the attribute, presently doing so gives key and value pair instead of just value.

To moderators, please don't dismiss this as a duplicate because I think I've discovered a new angle on this.

The following SO articles all suggest wrapping the Xpath in string() but that does not work for MSXML2.SelectNodes because it must return a nodeset
Getting attribute using XPath
Extract value of attribute node via XPath
How to get attribute value from node using xpath?

Option Explicit


Sub Test()
    '* Requires Tools->References-> Microsoft XML, v6.0


    Dim doc As MSXML2.DOMDocument60
    Set doc = New MSXML2.DOMDocument60

    '* THE OBJECTIVE IS TO INVENT AN XPATH THAT RETRIEVES THE VALUE OF AN ATTRIBUTE USING SOMETHING LIKE /a/c/@id/text()

    Dim s As String
    s = _
        "<a>" & _
            "<b>1stbText" & _
            "</b>" & _
            "<b>2ndbText" & _
            "</b>" & _
            "<c id='5'>cText" & _
            "</c>" & _
        "</a>"

    doc.LoadXML s
    Debug.Assert doc.parseError = 0

    TestB doc
    TestA doc
    TestC doc
    TestCID doc
    TestCIDText doc
End Sub

Sub TestB(ByVal doc As MSXML2.DOMDocument60)
    Dim xmlBTextNodes As MSXML2.IXMLDOMNodeList
    Set xmlBTextNodes = doc.SelectNodes("/a/b/text()")
    Debug.Assert Not xmlBTextNodes Is Nothing
    Debug.Assert xmlBTextNodes.Length = 2
    Debug.Assert xmlBTextNodes(0).Text = "1stbText"
    Debug.Assert xmlBTextNodes(1).Text = "2ndbText"

    Debug.Assert TypeName(xmlBTextNodes(0)) = "IXMLDOMText"

    Dim xmlCastToText As MSXML2.IXMLDOMText
    Set xmlCastToText = xmlBTextNodes(0)
    Debug.Assert xmlCastToText.xml = "1stbText"
    Debug.Assert xmlCastToText.Text = "1stbText"
End Sub

Sub TestA(ByVal doc As MSXML2.DOMDocument60)
    Dim xmlATextNodes As MSXML2.IXMLDOMNodeList
    Set xmlATextNodes = doc.SelectNodes("/a/text()")
    Debug.Assert Not xmlATextNodes Is Nothing
    Debug.Assert xmlATextNodes.Length = 0  '* interesting
End Sub

Sub TestC(ByVal doc As MSXML2.DOMDocument60)
    Dim xmlCTextNodes As MSXML2.IXMLDOMNodeList
    Set xmlCTextNodes = doc.SelectNodes("/a/c/text()")
    Debug.Assert Not xmlCTextNodes Is Nothing
    Debug.Assert xmlCTextNodes.Length = 1
    Debug.Assert xmlCTextNodes(0).xml = "cText"
    Debug.Assert xmlCTextNodes(0).Text = "cText"
    Dim xmlCastToText As MSXML2.IXMLDOMText
    Set xmlCastToText = xmlCTextNodes(0)
    Debug.Assert xmlCastToText.xml = "cText"
    Debug.Assert xmlCastToText.Text = "cText"
End Sub



Sub TestCID(ByVal doc As MSXML2.DOMDocument60)
    Dim xmlCIDNodes As MSXML2.IXMLDOMNodeList
    Set xmlCIDNodes = doc.SelectNodes("/a/c/@id")
    Debug.Assert Not xmlCIDNodes Is Nothing
    Debug.Assert xmlCIDNodes.Length = 1
    Debug.Assert xmlCIDNodes(0).xml = "id=""5"""
    Debug.Assert xmlCIDNodes(0).Text = "5"

    Debug.Assert TypeName(xmlCIDNodes(0)) = "IXMLDOMAttribute"


    Debug.Assert xmlCIDNodes(0).ChildNodes.Length = 1

    Dim xmlCastToText As MSXML2.IXMLDOMText
    Set xmlCastToText = xmlCIDNodes(0).ChildNodes(0)
    Debug.Assert xmlCastToText.xml = "5"
    Debug.Assert xmlCastToText.Text = "5"

    Debug.Assert xmlCastToText.ChildNodes.Length = 0

End Sub

Sub t2()
    '* a convenient entry so I can press F5 and run in the same vicinity as Sub TestCIDText()
    Test
End Sub

Sub TestCIDText(ByVal doc As MSXML2.DOMDocument60)
    Dim xmlCIDTextNodes As MSXML2.IXMLDOMNodeList

    '*** crucial XPath where I'd like to retrieve the text of an attribute
    'Set xmlCIDTextNodes = doc.SelectNodes("/a/c/@id/text")  '* doesn't work, returns 0 nodes
    'Set xmlCIDTextNodes = doc.SelectNodes("/a/c/@id/text()")  '* doesn't work, returns 0 nodes
    'Set xmlCIDTextNodes = doc.SelectNodes("string(/a/c/@id)")  '* doesn't work, throws "Expression must evaluate to a node-set. -->string(/a/c/@id)<--"


    Debug.Assert Not xmlCIDTextNodes Is Nothing
    Debug.Assert xmlCIDTextNodes.Length = 1  '<========stops here because of XPath not working

    Debug.Assert xmlCIDTextNodes(0).xml = "5"  '<== we can predict these results from the tail of Sub TestCID
    Debug.Assert xmlCIDTextNodes(0).Text = "5"

    Debug.Assert xmlCIDTextNodes(0).ChildNodes.Length = 0


End Sub

Upvotes: 1

Views: 1729

Answers (1)

Tim Williams
Tim Williams

Reputation: 166221

Sub Test()
    '* Requires Tools->References-> Microsoft XML, v6.0
    Dim doc As MSXML2.DOMDocument60
    Dim nl As MSXML2.IXMLDOMNodeList
    Dim s As String, n As MSXML2.IXMLDOMNode
    Dim v


    Set doc = New MSXML2.DOMDocument60

    s = "<a>" & _
            "<b>1stbText" & _
            "</b>" & _
            "<b>2ndbText" & _
            "</b>" & _
            "<c id='5'>cText" & _
            "</c>" & _
        "</a>"

    doc.LoadXML s

    'if possibly multiple matches...
    Set nl = doc.SelectNodes("/a/c/@id")
    For Each n In nl
        Debug.Print n.NodeValue ' >> "5" 
    Next n

    'if expecting just one match...
    Set n = doc.SelectSingleNode("/a/c/@id")
    If Not n Is Nothing Then Debug.Print n.NodeValue '>> "5" 

    'or?
    Debug.Print doc.SelectSingleNode("/a/c/@id").Text

End Sub

Upvotes: 1

Related Questions