XML-Dokumente transformieren mit XSLT

Mit den eingebauten Funktionen für den Export von Daten aus Tabellen und Abfragen in das XML-Format können Sie bereits recht gute Ergebnisse erzielen. Natürlich können Sie aber nicht komplett steuern, wie das Zieldokument später aussehen wird. Je nach den Anforderungen der Anwendung, die das XML-Dokument weiterverarbeiten soll, sind noch änderungen notwendig. Hier tritt die Transformation von XML-Dokumenten auf den Plan: Mit einer sogenannten .xslt-Datei legen Sie fest, wie ein Dokument in ein anderes umgeformt werden soll. Den vollständigen Vorgang steuern Sie dann per VBA-Prozedur. Dieser Beitrag liefert die Grundlagen der Transformation und die notwendigen VBA-Techniken.

Voraussetzungen

Wenn Sie mit den eingebauten Export-Funktionen einfach nur XML-Dokumente auf Basis von Tabellen oder Abfragen erstellen wollen, benötigen Sie dazu keine weiteren Bibliotheken. Auch eine Transformation eines exportierten XML-Dokuments über die entsprechende Funktion der Benutzeroberfläche (zum Beispiel über den Ribbon-Eintrag Externe Daten|Exportieren|XML-Datei) können Sie ohne weitere Hilfsmittel durchführen – Sie können einfach im Assistenten angeben, welche .xslt-Datei die Vorgaben für die Transformation enthält. Das erzeugte XML-Dokument wird dann nach dem Export automatisch auf Basis dieser Datei transformiert. Sollten Sie jedoch einen Export mit der Methode ExportXML des Application-Objekts durchführen wollen, können Sie die .xslt-Datei dort nicht etwa per Parameter angeben. Sie exportieren die Daten dort erst in ein XML-Dokument und führen dann die Transformation durch. Für diese Transformation benötigen Sie Objekte und Methoden der Bibliothek Microsoft XML, vx.0, wobei Sie die jeweils aktuellste Version dieser Bibliothek wählen sollten (s. Bild 1).

Verweis auf die Bibliothek Microsoft XML, v6.0

Bild 1: Verweis auf die Bibliothek Microsoft XML, v6.0

Transformations-Grundlagen

XML-Dokumente bestehen aus Daten und aus Elementen zur Strukturierung dieser Daten. Damit lassen sich beispielsweise die Daten aus verknüpften Tabellen einer Datenbank hierarchisch darstellen – zum Beispiel haben Sie eine Kategorien-Tabelle und eine Artikel-Tabelle, wo jedem Artikel eine Kategorie zugewiesen ist. In einem XML-Dokument könnten Sie nun die Kategorien und Artikel hierarchisch strukturiert speichern:

<xml version="1.0" encoding="UTF-8">
<Kategorien>
   <Kategorie>
     <KategorieID>1</KategorieID>
     <Kategoriename>Kategorie 1</Kategoriename>
     <Artikel>
       <ArtikelID>1</ArtikelID>
       <Artikelname>Artikel 1</Artikelname>
     </Artikel>
     <Artikel>
       <ArtikelID>2</ArtikelID>
       <Artikelname>Artikel 2</Artikelname>
     </Artikel>
   </Kategorie>
   <Kategorie>
     <KategorieID>2</KategorieID>
     <Kategoriename>Kategorie 2</Kategoriename>
     <Artikel>
       <ArtikelID>3</ArtikelID>
       <Artikelname>Artikel 3</Artikelname>
     </Artikel>
     <Artikel>
       <ArtikelID>4</ArtikelID>
       <Artikelname>Artikel 4</Artikelname>
     </Artikel>
   </Kategorie>
</Kategorien>

Die Elemente aus solch einem XML-Dokument können Sie mit einer entsprechenden .xslt-Datei in beliebiger Form umstrukturieren, also transformieren.

