1

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
Community
  • 1
  • 1
S Meaden
  • 8,050
  • 3
  • 34
  • 65
  • What's the possible duplicate you are mentioning in the question? By the way: Anyone above a certain reputation can close questions, not just moderators. – Mathias Müller Mar 05 '15 at 22:16
  • @Mathias: I have amended text of question to give related articles but they don;t work with MSXML2.SelectNodes. – S Meaden Mar 06 '15 at 09:18
  • Why are selecting the text as a node, try removing the '()' from your selector i.e. `"/a/b/text()"` to `"/a/b/text"` – tbc Mar 24 '15 at 09:30
  • I've tried removing (), it is one of the commented out lines that doesn't work. As regards to why select as a node, answer is I want to pass in an Xpath expression from user and not require special handling. Everything ought to be able to fit into a node, see IXMLDOMText for text node. SelectNodes() requires polymorphism and that everything returns a node. – S Meaden Mar 24 '15 at 12:40

1 Answers1

1
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
Tim Williams
  • 154,628
  • 8
  • 97
  • 125
  • Well I already have that as a part solution see TestCID. I suppose I need to tighten the question. I want to be able to call the .xml method on the node and get the value of the attribute, presently doing so give key and value pair instead of just value. – S Meaden Mar 06 '15 at 09:07
  • Sorry - maybe too much text in your question, and it would be useful to skip the Debug.Assert and just state the outcome of each line, otherwise it's impossible to just read the question and figure out what works or doesn't work. I'm still not clear on what the issue is here though. – Tim Williams Mar 06 '15 at 17:19
  • The Debug.Assert very clearly state the outcomes expected, that is indeed the purpose of Debug.Assert. One is meant to open up Excel VBA and paste the code in. Sorry, if you were just reading the code from text. – S Meaden Mar 24 '15 at 12:44
  • 1
    I used this extensively years ago. You can't use the .xml property and get the just the value. You can use SelectSingleNode to get an IXmlAttribute - you then use that object reference in your program. If you're iterating over mixed object types, you'll have to type check i.e. if TypeName(node) = "XmlAttribute" then ... – Mark Rabjohn Mar 24 '15 at 16:10