您可以使用 XSL 转换来评估 XPath 表达式,特别是 xsl:value-of。
我写了一个Evaluate 函数,就是按照这个原理工作的。它在内存中创建一个 XSL 样式表,其中包含一个 XSL 模板,该模板将采用 XPath 表达式,对其进行评估,并返回一个新的 XML 文档,该文档包含<result> 节点中的结果。它检查以确保 value-of 返回了某些内容(如果没有返回则抛出错误),如果是,则将 XPath 表达式的结果转换为以下数据类型之一:Long、Double、@987654331 @,或String。
以下是我用来练习代码的一些测试。我使用了您链接到的 MSDN 页面中的 books.xml 文件(如果要运行这些测试,您必须将路径更改为 books.xml)。
Public Sub Test_Evaluate()
Dim doc As New DOMDocument
Dim value As Variant
doc.async = False
doc.Load "C:\Development\StackOverflow\XPath Evaluation\books.xml"
Debug.Assert (doc.parseError.errorCode = 0)
' Sum of book prices should be a Double and equal to 30.97
'
value = Evaluate(doc, "sum(descendant::price)")
Debug.Assert TypeName(value) = "Double"
Debug.Assert value = 30.97
' Title of second book using text() selector should be "The Confidence Man"
'
value = Evaluate(doc, "descendant::book[2]/title/text()")
Debug.Assert TypeName(value) = "String"
Debug.Assert value = "The Confidence Man"
' Title of second book using string() function should be "The Confidence Man"
'
value = Evaluate(doc, "string(/bookstore/book[2]/title)")
Debug.Assert TypeName(value) = "String"
Debug.Assert value = "The Confidence Man"
' Total number of books should be 3
'
value = Evaluate(doc, "count(descendant::book)")
Debug.Assert TypeName(value) = "Long"
Debug.Assert value = 3
' Title of first book should not be "The Great Gatsby"
'
value = Evaluate(doc, "not(string(/bookstore/book[1]/title))='The Great Gatsby'")
Debug.Assert TypeName(value) = "Boolean"
Debug.Assert value = False
' Genre of second book should be "novel"
'
value = Evaluate(doc, "string(/bookstore/book[2]/attribute::genre)='novel'")
Debug.Assert TypeName(value) = "Boolean"
Debug.Assert value = True
' Selecting a non-existent node should generate an error
'
On Error Resume Next
value = Evaluate(doc, "string(/bookstore/paperback[1])")
Debug.Assert Err.Number = vbObjectError
On Error GoTo 0
End Sub
这里是Evaluate 函数的代码(IsLong 函数是一个帮助函数,使数据类型转换代码更具可读性):
注意: 正如 barrowc 在 cmets 中提到的那样,您可以通过将 DOMDocument 替换为特定于版本的类名来明确说明要使用的 MSXML 版本,例如DOMDocument30 (MSXML3) 或DOMDocument60 (MSXML6)。编写的代码将默认使用 MSXML3,它目前部署更广泛,但 MSXML6 具有更好的性能,并且是最新版本,是微软目前推荐的版本。
有关不同版本的 MSXML 的更多信息,请参阅问题 Which version of MSXML should I use?。
Public Function Evaluate(ByVal doc As DOMDocument, ByVal xpath As String) As Variant
Static styleDoc As DOMDocument
Dim valueOf As IXMLDOMElement
Dim resultDoc As DOMDocument
Dim result As Variant
If styleDoc Is Nothing Then
Set styleDoc = New DOMDocument
styleDoc.loadXML _
"<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>" & _
"<xsl:template match='/'>" & _
"<result>" & _
"<xsl:value-of />" & _
"</result>" & _
"</xsl:template>" & _
"</xsl:stylesheet>"
End If
Set valueOf = styleDoc.selectSingleNode("//xsl:value-of")
valueOf.setAttribute "select", xpath
Set resultDoc = New DOMDocument
doc.transformNodeToObject styleDoc, resultDoc
If resultDoc.documentElement.childNodes.length = 0 Then
Err.Raise vbObjectError, , "Expression '" & xpath & "' returned no results."
End If
result = resultDoc.documentElement.Text
If IsLong(result) Then
result = CLng(result)
ElseIf IsNumeric(result) Then
result = CDbl(result)
ElseIf result = "true" Or result = "false" Then
result = CBool(result)
End If
Evaluate = result
End Function
Private Function IsLong(ByVal value As Variant) As Boolean
Dim temp As Long
If Not IsNumeric(value) Then
Exit Function
End If
On Error Resume Next
temp = CLng(value)
If Not Err.Number Then
IsLong = (temp = CDbl(value))
End If
End Function