Dazu sind nur wenige Schritte nötig:

  • Sie benötigen ein Objekt des Typs DOMDocument (oder DOMDocument60, je nach verwendeter Typ-Bibliothek – in unserem Fall Microsoft XML, v6.0 und DOMDocument60), das Sie mit dem Inhalt des zu transformierenden XML-Dokuments füllen.
  • Ein weiteres Objekt des gleichen Typs füllen Sie mit dem Inhalt der .xslt-Datei.
  • Schließlich brauchen Sie noch ein drittes DOMDocument-Objekt, in welchem die transformierte Datei landet.
  • Für das erste DOMDocument-Objekt führen Sie die Methode transformNodeToObject aus, dem Sie das zweite und das dritte DOMDocument-Objekt als Parameter übergeben.

Für diese Anweisungen haben wir eine einfache Prozedur geschrieben, der Sie die Pfade für die drei beteiligten XML-Dokumente per Parameter übergeben können. Diese Prozedur heißt Transformieren und sieht wie in Listing 1 aus. Mit dem ersten Parameter übergeben Sie den Pfad zu der zu transformierenden XML-Datei, mit dem zweiten den Pfad zur .xslt-Datei und mit dem dritten den Pfad, unter dem die transformierte Datei gespeichert werden soll.

Public Function Transformieren(strQuelle As String, strXSLT As String, strZiel As String, _
         Optional strFehler As String) As Long
     Dim objQuelle As MSXML2.DOMDocument60
     Dim objXSLT As MSXML2.DOMDocument60
     Dim objZiel As MSXML2.DOMDocument60
     Set objQuelle = New MSXML2.DOMDocument60
     objQuelle.Load strQuelle
     If objQuelle.parseError = 0 Then
         Set objXSLT = New MSXML2.DOMDocument60
         objXSLT.Load strXSLT
         If objXSLT.parseError = 0 Then
             Set objZiel = New MSXML2.DOMDocument60
             objQuelle.transformNodeToObject objXSLT, objZiel
             objZiel.Save strZiel
         Else
             Transformieren = objXSLT.parseError.errorCode
             strFehler = ".xslt-datei: " & vbCrLf & strXSLT & vbCrLf & objXSLT.parseError.reason
         End If
     Else
         Transformieren = objQuelle.parseError.errorCode
         strFehler = "Quelldatei: " & vbCrLf & strQuelle & vbCrLf & objQuelle.parseError.reason
     End If
End Function

Listing 1: Prozedur für die einfache Transformation eines XML-Dokuments

Der vierte Parameter ist ein optionaler Rückgabeparameter, der von der Funktion mit einer Fehlermeldung gefüllt wird, wenn ein Fehler auftritt. Die Funktion deklariert dann die drei benötigten Objekte vom Typ DOMDocument60.

Dann erstellt sie das erste Objekt objQuelle mit der New-Anweisung und füllt es mit der Load-Methode. Die Load-Methode erwartet den Pfad zu einer XML-Datei, den wir mit dem Parameter strQuelle übergeben. Hierbei kann es geschehen, dass ein Fehler auftritt – beispielsweise, dass unter dem mit strQuelle angegebenen Pfad gar keine Datei gefunden werden kann. Tritt ein solcher Fehler auf, liefert die Eigenschaft parseError von objQuelle einen Wert ungleich 0. Dies prüfen wir in einer If…Then-Bedingung, deren Else-Teil gegebenenfalls die Fehlermeldung in den Rückgabeparameter strFehler schreibt – samt Angabe der fehlerhaften Datei. Außerdem weist die Funktion dem Rückgabewert der Funktion die Fehlernummer zu.

Tritt kein Fehler auf, erstellt die Funktion das zweite Objekt objXSLT und füllt es mit dem Inhalt der mit strXSLT angegebenen .xslt-Datei – wieder unter Verwendung der Load-Methode. Auch hier eventuell auftretende Fehler werden entsprechend behandelt.

Ist bis hierher kein Fehler aufgetreten, erstellt die Prozedur das DOMDocument60-Objekt für das transformierte XML-Dokument. Die Transformation selbst erfolgt dann durch die Methode transformNodeToObject des Objekts objQuelle. Dieser übergeben wir Verweise auf die DOMDocument60-Objekte mit der .xslt-Datei und der Zieldatei als Parameter.

Nach erfolgter Transformation speichern wir den Inhalt des neu erzeugten und gefüllten XML-Dokuments aus objZiel mit der Save-Methode in der mit dem Parameter strZiel angegebenen XML-Datei.

