XPath возвращает строку или логическое значение в VBA? - PullRequest
2 голосов
/ 07 ноября 2010

Какой самый простой способ для макроса Word выполнить выражения XPath, такие как:

"string(/alpha/beta)" 

"not(string(/alpha/beta)='true')" 

, которые должны возвращать соответственно строку и логическое значение?(в отличие от узла xml или списка узлов)

Я хочу избегать библиотек DLL, которые еще не присутствуют на компьютере под управлением Office 2007 или 2010.

Функция selectSingleNode (queryString As String) возвращает IXMLDOMNode, так что это не сработает.

Другими словами, что-то похожее на .NET xpathnavigator.evaluate [1], что делает это?

[1] http://msdn.microsoft.com/en-us/library/2c16b7x8.aspx

1 Ответ

3 голосов
/ 07 ноября 2010

Вы можете использовать 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
...