XML-Zugriff per VBA XPath

XML-Dokumente erscheinen je nach Größe auf den ersten Blick oft unübersichtlich und mächtig. Wie soll man hier die gewünschten Daten extrahieren – und das auch noch programmgesteuert per VBA Beispielsweise, um Informationen aus einem XML-Dokument in eine Access-Tabelle zu übertragen. Dafür steht die Abfragesprache VBA XPath zur Verfügung. Sie erlaubt es, mit verschiedenen Ausdrucken gezielt auf Elemente mit bestimmten Namen oder Eigenschaften zuzugreifen. Dieser Beitrag zeigt anhand einiger Beispiele, wie Sie XPath unter Access/VBA einsetzen.

Voraussetzungen für VBA XPath

Um die folgenden Beispiele auszuführen, benötigen Sie eine Beispieldatenbank mit einem Verweis auf die XML-Bibliothek von Microsoft. Dazu öffnen Sie den Verweise-Dialog (VBA-Editor, Menüeintrag Extras|Verweise) und wählen dort den Eintrag Microsoft XML, v3.0 aus (s. Bild 1). Mit Microsoft XML, v6.0 gab es bei der Erstellung der Beispiele Probleme, da hier einige Methoden nicht die erwarteten Ergebnisse lieferten.

Hinzufügen des Verweises auf die XML-Bibliothek

Bild 1: Hinzufügen des Verweises auf die XML-Bibliothek

Beispieldokument

Um per VBA XPath auf den Inhalt eines XML-Dokuments zugreifen zu können, benötigen Sie zunächst ein solches. Unseres heißt KategorienUndArtikel.xml und sollte sich im gleichen Verzeichnis wie die Beispieldatenbank befinden, da die Zugriffe auf diese Datei dahingehend ausgerichtet sind. Der Inhalt dieser XML-Datei sieht wie in Listing 1 aus.