Aufruf der Funktion „Transformieren“

Der Aufruf dieser Funktion kann, wenn Sie das zu transformierende XML-Dokument und das .xslt-Dokument bereits auf der Festplatte abgelegt haben, ganz einfach wie folgt geschehen:

Transformieren <Quelldokument>, <XSLT-Dokument>,  <Zieldokument>

So erhalten Sie zwar keinen Zugriff auf eine eventuelle Fehlermeldung, aber es ist der schnellste Weg, um die Transformation durchzuführen, wenn die Dateien im Dateisystem liegen.

Wenn Sie sich die Funktionsweise inklusive Fehlermeldung ansehen möchten, können Sie die Methode aus Listing 2 nutzen – gemeinsam mit den Tabellen der Beispieldatenbank zu diesem Beitrag. Die Methode deklariert zunächst die benötigten Variablen. Dann exportiert sie ein XML-Dokument auf Basis der Tabellen tblKategorien und tblArtikel, wobei die Artikeldaten den Kategorie-Elementen untergeordnet werden sollen (wie dies im Detail funktioniert, lesen Sie im Beitrag XML-Export mit VBA, www.access-im-unternehmen.de/1046).

Public Sub TestTransformieren()
     Dim strQuelle As String
     Dim strXSLT As String
     Dim strZiel As String
     Dim strFehler As String
     Dim lngFehler As Long
     Dim objAdditionalData As AdditionalData
     Set objAdditionalData = Application.CreateAdditionalData
     objAdditionalData.Add "tblArtikel"
     strQuelle = CurrentProject.Path & "\KategorienUndArtikel_Untransformiert.xml"
     Application.ExportXML acExportTable, "tblKategorien", strQuelle, AdditionalData:=objAdditionalData
     strXSLT = CurrentProject.Path & "\KategorienUndArtikel.xslt"
     strZiel = CurrentProject.Path & "\KategorienUndArtikel_Transformiert.xml"
     lngFehler = Transformieren(strQuelle, strXSLT, strZiel, strFehler)
     If Not lngFehler = 0 Then
         MsgBox strFehler
     End If
End Sub

Listing 2: Aufruf der Funktion Transformieren mit Beispieldaten

Der Export landet in der Datei KategorienUndArtikel_Untransformiert.xml. Die .xslt-Datei zu diesem Beispiel finden Sie in den Download-Dateien. Sie heißt KategorienUndArtikel.xslt und sollte sich im gleichen Verzeichnis wie die Datenbank befinden. Schließlich legt die Prozedur noch den Namen der Zieldatei fest, die unter KategorienUndArtikel_Transformiert.xml gespeichert werden soll. Die drei Variablen strQuelle, strXSLT und strZiel werden samt der Variablen strFehler für den optionalen Parameter an die Funktion Transformieren übergeben. Sollte hier einer der oben erläuterten Fehler auftreten, liefert diese einen Wert ungleich 0 zurück, was zur Ausgabe der mit strFehler zurückgegebenen Fehlermeldung per MsgBox-Anweisung führt. Anderenfalls finden Sie nun in der Datei KategorienUndArtikel_Transformiert.xml das transformierte XML-Dokument vor. Sie können sich das Beispiel vorab anhand der Beispieldaten anschauen, in den folgenden Abschnitten erläutern wir die einzelnen Elemente einer .xslt-Datei.

XSLT

XSLT ist die Sprache, mit der Transformationen von XML-Dateien in andere XML-Dateien oder auch HTML-Dateien durchgeführt werden. Dabei greifen Sie über eine spezielle weitere Sprache namens XPath auf das oder die gewünschten Elemente des zu transformierenden XML-Dokuments zu und überführen die kompletten Elemente oder auch nur deren Inhalt in das zu erstellende Dokument. Die Sprache XPath und ihre Anwendung mittels VBA beschreiben wir in einem weiteren Beitrag namens VBA und XPath (www.access-im-unternehmen.de/1050).

