Reputation: 8260
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
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