<xml version="1.0" encoding="UTF-16">
<Bestellverwaltung xmlns="http://www.w3.org/TR/REC-html40">
   <Kategorie KategorieID="1">
     <Kategoriename>Getränke</Kategoriename>
     <Beschreibung>Alkoholfreie Getränke, Kaffee, Tee, Bier</Beschreibung>
     <Artikel ArtikelID="1">
       <Artikelname>Chai</Artikelname>
       <Einzelpreis>EUR 9.00</Einzelpreis>
     </Artikel>
     <Artikel ArtikelID="2">
       <Artikelname>Chang</Artikelname>
       <Einzelpreis>EUR 9.50</Einzelpreis>
     </Artikel>
     ..
   </Kategorie>
   <Kategorie KategorieID="2">
     <Kategoriename>Gewürze</Kategoriename>
     <Beschreibung>Süße und saure Soßen, Gewürze</Beschreibung>
     <Artikel ArtikelID="3">
       <Artikelname>Aniseed Syrup</Artikelname>
       <Einzelpreis>EUR 5.00</Einzelpreis>
     </Artikel>
     <Artikel ArtikelID="4">
       <Artikelname>Chef Anton''''s Cajun Seasoning</Artikelname>
       <Einzelpreis>EUR 11.00</Einzelpreis>
     </Artikel>
   ...
   </Kategorie>
   ...
</Bestellverwaltung>

Listing 1: XML-Dokument für die Beispiele dieses Beitrags

Zugriff mit VBA XPath über den Speicher

Um per VBA XPath auf ein XML-Dokument zugreifen zu können, müssen Sie dieses zunächst in den Speicher laden, beziehungsweise es mit einem geeigneten Objekt referenzieren. Dies erledigen Sie mit dem Code, wie er etwa in der folgenden Prozedur enthalten ist:

Public Sub DokumentLaden()
     Dim strDatei As String
     Dim objXML As MSXML2.DOMDocument
     strDatei =  CurrentProject.Path & "KategorienUndArtikel.xml"
     Set objXML = New MSXML2.DOMDocument
     objXML.Load strDatei
     If Not Len(objXML.XML) = 0 Then
         Debug.Print objXML.XML
     Else
         Debug.Print objXML.parseError.errorCode,  objXML.parseError.reason
     End If
End Sub

Die Prozedur erstellt ein neues Objekt des Typs DOMDocument und verwendet die Load-Methode, um die angegebene Datei in das Objekt zu laden. Gelingt dies, sollte die Länge der über die Eigenschaft XML zu ermittelnde Zeichenkette, also der Inhalt des Dokuments, größer als 0 sein. In diesem Fall soll die Prozedur den Inhalt des XML-Dokuments im Direktbereich des VBA-Editors ausgeben. Anderenfalls ist etwas beim Einlesen schiefgelaufen. Dann soll die Fehlernummer samt der Fehlerbeschreibung im Direktbereich erscheinen.

XPath per VBA nutzen

Um die Abfragesprache XPath von VBA aus nutzen zu können, gibt es zwei Funktionen. Die erste heißt selectSingleNode und erwartet einen XPath-Ausdruck als Parameter. Sie liefert ein einziges Node-Element als Ergebnis zurück, sofern die Abfrage ein Ergebnis hat.

Die zweite Funktion heißt selectNodes und liefert eine Auflistung des Typs DOMSelection zurück. Sie kann kein, ein oder mehrere Elemente enthalten, die wiederum den Typ DOMDocument für das Dokument-Objekt, IXMLDOMProcessingInstruction für das -Element oder IXMLDOMElement für die übrigen Elemente aufweisen.

üblicherweise werden Sie aber mit den Elementen des Typs IXMLDOMElement arbeiten, ein Zugriff auf das DOMDocument-Objekt oder das IXMLDOMProcessingInstruction-Objekt ist selten in Zusammenhang mit dem Zugriff per XPath nötig.

Unsere Beispielprozedur für das Erstellen eines XML-Dokuments und das Füllen dieses Objekts aus einer XML-Datei haben wir etwas abgewandelt, damit wir damit mit einer Anweisung innerhalb unserer Beispielprozeduren auf das XML-Dokument zugreifen können (s. Listing 2).

Public Function GetDocument() As MSXML2.DOMDocument
     Dim strDatei As String
     Dim objXML As MSXML2.DOMDocument
     strDatei = CurrentProject.Path & "KategorienUndArtikel.xml"
     Set objXML = New MSXML2.DOMDocument
     objXML.Load strDatei
     If Not Len(objXML.XML) = 0 Then
         Set GetDocument = objXML
     Else
         MsgBox "Fehler " & objXML.parseError.errorCode & ": " & objXML.parseError.reason
     End If
End Function

Listing 2: Hilfsfunktion, um ein gefülltes DOMDocument-Element zu holen

Auf das Root-Element zugreifen

Ein Beispiel für den Zugriff auf ein einzelnes XML-Element sieht danach wie folgt aus:

Public Sub RootelementHolen()
     Dim objElement As MSXML2.IXMLDOMElement
     Dim objDocument As MSXML2.DOMDocument
     Set objDocument = GetDocument
     Set objElement = _
         objDocument.selectSingleNode("Bestellverwaltung")
     Debug.Print objElement.XML
End Sub

Dies liest das Element Bestellverwaltung samt allen untergeordneten Elementen ein. Wenn wir wie in obiger Beispielprozedur den Inhalt der XML-Eigenschaft im Direktbereich ausgeben, erhalten wir also fast das komplette Dokument – mit Ausnahme der Formatinformationen in der -Zeile.

Dies gelingt aber auch nur über den VBA XPath-Ausdruck Bestellverwaltung, weil wir die selectSingleNode-Funktion für das DOMDocument-Objekt aufrufen und das Bestellverwaltung-Objekt diesem direkt untergeordnet ist.

Wir könnten also nicht etwa auf das erste Kategorie-Objekt zugreifen, indem wir einfach folgenden Ausdruck nutzen:

Set objElement = objDocument.selectSingleNode("Kategorie")

Wenn wir dies versuchen, erhalten wir eine Fehlermeldung wie in Bild 2. Die Anweisung Set objElement… löst zwar noch keinen Fehler aus. Aber objElement wird hier nicht gefüllt und der folgende Zugriff auf eine Eigenschaft von objElement führt dann zum Fehler.

Fehler beim Zugriff auf ein XML-Element

Bild 2: Fehler beim Zugriff auf ein XML-Element

Auf ein direktes Unterelement des Root-Elements zugreifen

Mit dem Namen eines Elements allein können Sie also nur auf ein Element zugreifen, wenn sich dieses direkt unterhalb des Objekts befindet, für das Sie die SelectSingleNode-Methode aufrufen.

Dafür müssten Sie zuerst das Root-Element Bestellverwaltung per IXMLDomElement-Variable referenzieren und könnten dann von dort aus auf das Kategorie-Element zugreifen:

Dim objBestellverwaltung As MSXML2.IXMLDOMElement
Dim objKategorie As MSXML2.IXMLDOMElement
Dim objDocument As MSXML2.DOMDocument
Set objDocument = GetDocument
Set objBestellverwaltung =  objDocument.selectSingleNode("Bestellverwaltung")
Set objKategorie = objBestellverwaltung. selectSingleNode("Kategorie")
Debug.Print objKategorie.XML

Dies gibt den Inhalt des ersten Kategorie-Elements aus.

An dieser Stelle ist es wichtig zu erwähnen, dass die SelectSingleNode immer das erste Element liefert, das dem angegebenen Ausdruck entspricht. Während es nur ein Bestellverwaltung-Element gibt, befinden sich darunter allerdings gleich sieben Kategorie-Elemente. Davon liefert SelectSingleNode dann das erste.

Auf mehrere Elemente zugreifen

Das ist ein guter Anlass, die Funktion selectNodes vorzustellen. Sie liefert nicht nur ein einziges Element zurück, sondern kann auch einmal kein oder mehrere Elemente zurückgeben. Diese kommen immer in einer Auflistung vom Typ DOMSelection.

Wenn Sie die gefundenen Elemente mit der For Each-Schleife durchlaufen wollen, definieren Sie wie im folgenden Beispiel sowohl ein Objekt namens objKategorie mit dem Typ IXMLDOMElement als Laufvariable sowie eines für die Auflistung namens objKategorien mit dem Typ IXMLDOMSelection.

Dann referenzieren Sie wieder das Root-Element und nutzen dann dessen selectNodes-Funktion, um alle untergeordneten Kategorie-Elemente zu ermitteln. Diese landen dann im Auflistungsobjekt objKategorien. Dieses können wir dann per For Each-Schleife mit der Laufvariablen objKategorie durchlaufen. Innerhalb der Schleife geben wir wieder den Inhalt der XML-Eigenschaft aus:

Public Sub KategorienHolen()
     Dim objBestellverwaltung As MSXML2.IXMLDOMElement
     Dim objKategorie As MSXML2.IXMLDOMElement
     Dim objKategorien As MSXML2.IXMLDOMSelection
     Dim objDocument As MSXML2.DOMDocument
     Set objDocument = GetDocument
     Set objBestellverwaltung =  objDocument.selectSingleNode("Bestellverwaltung")
     Set objKategorien =  objBestellverwaltung.selectNodes("Kategorie")
     For Each objKategorie In objKategorien
         Debug.Print objKategorie.XML
     Next objKategorie
End Sub

Da die Ausgabe alle Kategorie-Elemente umfasst, die selbst jeweils einige Artikel-Elemente enthalten, sprengt die Ausgabe das Direktfenster. Also geben wir etwas weniger Umfangreiches aus, indem wir die Debug.Print-Anweisung wie folgt ersetzen und damit gleich noch einen einfachen XPath-Ausdruck nutzen:

Debug.Print objKategorie.selectSingleNode( "Kategoriename").nodeTypedValue

Da wir in diesem Fall nicht einfach den Inhalt der Eigenschaft XML ausgeben wollen, sondern den Inhalt des Elements Kategoriename selbst, verwenden wir die Eigenschaft nodeTypedValue.

Die gefundenen Elemente können Sie auch per For…Next-Schleife durchlaufen. Dann nutzen Sie die length-Eigenschaft der IXMLDOMSelection-Auflistung zur Bestimmung der Anzahl der Elemente.

über die Item()-Eigenschaft greifen Sie dann auf das jeweilige Element zu, wobei der Index 0-basiert ist, was für den Wertebereich der Schleife berücksichtigt werden muss:

Dim i As Integer
...
Set objKategorien =  objBestellverwaltung.selectNodes("Kategorie")
For i = 0 To objKategorien.length - 1
     Debug.Print objKategorien.Item(i).selectSingleNode( "Kategoriename").nodeTypedValue
Next i

Beispiele für XPath-Ausdrücke

Nachdem Sie nun erfahren haben, wo und wie Sie überhaupt XPath-Ausdrücke einsetzen können (wobei wir ja immer nur den Namen eines Elements als Ausdruck angegeben haben), wollen wir uns nun die wichtigsten Beispiele für XPath-Ausdrücke ansehen.

Dabei ist immer der Kontext wichtig, also von welchem Element des XML-Dokuments aus Sie das betroffene Element referenzieren wollen. Für die ersten Beispiele gehen wir jeweils vom Root-Element aus, also vom Element Bestellverwaltung.

Zugriff auf Enkel-Elemente

Wenn Sie direkt vom DOMDocument-Objekt auf die Kategorie-Elemente unterhalb von Bestellverwaltung zugreifen wollen, gelangen Sie so dorthin:

Set objKategorien =  objDocument.selectNodes("Bestellverwaltung/Kategorie")
For i = 0 To objKategorien.length - 1
     Debug.Print objKategorien.Item(i).selectSingleNode( "Kategoriename").nodeTypedValue
Next i

Alle Elemente mit bestimmtem Namen

Wenn Sie alle Elemente eines Dokuments mit einem bestimmten Namen finden wollen, können Sie den Ausdruck //Kategorie verwenden:

Set objKategorien = objDocument.selectNodes("//Kategorie")

Vorsicht, wenn sich auch in anderen Ebenen noch Elemente des gleichen Namens befinden – die werden dann auch berücksichtigt.

Wenn Sie beispielsweise alle Elemente namens Artikelname ermitteln wollen, die sich unterhalb des Elements Artikel befinden, gelingt dies so:

Set objArtikel =  objDocument.SelectNodes("//Artikel/Artikelname")

Alle Elemente mit VBA XPath ermitteln

Sollten Sie überhaupt alle Elemente eines Dokuments durchlaufen wollen, dann nutzen Sie den XPath-Ausdruck //*. Die Elemente werden dann von oben nach unten durchlaufen:

Dim objObjects As MSXML2.IXMLDOMSelection
Dim objDocument As MSXML2.DOMDocument
Dim i As Integer
Set objDocument = GetDocument
Set objObjects = objDocument.selectNodes("//*")
For i = 0 To objObjects.length - 1
     Debug.Print objObjects.Item(i).baseName
Next i

Hier verwenden wir wieder einmal die Eigenschaft baseName, um einfach die Namen der enthaltenen Elemente auszugeben. Die Ausgabe sieht dann etwa so aus:

Bestellverwaltung
Kategorie
Kategoriename
Beschreibung
Artikel
Artikelname
Einzelpreis
Artikel
Artikelname
Einzelpreis
Artikel
Artikelname
...

Von oben nach unten mit VBA XPath

Wenn Sie auf Nummer sicher gehen wollen und wissen, wo sich das gesuchte Element befindet, können Sie die Elemente von oben nach unten als XPath-Ausdruck angeben. Um zum Beispiel den Namen des ersten Artikels auszugeben, verwenden Sie den Ausdruck Bestellverwaltung/Kategorie/Artikel/Artikelname:

Public Sub VonObenNachUnten()
     Dim objDocument As MSXML2.DOMDocument
     Set objDocument = GetDocument
     Debug.Print objDocument.selectSingleNode( _
         "Bestellverwaltung/Kategorie/Artikel/Artikelname"). nodeTypedValue
End Sub

Alle direkt untergeordneten Elemente

Alle untergeordneten Elemente etwa der Kategorie-Elemente erhalten Sie, indem Sie vom DOMDocument-Element den Ausdruck Bestellverwaltung/Kategorie/* mit der Methode selectNodes nutzen. Die folgende Prozedur gibt die Namen aller den Kategorie-Elementen untergeordneten Elemente aus, also zum Beispiel Kategoriename, Beschreibung und Artikel:

Public Sub AlleUntergeordneten()
     Dim objDocument As MSXML2.DOMDocument
     Dim objKinder As MSXML2.IXMLDOMSelection
     Dim objKind As MSXML2.IXMLDOMElement
     Set objDocument = GetDocument
     Set objKinder = objDocument. selectNodes("Bestellverwaltung/Kategorie/*")
     For Each objKind In objKinder
         Debug.Print objKind.baseName
     Next objKind
End Sub

Das könnte man auf naive Weise und ohne Kenntnis der XPath-Syntax auch umständlicher machen – zum Beispiel wie folgt:

Public Sub AlleUntergeordneten_Naiv()
     Dim objDocument As MSXML2.DOMDocument
     Dim objBestellverwaltung As MSXML2.IXMLDOMElement
     Dim objKategorien As MSXML2.IXMLDOMSelection
     Dim objKategorie As MSXML2.IXMLDOMElement
     Dim objKinder As MSXML2.IXMLDOMSelection
     Dim objKind As MSXML2.IXMLDOMElement
     Set objDocument = GetDocument
     Set objBestellverwaltung =  objDocument.selectSingleNode("Bestellverwaltung")
     Set objKategorien =  objBestellverwaltung.selectNodes("Kategorie")
     For Each objKategorie In objKategorien
         Set objKinder = objKategorie.selectNodes("*")
         For Each objKind In objKinder
             Debug.Print objKind.baseName
         Next objKind
     Next objKategorie
End Sub

Bestimmte Elemente mit VBA XPath ermitteln

Elemente zeichnen sich entweder durch den Inhalt aus oder durch den Wert ihrer Attribute. Früher oder später werden Sie XML-Dokumente nach Elementen mit solchen Eigenschaften durchforsten müssen.

Kategorie-Element mit bestimmtem Namen

Wenn Sie ein Element mit einem bestimmten atomaren Wert suchen (ein atomarer Wert ist ein Wert, der keine weiteren untergeordneten Elemente aufweist), können Sie die Syntax mit einem Suchausdruck in eckigen Klammern gleich hinter dem Elementnamen nutzen. Wenn Sie etwa die Kategorie mit dem Wert Getränke im Element Kategorienamen suchen, sieht das wie folgt aus – hier geben wir den XML-Inhalt des gefundenen Objekts aus:

Public Sub KategorieMitBestimmtemNamen()
     Dim objDocument As MSXML2.DOMDocument
     Dim objKategorie As MSXML2.IXMLDOMElement
     Set objDocument = GetDocument
     Set objKategorie = objDocument.selectSingleNode( "//Kategorie[Kategoriename=''''Getränke']")
     If Not objKategorie Is Nothing Then
         Debug.Print objKategorie.XML
     End If
