2011-04-11 5 views
3

VBA에서 구문 분석해야하는 큰 XML 파일이 있습니다 (Excel 2003 & 2007). xml 파일에는 11,000 개 이상의 '행'데이터가있을 수 있습니다. 각 행은 10-20 열을 포함합니다. 이것은 파싱하고 데이터 (5 - 7 분)를 수집하는 거대한 작업이됩니다. 나는 xml을 읽고 각각의 'row'를 사전 (key = row number, value = Row Attributes)에 위치 시키려고 시도했지만, 이것은 오래 걸린다.VBA에서 XML 구문 분석 속도를 향상시키는 방법

DOM을 영원히 통과하고 있습니다. 더 효율적인 방법이 있습니까?

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 
+0

달성하려는 목표는 무엇입니까? 모든 행과 열을 반복 할 필요가 있거나 실제로 필요한 데이터의 하위 집합이 있습니까? SQL을 사용하여 데이터베이스에서 레코드 세트로 "선택 *"하고 테이블의 모든 행과 열을 반복하는 경우 처리 속도가 매우 느릴 수 있지만 일반적으로 데이터베이스의 데이터를 효율적으로 처리하는 방법은 아닙니다. 마찬가지로 XPath를 사용하여 전체 문서를 반복하지 않고 처리해야하는 XML의 하위 집합을 선택할 수 있습니다. –

답변

5

DOM 대신 SAX를 사용해 볼 수 있습니다. SAX는 문서를 파싱하고 문서의 크기가 작을 때 더 빠를 것입니다. MSXML에서의 SAX2 구현에 대한 참조는 here

입니다. 일반적으로 Excel에서 대부분의 XML 구문 분석을 위해 DOM에 곧바로 접근하지만 SAX는 일부 상황에서 이점이있는 것으로 보입니다. 짧은 비교 here은 그 차이점을 설명하는 데 도움이 될 수 있습니다.

여기에 바로 출력 Debug.Print를 사용하여 (부분적으로 this 기준) 해킹 - 함께 예입니다 :

가 기준이 "마이크로 소프트 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 

새로운 요소가 시작될 때마다에서 텍스트 데이터를 읽으려면

가 명시 적으로 표시하기 위해 카운터와 플래그를 설정 체크를 다음과 같이 10

은 명세서의 일부 코드를 추가합니다 요소의 이름 및 취할 적절한 조치

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 

확인이 수도 또는하지 않을 수 있습니다 (우리는 텍스트 데이터에 관심이 있는지 확인하고, 우리가있는 경우, 불필요한 공백을 잘라 모든 줄 바꿈 제거하는 문서에 따라 바람직하다. 우리가 다음 텍스트 값을 읽는 중지하고 Col의 끝에 도달 한 경우)

Private Sub IVBSAXContentHandler_characters(strChars As String) 

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

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 

값에서 여기에 장소에 스텁 방법의 알과 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

'Implements IVBSAXContentHandler'는 컴파일 오류를 발생시킵니다 : 객체 모듈은 '~'인터페이스를 구현해야합니다. –

+0

@ RyszardJędraszyk 답변의 끝까지'ContentHandlerImpl'의 전체 구현을 추가했습니다. 누락 된 스텁 메서드는 구현되지 않은 메서드에 대한 오류를 일으킬 수 있습니다. [이 대답] (http : // stackoverflow)에 링크 된 – barrowc

+0

@ RyszardJędraszyk [이 답변] (http://stackoverflow.com/a/29376815/2127508). com/a/26604768/2127508) 구현중인 Sub의 이름에 밑줄 문자가 있으면이 문제가 발생할 수 있다고 제안합니다. – barrowc

0

SelectSingleNode 기능을 사용하십시오. 이렇게하면 패턴 일치를 기반으로 노드를 검색 할 수 있습니다. 그것을

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

과 : An XML Response

는 단순히 호출 할 수 있습니다 : 나는 다음과 같은 XML 파일이있는 경우, 이제

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 

: 예를 들어

, 나는 다음과 같은 기능을 만들어 어떤 깊이에서도 'Error Status'라는 첫 번째 키로 점프하고 'Source'노드의 텍스트를 꺼내 "INTEGRATION"을 반환합니다.

+0

문서의 모든 행을 반복하고 모든 노드를 가져와야합니다. 이것으로 속도가 향상되지는 않을까요? 검색을 위해 SelectSingleNode를 더 사용하지 않습니까? –

+0

죄송합니다. 나는 당신이 관심을 가진 노드에 도달하기 위해 많은 중요하지 않은 노드를 탐색한다는 인상하에있었습니다. – Alain