Wenn Sie schon einmal eine Webseite programmiert haben, die nicht nur aus reinem HTML besteht, sondern auch aus Skript-Elementen etwa auf Basis von PHP oder ASP/ASP.NET, haben Sie unbewusst bereits eine Vorstellung davon, wie XSLT ein neues Dokument auf Basis eines bestehenden Dokuments zusammensetzt. Ein XSLT-Dokument ist dabei ähnlich aufgebaut wie eine aus Skript- und HTML-Teilen bestehende Webseite.

Sie finden dort nämlich feste Zeichenketten, aber auch dynamische Elemente, mit denen etwa die Inhalte des zu transformierenden Dokuments ermittelt und ausgegeben werden.

XSLT deklarieren

Damit die .xslt-Datei korrekt interpretiert werden kann, teilen wir der jeweiligen Verarbeitungsinstanz (in unserem Beispiel etwa die Methode transformNodeToObject) mit einer entsprechenden Deklaration in der ersten Zeile mit, um was für einen Dokumenttyp es sich handelt. In diesem Fall soll die Datei mit der folgenden Zeile starten:

<xsl:stylesheet version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns="http://www.w3.org/TR/REC-html40">

Damit wird der offizielle Namespace des W3C-Konsortiums vorgegeben. Alle folgenden Zeilen, die XSLT-Befehle enthalten, starten mit <xsl: und werden mit einem XSLT-Schlüsselwort fortgesetzt. Dadurch können Sie die auszuführenden Elemente des .xslt-Dokuments von den statischen Elementen unterscheiden – ähnlich wie etwa beim einem PHP-Dokument, wo die PHP-Anweisungen in -Blöcken stehen.

Dieser Zeile stellen wir noch die folgende Zeile voran:

<xml version="1.0" encoding="UTF-8">

Das template-Element

Das Basis-Element einer .xslt-Datei ist das template-Element. Es enthält auch ein Attribut namens match. Mit match referenzieren Sie das Element eines XML-Dokuments, auf das sich die innerhalb des template-Elements befindlichen Elemente beziehen. Der Wert von match ist ein XPath-Ausdruck. XPath ist, wie oben bereits erwähnt wurde, die Sprache für den Zugriff auf die Elemente in einem XML-Dokument. Jede Menge Beispiele dazu finden Sie im Beitrag VBA und XPath (www.access-im-unternehmen.de/1050). Wenn Sie beispielsweise auf das Root-Element des Dokuments (also das oberste Element) zugreifen wollen, geben Sie für das Attribut match einen Schrägstrich an (/).

Es werden nur Informationen ausgegeben, die sich innerhalb eines template-Elements befinden. Sie können also Folgendes in die .xslt-Datei schreiben und es wird nichts ausgegeben:

<xml version="1.0" encoding="utf-8">
<xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns="http://www.w3.org/TR/REC-html40">
   <blabla>blub</blabla>
   <xsl:template match="/">
   </xsl:template>
</xsl:stylesheet>

Innerhalb des template-Elements befinden sich keine Daten, und das davor angegebene blabla-Element wird nicht ausgegeben, weil es sich nicht innerhalb eines template-Elements befindet. Das Ergebnisdokument ist folglich leer. Wenn Sie das blabla-Element innerhalb des template-Elements platzieren, wird es allerdings ausgegeben:

<xsl:stylesheet version="1.0" ...>
   <xsl:template match="/">
     <blabla>blub</blabla>
   </xsl:template>
</xsl:stylesheet>

Das heißt, dass Sie selbst eigene Elemente zur Ausgabe hinzufügen können, auch ohne dynamische xsl:…-Elemente innerhalb des template-Elements hinzuzufügen. Sie könnten also etwa die Grundstruktur des Dokuments anlegen:

<xsl:stylesheet version="1.0" ...>
   <xsl:template match="/">
     <Bestellverwaltung>
     </Bestellverwaltung>
   </xsl:template>
</xsl:stylesheet>

Dies liefert die folgende Ausgabe:

<xml version="1.0" encoding="UTF-16">
<Bestellverwaltung ...></Bestellverwaltung>

Zeilenumbruch herstellen