End Sub

Vor dem Suchausdruck befindet sich also der Name des Elements, der Suchausdruck landet in eckigen Klammern und lautet etwa Kategoriename=““Getränke““. Die Ausgabe des Ergebnisses haben wir diesmal in eine Prüfung eingefasst, ob die Suche überhaupt erfolgreich war und objKategorie nicht gegebenenfalls noch leer ist, was beim Zugriff einen Fehler auslösen würde.

Alle Artikelnamen einer bestimmten Kategorie

Wenn Sie nun alle Artikelnamen dieser Kategorie ausgeben möchten, brauchen Sie keine weiteren Objekte zu erstellen, die Sie nach dem Herausfinden der Kategorie mit einem weiteren XPath-Ausdruck ermitteln und dann durchlaufen. Eine Liste aller Artikelnamen einer bestimmten Kategorie, beispielsweise der Kategorie Getränke, finden Sie auch mit diesem Ausdruck (s. Listing 3):

Public Sub ArtikelEinerKategorie()
     Dim objDocument As MSXML2.DOMDocument
     Dim objArtikelnamen As MSXML2.IXMLDOMSelection
     Dim objArtikelname As MSXML2.IXMLDOMElement
     Set objDocument = GetDocument
     Set objArtikelnamen = _
         objDocument.selectNodes("//Kategorie[Kategoriename=''''Getränke']/Artikel/Artikelname")
     For Each objArtikelname In objArtikelnamen
         Debug.Print objArtikelname.nodeTypedValue
     Next objArtikelname
