Вы можете использовать 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 см. Вопрос 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
Это именно то, как я думал, что я мог бы сделать это. Приятно, чтобы это было подтверждено так быстро, и очень приятный сюрприз для реализации на блюде :-) – JasonPlutext
@plutext: проблем нет. Это был интересный вопрос и что-то, что я мог бы использовать в предстоящем проекте, поэтому я подумал, что напишу какой-нибудь код, чтобы убедиться, что это выполнимо. :-) –
+1 но с небольшой осторожностью при использовании 'DOMDocument', а не' DOMDocument60'. В этом контексте 'DOMDocument' почти наверняка равен' DOMDocument30' - см. Http://msdn.microsoft.com/en-us/library/ms757837% 28v = VS.85% 29.aspx - см. также http://stackoverflow.com/questions/951804/which-version-of-msxml-should-i-use – barrowc