title: With Microsoft Xml Parser MSXML2 how to retrieve value of an attribute using XPath?
body: <p>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.  </p>
  
  <p>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.  </p>
  
  <p>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.</p>
  
  <p>Here is some experimental code.  It runs in Excel VBA, you'll need to set a Tools->Reference </p>
  
  <p>You can see at the bottom with my attempts commented out what I have tried.  Any clever people know the answer?</p>
  
  <p><strong>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.</strong> </p>
  
  <p>To moderators, please don't dismiss this as a duplicate because I think I've discovered a new angle on this.</p>
  
  <p>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<br>
  <a href="https://stackoverflow.com/questions/4531995/getting-attribute-using-xpath">Getting attribute using XPath</a><br>
  <a href="https://stackoverflow.com/questions/4835891/how-to-extract-attribute-s-value-through-xpath">Extract value of attribute node via XPath</a><br>
  <a href="https://stackoverflow.com/questions/13194183/how-to-get-attribute-value-from-node-using-xpath">How to get attribute value from node using xpath?</a><br></p>
  
  <pre class="lang-vb prettyprint-override"><code>Option Explicit
  
  
  Sub Test()
      '* Requires Tools-&gt;References-&gt; 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 = _
          "&lt;a&gt;" &amp; _
              "&lt;b&gt;1stbText" &amp; _
              "&lt;/b&gt;" &amp; _
              "&lt;b&gt;2ndbText" &amp; _
              "&lt;/b&gt;" &amp; _
              "&lt;c id='5'&gt;cText" &amp; _
              "&lt;/c&gt;" &amp; _
          "&lt;/a&gt;"
  
      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. --&gt;string(/a/c/@id)&lt;--"
  
  
      Debug.Assert Not xmlCIDTextNodes Is Nothing
      Debug.Assert xmlCIDTextNodes.Length = 1  '&lt;========stops here because of XPath not working
  
      Debug.Assert xmlCIDTextNodes(0).xml = "5"  '&lt;== 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
  </code></pre>