End Sub

Listing 3: Einlesen aller Artikelnamen der Artikel einer bestimmten Kategorie

//Kategorie[Kategoriename=''''Getränke']/Artikel/Artikelname

Haben Sie etwas bemerkt Sie brauchen prinzipiell immer nur eine einzige selectNodes– oder selectSingleNode-Funktion zu nutzen, um direkt auf die Werte des gesuchten Elements zugreifen zu können, weil Sie immer so weit navigieren können, wie es der Anwendungsfall erfordert.

Artikeleigenschaften eines bestimmten Artikels ausgeben

Manchmal brauchen Sie dennoch mehrere Aufrufe von selectNodes oder selectSingleNode – so etwa, wenn Sie mehrere Unterelemente eines Elements auswerten müssen. Ein Artikel-Element hat ja beispielsweise die Unterelemente Artikelname und Einzelpreis, die vermutlich auch einmal gemeinsam abgefragt werden müssen.

In diesem Fall durchlaufen Sie dann eben alle Artikel-Elemente der gewünschten Kategorie und greifen dann in der Schleife über alle Elemente auf die beiden Unterelemente Artikelname und Einzelpreis zu und geben diese aus:

Public Sub UnterelementeArtikelEinerKategorie()
     Dim objDocument As MSXML2.DOMDocument
     Dim objArtikelliste As MSXML2.IXMLDOMSelection
     Dim objArtikel As MSXML2.IXMLDOMElement
     Set objDocument = GetDocument
     Set objArtikelliste = objDocument.selectNodes( "//Kategorie[Kategoriename=''''Getränke']/Artikel")
     For Each objArtikel In objArtikelliste
         Debug.Print objArtikel.selectSingleNode( "Artikelname").nodeTypedValue
         Debug.Print objArtikel.selectSingleNode( "Einzelpreis").nodeTypedValue
     Next objArtikel
End Sub

Weitere Vergleichsausdrücke mit VBA XPath

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar