Как я могу улучшить скорость синтаксического анализа XML в VBA - PullRequest
3 голосов
/ 11 апреля 2011

У меня большой XML-файл, который необходимо проанализировать в VBA (Excel 2003 и 2007).В XML-файле может быть более 11 000 «строк» ​​данных, причем каждая «строка» содержит от 10 до 20 «столбцов».Это в конечном итоге является огромной задачей, просто разобрать и получить данные (5-7 минут).Я попытался прочитать xml и поместить каждую «строку» в словарь (ключ = номер строки, значение = атрибуты строки), но это занимает столько же времени.Есть ли более эффективный способ?

Dim XMLDict
    Sub ParseXML(ByRef RootNode As IXMLDOMNode)
        Dim Counter As Long
        Dim RowList As IXMLDOMNodeList
        Dim ColumnList As IXMLDOMNodeList
        Dim RowNode As IXMLDOMNode
        Dim ColumnNode As IXMLDOMNode
        Counter = 1
        Set RowList = RootNode.SelectNodes("Row")

        For Each RowNode In RowList
            Set ColumnList = RowNode.SelectNodes("Col")
            Dim NodeValues As String
            For Each ColumnNode In ColumnList
                NodeValues = NodeValues & "|" & ColumnNode.Attributes.getNamedItem("id").Text & ":" & ColumnNode.Text
            Next ColumnNode
            XMLDICT.Add Counter, NodeValues
            Counter = Counter + 1
        Next RowNode
    End Sub

Ответы [ 2 ]

5 голосов
/ 12 апреля 2011

Вы можете попробовать использовать SAX вместо DOM.SAX должен работать быстрее, когда все, что вы делаете, это анализ документа, а документ имеет нетривиальный размер.Ссылка для реализации SAX2 в MSXML: здесь

Я обычно обращаюсь непосредственно к DOM для большинства операций синтаксического анализа XML в Excel, но SAX, кажется, имеет преимущества в некоторых ситуациях.Краткое сравнение здесь может помочь объяснить различия между ними.

Вот взломанный пример (частично основанный на this ), просто использующий Debug.Print для вывода:

Добавьте ссылку на «Microsoft XML, v6.0» через Сервис> Ссылки

Добавьте этот код в обычный модуль

Option Explicit

Sub main()

Dim saxReader As SAXXMLReader60
Dim saxhandler As ContentHandlerImpl

Set saxReader = New SAXXMLReader60
Set saxhandler = New ContentHandlerImpl

Set saxReader.contentHandler = saxhandler
saxReader.parseURL "file://C:\Users\foo\Desktop\bar.xml"

Set saxReader = Nothing

End Sub

Добавьте модуль класса,назовите его ContentHandlerImpl и добавьте следующий код

Option Explicit

Implements IVBSAXContentHandler

Private lCounter As Long
Private sNodeValues As String
Private bGetChars As Boolean

. Используйте раскрывающийся список слева вверху модуля, чтобы выбрать «IVBSAXContentHandler», а затем используйте раскрывающийся список справа, чтобы добавитьзаглушки для каждого события по очереди (от characters до startPrefixMapping)

Добавьте код для некоторых заглушек следующим образом

Явно установите счетчик и флаг, чтобы показать, если мы хотимдля чтения текстовых данных в это время

Private Sub IVBSAXContentHandler_startDocument()

lCounter = 0
bGetChars = False

End Sub

Каждый раз, когда запускается новый элемент, проверяйте имя элемента и предпринимайте соответствующие действия

Private Sub IVBSAXContentHandler_startElement(strNamespaceURI As String, strLocalName As String, strQName As String, ByVal oAttributes As MSXML2.IVBSAXAttributes)

Select Case strLocalName
    Case "Row"
        sNodeValues = ""
    Case "Col"
        sNodeValues = sNodeValues & "|" & oAttributes.getValueFromName(strNamespaceURI, "id") & ":"
        bGetChars = True
    Case Else
        ' do nothing