XML-Dokumente haben den Vorteil, dass sie sowohl maschinell erfasst werden können also auch durch das menschliche Auge in den meisten Fällen gut verarbeitet werden können. Dies fällt jedoch umso leichter, wenn der Inhalt des Dokuments einigermaßen strukturiert ausgegeben wird – also mit Zeilenumbrüchen und Einrückungen. Das vorherige Beispiel enthält keine Zeilenumbrüche, was bei dem Hauptelement jedoch hilfreich wäre, da ja dazwischen noch einige weitere Informationen eingefügt werden sollen. Also fügen wir dazwischen einen Zeilenumbruch hinzu, was wir mit dem Element erledigen:

<xsl:stylesheet version="1.0" ...>
   <xsl:template match="/">
     <Bestellverwaltung>
         <xsl:text>
</xsl:text>
     </Bestellverwaltung>
   </xsl:template>
</xsl:stylesheet>

Damit erhalten wir nun im Zieldokument:

<xml version="1.0" encoding="UTF-16">
<Bestellverwaltung xmlns="http://www.w3.org/TR/REC-html40">
</Bestellverwaltung>

Das ist viel besser – darauf können wir aufbauen! Das Element schließt übrigens auszugebenden und zu interpretierenden Text ein. Würden Sie den Ausdruck genau wie etwa einfach in die .xslt-Datei schreiben, würde dies nicht korrekt als Zeilenumbruch interpretiert werden. Deshalb schließen Sie Platzhalter für ASCII-Zeichen wie Chr(10), hier als definiert (a ist hexadezimal und steht für 10, x füllt die zweistellige Hexadezimalzahl auf) in das -Element ein.

Kommentare

Wenn Sie während der Erstellung eines .xslt-Dokuments Bereiche auskommentieren wollen, finden Sie dazu ein eigenes Element. Dieses heißt comment und wird beispielsweise wie folgt eingesetzt:

<xsl:comment>
... auszukommentierender Bereich
</xsl:comment>

Daten aus dem Originaldokument ausgeben

Nun wollen wir endlich auf die Daten in unserem Ausgangsdokument zugreifen, das wir transformieren wollen. Bereits jetzt wird offensichtlich, dass es eher eine Neuerstellung eines Dokuments ist als eine Transformation, denn wir müssen wohl für jedes einzelne gewünschte Element festlegen, ob und wo wir es platzieren wollen.

Wenn Sie nur den Inhalt eines bestimmten Elements des Ausgangsdokuments ausgeben wollen, verwenden Sie dazu das value-of-Element. Dieses erwartet mit dem select-Attribut die Angabe des betroffenen Elements, das Sie wiederum mit einem XPath-Ausdruck definieren.

Wir möchten einfach den Namen der ersten Kategorie in unserem Ausgangsdokument ermitteln. Damit wir wissen, von welchem Aufbau wir beim Auslesen des Dokuments reden, haben wir dieses auszugsweise in Listing 3 abgebildet. Dieses Dokument wird mit der Prozedur TestTransformieren aus den beiden Tabellen tblKategorien und tblArtikel der Beispieldatenbank erzeugt, die wir weiter oben vorgestellt haben.

<xml version="1.0" encoding="UTF-8">
<dataroot xmlns:od="urn:schemas-microsoft-com:officedata" generated="2016-07-09T10:30:28">
   <tblKategorien>
     <KategorieID>1</KategorieID>
     <Kategoriename>Getränke</Kategoriename>
     <Beschreibung>Alkoholfreie Getränke, Kaffee, Tee, Bier</Beschreibung>
     <Abbildung>...</Abbildung>
     <tblArtikel>
       <ArtikelID>1</ArtikelID>
       <Artikelname>Chai</Artikelname>
       <LieferantID>1</LieferantID>
       <KategorieID>1</KategorieID>
       <Liefereinheit>10 Kartons x 20 Beutel</Liefereinheit>
       <Einzelpreis>9</Einzelpreis>
       <Lagerbestand>39</Lagerbestand>
       <BestellteEinheiten>0</BestellteEinheiten>
       <Mindestbestand>10</Mindestbestand>
       <Auslaufartikel>0</Auslaufartikel>
     </tblArtikel>
     <tblArtikel>
       <ArtikelID>2</ArtikelID>
       <Artikelname>Chang</Artikelname>
       ...
     </tblArtikel>
     ...
   </tblKategorien>
   <tblKategorien>
     <KategorieID>2</KategorieID>
     <Kategoriename>Gewürze</Kategoriename>
     <Beschreibung>Süße und saure Soßen, Gewürze</Beschreibung>
     <Abbildung>...</Abbildung>
     <tblArtikel>
       <ArtikelID>3</ArtikelID>
       <Artikelname>Aniseed Syrup</Artikelname>
       ...
     </tblArtikel>
     ...
   </tblKategorien>
   ...
