Вы можете использовать XSL-преобразование для оценки выражений XPath, в частности xsl: value-of .
Я написал Evaluate
функцию, которая работает по этому принципу. Он создает в памяти таблицу стилей XSL, которая содержит шаблон XSL, который будет принимать выражение XPath, оценивать его и возвращать новый документ XML, содержащий результат в узле <result>
. Он проверяет, что value-of
что-то вернул (и выдает ошибку, если нет), и, если это так, преобразует результат выражения XPath в один из следующих типов данных: Long
, Double
, Boolean
или String
.
Вот несколько тестов, которые я использовал для проверки кода. Я использовал файл books.xml
со страницы MSDN, на которую вы ссылались (вам нужно изменить путь на 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 , вы можете четко указать, какую версию MSXML вы хотите использовать, заменив DOMDocument
на версию, специфичную для конкретной версии. имя класса, например DOMDocument30
(MSXML3) или DOMDocument60
(MSXML6). В написанном коде по умолчанию будет использоваться MSXML3, который в настоящее время используется более широко, но MSXML6 обладает более высокой производительностью и является последней версией, которую Microsoft рекомендует в настоящее время.
См. Вопрос Какую версию MSXML мне следует использовать? для получения дополнительной информации о различных версиях MSXML.
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