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.
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.
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