</dataroot>

Listing 3: Ausgangsdokument für unsere Experimente

Wir möchten also nun auf den Inhalt des Elements Kategoriename unterhalb von dataroot und tblKategorien zugreifen. Dazu fügen wir unserem Dokument nun einfach eine Zeile mit dem value-of-Element und der Angabe des gesuchten Elements, also dataroot/tblKategorien/Kategoriename hinzu:

...
<Bestellverwaltung>
   <xsl:text>
</xsl:text>
   <xsl:value-of select="dataroot/tblKategorien/Kategoriename"/>
   <xsl:text>
</xsl:text>
</Bestellverwaltung>
...

Dies liefert nun ein XML-Dokument mit folgendem Inhalt:

<xml version="1.0" encoding="UTF-16">
<Bestellverwaltung xmlns="http://www.w3.org/TR/REC-html40">
Getränke
</Bestellverwaltung>

Oh – das ist zwar ein gültiges XML-Dokument, aber wir wollen die Kategorie natürlich in ein eigenes Element stecken.

Dazu fügen wir einfach ein paar statische Elemente zum .xslt-Dokument hinzu, sodass wir sowohl ein Kategorie– als auch ein Kategoriename-Element erhalten:

...
<xsl:template match="/">
   <Bestellverwaltung>
     <xsl:text>
</xsl:text>
     <Kategorie>
       <xsl:text>
</xsl:text>
       <Kategoriename>
         <xsl:value-of  select="dataroot/tblKategorien/Kategoriename"/>
       </Kategoriename>
       <xsl:text>
</xsl:text>
     </Kategorie>
     <xsl:text>
</xsl:text>
   </Bestellverwaltung>
</xsl:template>
...

Innerhalb der .xslt-Datei haben wir überall dort, wo Zeilenumbrüche eingefügt werden sollen, das Element integriert. Einrückungen in Form von Leerzeichen oder Tab-Zeichen brauchen Sie übrigens nicht einzufügen, denn diese werden automatisch entsprechend der Struktur des XML-Dokuments erzeugt. Diese dienen also eher der übersicht für das menschliche Auge.

Das Ergebnis sieht schon eher nach XML aus:

<xml version="1.0" encoding="UTF-16">
<Bestellverwaltung xmlns="http://www.w3.org/TR/REC-html40">
   <Kategorie>
     <Kategoriename>Getränke</Kategoriename>
   </Kategorie>
</Bestellverwaltung>

Attributwerte transformieren

Wenn das Ausgangsdokument Elemente mit Attributen enthält, auf die Sie per XSLT zugreifen wollen, gelingt dies ebenfalls mit dem value-of-Element. Sie müssen hier nur den entsprechenden select-Ausdruck für ein Attribut verwenden. Auf die ArtikelID in greifen Sie etwa mit einem Element wie dem folgenden zu:

<xsl:value-of select="Artikel/@ArtikelID">

Elemente per Schleife durchlaufen

Allerdings haben wir nun erst eine einzige Kategorie in das Zieldokument übertragen. Wie gelangen wir an die übrigen

Sie ahnen es bereits: Genau, wie es ein Element gibt, mit dem man auf das Ausgangselement zeigen kann (), und ein Element, mit dem Sie den Inhalt eines bestimmten Elements ermitteln können, liefert XSD auch ein Element, mit dem sich eine Schleife ähnlich wie unter VBA abbilden lässt. Dieses Element entspricht der For Each-Schleife unter VBA und es durchläuft alle Elemente des angegebenen Namens, die sich im aktuellen Kontext befinden.

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