End Select

End Sub

Проверьте, не интересует ли настекстовые данные и, если мы, отрубить любой посторонний пробелd удалите все переводы строк (это может или не может быть желательно в зависимости от документа, который вы пытаетесь проанализировать)

Private Sub IVBSAXContentHandler_characters(strChars As String)

If (bGetChars) Then
    sNodeValues = sNodeValues & Replace(Trim$(strChars), vbLf, "")
End If

End Sub

Если мы достигли конца Col, тогда прекратите чтение текстовых значений;если мы достигли конца Row, то распечатаем строку значений узлов

Private Sub IVBSAXContentHandler_endElement(strNamespaceURI As String, strLocalName As String, strQName As String)

Select Case strLocalName
    Case "Col"
        bGetChars = False
    Case "Row"
        lCounter = lCounter + 1
        Debug.Print lCounter & " " & sNodeValues
    Case Else
        ' do nothing
End Select

End Sub

Чтобы прояснить ситуацию, вот полная версия ContentHandlerImpl со всеми заглушкамиметоды на месте:

Option Explicit

Implements IVBSAXContentHandler

Private lCounter As Long
Private sNodeValues As String
Private bGetChars As Boolean

Private Sub IVBSAXContentHandler_characters(strChars As String)

If (bGetChars) Then
    sNodeValues = sNodeValues & Replace(Trim$(strChars), vbLf, "")
End If

End Sub

Private Property Set IVBSAXContentHandler_documentLocator(ByVal RHS As MSXML2.IVBSAXLocator)

End Property

Private Sub IVBSAXContentHandler_endDocument()

End Sub

Private Sub IVBSAXContentHandler_endElement(strNamespaceURI As String, strLocalName As String, strQName As String)

Select Case strLocalName
    Case "Col"
        bGetChars = False
    Case "Row"
        lCounter = lCounter + 1
        Debug.Print lCounter & " " & sNodeValues
    Case Else
        ' do nothing
End Select

End Sub

Private Sub IVBSAXContentHandler_endPrefixMapping(strPrefix As String)

End Sub

Private Sub IVBSAXContentHandler_ignorableWhitespace(strChars As String)

End Sub

Private Sub IVBSAXContentHandler_processingInstruction(strTarget As String, strData As String)

End Sub

Private Sub IVBSAXContentHandler_skippedEntity(strName As String)

End Sub

Private Sub IVBSAXContentHandler_startDocument()

lCounter = 0
bGetChars = False

End Sub

Private Sub IVBSAXContentHandler_startElement(strNamespaceURI As String, strLocalName As String, strQName As String, ByVal oAttributes As MSXML2.IVBSAXAttributes)

Select Case strLocalName
    Case "Row"
        sNodeValues = ""
    Case "Col"
        sNodeValues = sNodeValues & "|" & oAttributes.getValueFromName(strNamespaceURI, "id") & ":"
        bGetChars = True
    Case Else
        ' do nothing
End Select

End Sub

Private Sub IVBSAXContentHandler_startPrefixMapping(strPrefix As String, strURI As String)

End Sub
0 голосов
/ 11 апреля 2011

Используйте функцию SelectSingleNode. Это позволит вам искать узел на основе сопоставления с шаблоном.

Например, я создал следующую функцию:

Private Function getXMLNodeValue(ByRef xmlDoc As MSXML2.DOMDocument, ByVal xmlPath As String)
    Dim node As IXMLDOMNode
    Set node = xmlDoc.SelectSingleNode(xmlPath)
    If node Is Nothing Then getXMLNodeValue = vbNullString Else getXMLNodeValue = node.Text
End Function

Теперь, если у меня есть следующий XML-файл: An XML Response

Я могу просто позвонить:

myValue = getXMLNodeValue(xmlResult, "//ErrorStatus/Source")

и он перейдет к первому ключу, называемому «Error Status» на любой глубине, и вытянет текст в узле «Source» - возвращая «INTEGRATION»

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...