Wenn Sie ein Abonnement des Magazins 'Access im Unternehmen' besitzen, können Sie sich anmelden und den kompletten Artikel lesen.
Anderenfalls können Sie das Abonnement hier im Shop erwerben.
SAX: XML-Dokumente parsen in der Praxis
Im Beitrag XML-Dokumente mit SAX parsen haben wir die Grundlagen ds SAX-Parsers vorgestellt. Dabei sind wir soweit gekommen, dass wir den kompletten Inhalt einer XML-Datei im Direktbereich des VBA-Editors ausgegeben haben. Das kann natürlich nicht alles sein: Die Daten sollen ja in der Regel in den Tabellen der Datenbank landen. Den Ereignisprozeduren, welche das XML-Dokument sequenziell durchlaufen, müssen wir dabei natürlich noch die eine oder andere zusätzliche Anweisung hinzufügen, damit die Daten an der gewünschten Stelle gespeichert werden.
Das sequenzielle Einlesen einer XML-Datei und die Ausgabe der gefundenen Daten ist bereits ein guter Startpunkt. Aber wie machen wir dann weiter, um die Daten, die ja meist in mehreren Datensätzen landen sollen, in eine entsprechende Tabelle zu schreiben?
Mit dem DOM-Parser war das relativ einfach: Man hat beispielsweise alle Kunde-Elemente mit der SelectNodes-Methode identifiziert und diese dann durchlaufen, wobei für jedes Element ein neuer Datensatz angelegt wurde. Die einzelnen Daten konnte man dann einfach über die entsprechenden Objekte einlesen.
Beim SAX-Parser ist dies völlig anders: Hier kann man nicht mal eben alle Kunde-Elemente durchlaufen. Genau genommen kann der SAX-Parser nur entweder alle Elemente durchlaufen – oder gar keines.
Dabei werden dann immer die gleichen Ereignisprozeduren ausgelöst, in denen wir lediglich mit den per Parameter übermittelten Daten arbeiten können.
Genau genommen liefert jede Ereignisprozedur für sich noch nicht einmal ausreichend Informationen, um die Inhalte der Elemente den richtigen Elementen zuordnen zu können. Darum müssen wir uns selbst kümmern.
Beispieldaten
Im ersten Beispiel wollen wir die Daten aus der XML-Datei Kunden.xml einlesen (s. Listing 1).
<?xml version="1.0" encoding="utf-8"?>
<Kunden xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Kunde KundeID="123">
<Vorname>André</Vorname>
<Nachname>Minhorst</Nachname>
</Kunde>
<Kunde KundeID="124">
<Vorname>Hermann</Vorname>
<Nachname>Müller</Nachname>
</Kunde>
<Kunde KundeID="125">
<Vorname>Klaus</Vorname>
<Nachname>Meier</Nachname>
</Kunde>
</Kunden>
Listing 1: Beispiel eines XML-Dokuments mit Kundendaten
Diese sollen in der Tabelle tblKunden landen, die lediglich ein Primärschlüsselfeld namens KundeID sowie zwei Textfelder namens Vorname und Nachname enthält (s. Bild 1).
Bild 1: Die Tabelle tblKunden
Wir verwenden an dieser Stelle das bereits im Beitrag XML-Dokumente mit SAX parsen verwendete Formular namens frmSAX, um den Einlesevorgang zu starten. Dieses Formular bietet die Möglichkeit, eine Datei auszuwählen und per Mausklick den Einlesevorgang auszulösen (s. Bild 2).
Bild 2: Formular zum Starten des EinlesevorgangsDie Schaltfläche cmdEinlesen löst die Prozedur aus Listing 2 aus. Diese verwendet die Klasse clsSAX, welche die Ereignisprozeduren der Schnittstelle MSXML2.IVBSAXContentHandler implementiert. In diese Ereignisprozeduren schreiben wir nachfolgend den Code, der zum Einlesen der Daten aus der XML-Datei Kunden.xml benötigt wird.
Private Sub cmdEinlesen_Click()
Dim objReader As SAXXMLReader60
Dim objSax As clsSAX
Set objReader = New SAXXMLReader60
Set objSax = New clsSAX
Set objReader.contentHandler = objSax
objReader.parseURL Me!txtDatei
Set objReader = Nothing
End Sub
Listing 2: Start des Einlesevorgangs
Dazu benötigen wir prinzipiell die folgenden Ereignisprozeduren:
- IVBSAXContentHandler_startDocument(): Wird beim Start des Einlesevorgangs ausgelöst. Hier bringen wir Anweisungen unter, die das Database-Objekt und das Recordset zum Hinzufügen der Daten vorbereiten.
- IVBSAXÂContentÂHandler_startÂEleÂment(strNameÂspaceURI As String, strLocalName As String, strQName As String, ByVal oAttributes As MSXML2.IVBSAXAttributes): Diese Prozedur wird für jedes Element ausgelöst. Hier prüfen wir per Select Case-Anweisung, welches Element gerade durchlaufen wird. Beim Kunde-Element etwa müssen wir einen neuen Datensatz im Recordset anlegen. In unserem Beispiel müssen wir hier auch das Attribut KundeID auslesen und den Wert gleich in das Feld KundeID des neuen Datensatzes eintragen. Bei den Elementen Vorname oder Nachname müssen wir uns merken, dass nun wohl Inhalte für diese Elemente über die Ereignisprozedur IVBSAXContentHandler_characters geliefert werden.
- IVBSAXContentHandler_characters(strChars As String): Hier werden die eigentlichen Inhalte der Elemente geliefert. Zusammen mit dem zuvor gemerkten Elementnamen können wir diese nun in die entsprechenden Felder eintragen.
- IVBSAXContentHandler_endElement(strNamespaceURI As String, strLocalName As String, strQName As String): Auch beim Ende eines Elements (etwa ) müssen wir per Select Case verschiedene Fälle untersuchen. Beim Element Vorname oder Nachname brauchen wir nichts zu tun. Beim Element Kunde müssen wir hingegen den Datensatz speichern, den wir zuvor mit den Werten für die Felder KundeID, Vorname und Nachname gefüllt haben.
- IVBSAXContentHandler_endDocument(): Dies bedeutet das Ende des Einlesevorgangs. Recordset- und Database-Objekt können nun geschlossen beziehungsweise die Objektvariablen geleert werden.
Damit begeben wir uns an die Arbeit. Beachten Sie, dass Sie – auch wenn wir nur einige der verfügbaren Ereignisse verwenden – alle Ereignisse der Schnittstelle implementieren müssen.
Deklarationen
Zunächst benötigen wir einige Deklarationen für Objekte, die von mehreren der Ereignisprozeduren verwendet werden. Da wäre zum Beispiel das Database-Objekt, das in der Prozedur IVBSAXContentHandler_startDocument gefüllt und in IVBSAXContentHandler_endDocument wieder geleert werden soll. Daher landet die Deklaration im Kopf des Klassenmoduls clsSAX:
Dim db As DAO.Database
Desweiteren benötigen wir ein Recordset, das wir in startDocument erstellen, in startElement mit einem neuen Datensatz füllen, dessen Felder in IVBSAXContentHandler_characters ihre Werte erhalten, das in IVBSAXContentHandler_endElement gespeichert und das in IVBSAXContentHandler_endDocument geschlossen wird:
Dim rstKunden As DAO.Recordset
Schließlich fehlt noch eine Variable, mit der wir uns merken können, welches Element gerade in IVBSAXContentHandler_startElement durchlaufen wurde:
Dim strFeld As String
Start des Dokuments
Das Ereignis IVBSAXContentHandler_startDocument implementieren wir wie in Listing 3. Diese Prozedur füllt zunächst die Variable db mit einem Verweis auf die aktuelle Datenbank. Die Recordset-Variable rstKunden füllen wir mit der OpenRecordset-Methode, die ein Recordset mit allen Datensätzen der Tabelle tblKunden zurückliefert. Dies alles fassen wir in eine rudimentäre Fehlerbehandlung ein, damit wir Fehler gleich an Ort und Stelle untersuchen können.
Private Sub IVBSAXContentHandler_startDocument()
On Error Resume Next
Set db = CurrentDb
Set rstKunden = db.OpenRecordset("SELECT * FROM tblKunden", dbOpenDynaset)
If Not Err.Number = 0 Then
MsgBox Err.Number & " " & Err.Description
End If
On Error GoTo 0
End Sub
Listing 3: Start des Dokuments
Start eines Elements
Danach wird als nächste für uns interessante Ereignisprozedur IVBSAXContentHandler_startElement ausgelöst (s. Listing 4).
Private Sub IVBSAXContentHandler_startElement(strNamespaceURI As String, _
strLocalName As String, strQName As String, _
ByVal oAttributes As MSXML2.IVBSAXAttributes)
Dim lngKundeID As Long
On Error Resume Next
Select Case strLocalName
Case "Kunde"
lngKundeID = oAttributes.getValueFromName("", "KundeID")
db.Execute "DELETE FROM tblKunden WHERE KundeID = " & lngKundeID, _
dbFailOnError
rstKunden.AddNew
rstKunden!KundeID = lngKundeID
strFeld = ""
Case "Vorname", "Nachname"
strFeld = strLocalName
Case Else
strFeld = ""
End Select
If Not Err.Number = 0 Then
MsgBox Err.Number & " " & Err.Description
End If
On Error GoTo 0
End Sub
Listing 4: Start eines Elements
Hier prüfen wir zunächst in einer Select Case-Bedingung, welches Element an der Reihe ist. Als Erstes wird hier das Element Kunden erscheinen, um das wir uns aber an dieser Stelle nicht zu kümmern brauchen – dies wird erst interessant, wenn ein XML-Dokument mehrerer solcher Hauptelemente enthält (wie Kunden, Artikel, Bestellungen et cetera).
Danach folgt das Element Kunde und wir beginnen, den ersten Kunden in der Tabelle tblKunden einzufügen.
Der entsprechende Zweig der Select Case-Bedingung ermittelt zunächst den Wert des Attributs KundeID – das ja in diesem Element enthalten ist:
<Kunde KundeID="123">
Dazu verwendet es die Funktion getValueÂFromName des mitgelieferten Parameters oAttributes und übergibt den Namen des Attributs (hier KundeID) als zweiten Parameter. Wir wollen Kundendatensätze, deren KundeID bereits vergeben ist, einfach löschen und neu füllen. Je nach Anforderungen werden Sie diesen Sachverhalt anders programmieren wollen. Dann legt die Prozedur mit der AddNew-Methode einen neuen Datensatz in der Tabelle tblKunden an. Da wir den Primärschlüsselwert bereits kennen, fügen wir diesen für das Feld KundeID ein (dieses darf zu diesem Zweck nicht als Autowert definiert sein).
Außerdem stellen wir die Variable strFeld auf eine leere Zeichenkette ein.
Später wird diese Prozedur für die Elemente Vorname und Nachname ausgelöst. In diesem Fall müssen wir natürlich keinen neuen Datensatz anlegen.
Wir können auch die Werte noch nicht eintragen, da diese ja erst in der Ereignisprozedur IVBSAXContentHandler_characters übermittelt werden.
Aber damit wir später beim Auslösen der Prozedur IVBSAXContentHandler_characters wissen, zu welchem Feld die enthaltenen Werte gehören, merken wir uns nun mithilfe der Variablen strFeld den Namen des Elements – also entweder Vorname oder Nachname.
Inhalt eines Elements einlesen
Damit geht es gleich weiter mit der Ereignisprozedur IVBSAXContentHandler_characters (s. Listing 5). Diese Prozedur erhält mit dem Parameter strChars den Inhalt des Elements.
Private Sub IVBSAXContentHandler_characters(strChars As String)
On Error Resume Next
Select Case strFeld
Case "Vorname"
rstKunden!Vorname = strChars
Case "Nachname"
rstKunden!Nachname = strChars
End Select
If Not Err.Number = 0 Then
MsgBox Err.Number & " " & Err.Description
End If
On Error GoTo 0
End Sub
Listing 5: Einlesen des Wertes eines Elements
Dabei kann es sich auch um Zeilenumbrüche oder Einschübe handeln, wenn das aktuelle Element weitere Unterelemente enthält.
Angenommen, das XML-Dokument ist an dieser Stelle wie folgt aufgebaut:
<Kunde KundeID="123">
<Vorname>André</Vorname>
Dann folgt nach dem Element Kunde zuerst ein Aufruf der Prozedur IVBSAXContentHandler_characters, bei der strChars den Zeilenumbruch von der Zeile <Kunde... zur Zeile <Vorname enthält.
Dann folgt ein weiterer Aufruf der Prozedur IVBSAXContentHandler_characters, welcher mit dem Parameter strChars die Einrückung des Textes <Vorname... enthält (also etwa einige Leerzeichen oder ein Tabulator-Zeichen).
Wenn Sie ein XML-Dokument verwenden, das um Zeilenumbrüche und Einschübe bereinigt ist, entfallen diese zahlreichen Aufrufe der Ereignisporzedur IVBSAXContentHandler_characters jedoch – dies dürfte sich positiv auf die Performance auswirken.
Solche Zeichen ignorieren wir jedoch, indem wir in einer Select Case-Bedingung den Wert der Variablen strFeld prüfen. Dabei wird die Prozedur nur tätig, wenn strFeld entweder den Wert Vorname oder Nachname enthält. In diesem Fall füllt die Prozedur das jeweilige Feld des Recordsets rstKunden mit dem per strChars übergebenen Wert.
Ende eines Elements
Die Ereignisprozedur IVBSAXContentHandler_endElement wird jeweils ausgelöst, wenn der SAX-Parser auf ein Element mit </... stößt (s. Listing 6). In unserem Beispiel geschieht dies zum ersten Mal mit dem Element </Vorname>, dann mit </Nachname>. Hier geschieht nichts, da die Prozedur in einer Select Case-Bedingung lediglich den Fall des Elements Kunde berücksichtigt.
Private Sub IVBSAXContentHandler_endElement(strNamespaceURI As String, _
strLocalName As String, strQName As String)
On Error Resume Next
Select Case strLocalName
Case "Kunde"
rstKunden.Update
End Select
strFeld = ""
If Not Err.Number = 0 Then
MsgBox Err.Number & " " & Err.Description
End If
On Error GoTo 0
End Sub
Listing 6: Bearbeitung des Ende-Tags eines Elements
Dies bedeutet, dass das komplette Kunde-Element samt enthaltenen Feldern abgearbeitet wurde und der Datensatz gespeichert werden kann. Dies erledigt die Prozedur mit der Update-Methode des Recordset-Objekts.
Ende des Dokuments
Nachdem alle Elemente eingelesen wurden, löst der SAX-Parser schließlich noch die Ereignisprozedur IVBSAXContentHandler_endDocument aus (s. Listing 7). Hier brauchen wir nur noch das geöffnete Recordset zu schließen und die Variablen db und rstKunden zu leeren.
Private Sub IVBSAXContentHandler_endDocument()
On Error Resume Next
rstKunden.Close
Set rstKunden = Nothing
Set db = Nothing
If Not Err.Number = 0 Then
MsgBox Err.Number & " " & Err.Description
End If
On Error GoTo 0
End Sub
Listing 7: Abschluss des Einlesevorgangs
Ein Blick in die Tabelle tblKunden zeigt, dass die Daten erfolgreich gespeichert wurden (s. Bild 3).
Bild 3: Die Tabelle tblKunden mit einigen BeispieldatenSpeichern in eine Tabelle mit Autowert-Feld
Nun hat man nicht immer die Gelegenheit, die Zieltabelle selbst zu definieren. Während wir hier also der Einfachheit halber den Primärschlüsselwert nicht als Autowert definiert haben, ist dies normalerweise nicht der Fall.
Wie aber bekommen wir den Primärschlüsselwert, hier aus dem Attribut KundeID der Kunde-Elemente, in das Primärschlüsselfeld? Legen wir doch als Erstes eine Kopie der Tabelle tblKunden unter dem Namen tblKundenMitAutowert an und ändern dort den Felddatentyp des Feldes KundeID auf Autowert (s. Bild 4).
Bild 4: Die Tabelle tblKunden mit Autowert als PrimärschlüsselAußerdem fügen wir dem Formular frmSAX eine weitere Schaltfläche namens cmdEinlesenMitAutowert hinzu. Diese löst die Prozedur aus Listing 8 aus. Der Unterschied zu der Prozedur der anderen Schaltfläche ist, dass wir hier die neu erstellte Klasse clsSAXMitAutowert referenzieren. Schließlich kopieren wir die Klasse clsSAX in eine neue Klasse namens clsSAXMitAutowert.
Private Sub cmdEinlesenMitAutowert_Click()
Dim objReader As SAXXMLReader60
Dim objSax As clsSAXMitAutowert
Set objReader = New SAXXMLReader60
Set objSax = New clsSAXMitAutowert
Set objReader.contentHandler = objSax
objReader.parseURL Me!txtDatei
Set objReader = Nothing
End Sub
Listing 8: Einlesen der gleichen Datei, diesmal in eine Tabelle mit Autowert-Feld
Danach wollen wir uns ansehen, ob wir die Tabelle mit dem Autowert im Primärschlüsselfeld nicht einfach genauso füllen können wie im vorherigen Beispiel (s. Listing 9). Und siehe da: Es klappt ohne Probleme! Sie müssen bei einem Autowert-Feld also nicht unbedingt den Autowert nutzen, sondern können auch neue Datensätze mit eigenem Primärschlüsselwert einfügen.
Dies war die Leseprobe dieses Artikels.
Melden Sie sich an, um auf den vollständigen Artikel zuzugreifen.