Groovy: XML

Aus Wikibooks

Wenn man bedenkt, dass die Auszeichnungssprache XML kaum zehn Jahre alt ist, kommt es einem doch erstaunlich vor, wie allgegenwärtig sie heutzutage in der Informationstechnik ist, und das völlig unabhängig von den verwendeten Programmiersprachen und Systemplattformen. Da man also in der Programmierung ständig mit XML zu tun hat, ist es auch wichtig, dafür effektive Hilfsmittel zur Hand zu haben. Leider sind die in Java integrierten APIs nach dem W3C-Standard alles andere als angenehm zu handhaben. Groovy umfasst daher eine ganze Reihe von Werkzeugen, die den Umgang mit XML erleichtern. Wir wollen uns hier auf diejenigen beschränken, bei denen die dynamischen Möglichkeiten von Groovy am effektivsten eingesetzt werden.

XML-Dokumente als Objektstruktur[Bearbeiten]

XML-Dokumente führen bei der Programmierung oft ein Doppelleben: Sie existieren einerseits in der Form einer Textdatei, in der die Auszeichnungen mittels vieler spitzer Klammern eingefügt sind. Andererseits gibt es sie aber auch innerhalb eines Programms als interne Datenstruktur, die die Struktur der verschachteln Auszeichnungen im Dokument widerspiegelt. Standardmäßig verwendet man in Java dazu das org.w3c.dom.Document, das natürlich auch mit Groovy verwendet werden kann und sogar durch eine Kategorienklasse namens groovy.xml.dom.DOMCategory handlicher wird. Wir wenden uns hier aber einer anderen Option zu.

Um ein Beispiel zu haben, bleiben wir gleich bei der Geografie und legen eine XML-Datei mit Staaten an. Um der hierarchischen Struktur von XML-Dokumenten Rechnung zu tragen, gruppieren wir sie jetzt aber nach Kontinenten und tragen zu jedem Staat noch ein paar wichtige Städte mit ihren Einwohnerzahlen ein.[1] Wir speichern die Daten einer Datei namens staaten.xml im aktuellen Verzeichnis, wie es im folgenden Beispiel zu sehen ist.

<? xml version="1.0" ?>
<staaten>
  <kontinent bez="Afrika">
    <staat bez="Ägypten" vorwahl="20" feiertag="1922-02-28">
      <stadt bez="Kairo" einwohner="7734614"/> 
      <stadt bez="Alexandria" einwohner="3811516"/>
      <stadt bez="Gizeh" einwohner="2443203"/>
      <stadt bez="Schubra al-Chaima" einwohner="991801"/>
    </staat>
    <staat bez="Algerien" vorwahl="213" feiertag="1962-03-18">
      <stadt bez="Algier" einwohner="1518083"/>
      <stadt bez="Oran" einwohner="771066"/>
    </staat>
    <staat bez="Angola" vorwahl="244" feiertag="1975-11-11">
      <stadt bez="Luanda" einwohner="2776125"/> 
      <stadt bez="Huambo" einwohner="226177"/>
      <stadt bez="Lobito" einwohner="207957"/>
    </staat>
  </kontinent>
  <kontinent bez="Asien">
    <staat bez="Afghanistan" vorwahl="93">
      <stadt bez="Kabul" einwohner="2536300"/>
      <stadt bez="Herat" einwohner="349000"/> 
      <stadt bez="Kandahar" einwohner="324800"/> 
      <stadt bez="Masar-e Scharif" einwohner="300600"/>
    </staat>
  </kontinent>
  <kontinent bez="Europa">
    <staat bez="Albanien" vorwahl="355" feiertag="1912-11-28">
      <stadt bez="Tirana" einwohner="347801"/> 
      <stadt bez="Durrës" einwohner="122034"/> 
    </staat>
    <staat bez="Andorra" vorwahl="376" feiertag="1278-09-08">
      <stadt bez="Andorra la Vella" einwohner="22884"/>
    </staat>
  </kontinent>
  <kontinent bez="Nordamerika">
    <staat bez="Antigua und Barbuda" vorwahl="1268" feiertag="1981-11-01">
      <stadt bez="Saint John's" einwohner="25150"/> 
    </staat>
  </kontinent>
</staaten>

Um diese Daten nun im Programm nutzbar zu machen, gibt es verschiedene Möglichkeiten. Sie unterscheiden sich im Ergebnis des Parse-Prozesses und in der Strategie.

XmlParser[Bearbeiten]

Eine sehr einfache Möglichkeit besteht darin, den XML-Parser von Groovy einzusetzen. Es kostet Sie genau eine Zeile:

def staaten = new XmlParser().parse(new File('staaten.xml'))

Die Klasse XmlParser befindet sich im Package groovy.util, daher brauchen Sie keinen Pfad angeben. Das Ergebnis des parse()-Aufrufs ist eine Baumstruktur aus Knotenobjekten. Das heißt, die Variable staaten verweist auf ein Objekt der Klasse groovy.util.Node, an dem alle weiteren Elemente des Baums »hängen«.

Es handelt sich hier um die gleiche Art von Struktur, die auch programmintern mit einem NodeBuilder erzeugt werden kann (siehe Kapitel Objekte).

Prüfen wir in einer kurzen Schleife, ob alle Staaten aus der XML-Datei angekommen sind.

staaten.kontinent.each { kont ->
  println kont.'@bez'
  kont.staat.each {staat ->
    println "- ${staat.'@bez'} ${staat.'@feiertag'}"
  }
}

Das Ergebnis gibt uns Recht:

Afrika
- Ägypten 1922-02-28
- Algerien 1962-03-18
- Angola 1975-11-11
Asien
- Afghanistan null
Europa
- Albanien 1912-11-28
- Andorra 1278-09-08
Nordamerika
- Antigua und Barbuda 1981-11-01

Zur Erinnerung: der Property-Zugriff bei Node-Objekten liefert immer eine Liste aller untergeordneten Elemente mit dem angegebenen Namen. Um ein Attribut abzufragen, muss dem Attributnamen ein @-Zeichen vorangestellt werden und beides muss in Anführungszeichen gesetzt werden. Wir brauchen an dieser Stell nicht tiefer in diese Art von Strukturen eingehen; lesen Sie bei Bedarf einfach noch einmal im Kapitel Objekte nach.

XmlNodePrinter[Bearbeiten]

Wenn Sie eine solche Baumstruktur im Programm haben und als XML-Dokument abspeichern möchten, so ist dies auch nicht schwierig.

np = new XmlNodePrinter()
np.print(staaten)

Der groovy.util.XmlNodePrinter funktioniert analog zu dem aus Kapitel Neue Konzepte bekannten NodePrinter. Sie übergeben seiner print()-Methode einen Wurzelknoten, und er gibt die gesamte Struktur ordentlich formatiert auf der Konsole aus. Wenn Sie den XmlNodePrinter mit einem PrintWriter als Argument instanziieren, dann wird das Ergebnis in diesen hineingeschrieben.

new File('staaten2.xml').withPrintWriter { writer ->
  new XmlNodePrinter(writer).print(staaten)
}

Und schon haben Sie die Daten in einer neuen XML-Datei staaten2.xml. Einschränkend ist zu erwähnen, dass der XmlNodePrinter keine Kopfzeilen in die XML-Datei schreibt und demzufolge schlecht mit Zeichen umgehen kann, die nicht 7-Bit-ASCII sind, denn dafür bräuchte man ja die Kopfzeile mit der Zeichensatz-Deklaration. Wenn man ganz ordnungsgemäße Ausgabedateien benötigt, muss man derzeit wohl doch noch auf die standardmäßigen Mittel in den Java-APIs zurückgreifen.

XML on the Fly verarbeiten[Bearbeiten]

Alle Daten eines XML-Dokuments in einer internen Struktur zu halten ist nicht in jedem Fall sinnvoll, insbesondere wenn die Daten sehr umfangreich sind und wenn nur ein kleiner Teil von ihnen von Interesse ist oder eine sequenzielle Verarbeitung möglich ist.

Zum sequentiellen Einlesen von XML-Daten ist der ereignisorientierte SAX-Parser geeignet, der Bestandteil der Java-Bibliotheken ist und dem Groovy nicht viel hinzufügen kann. Für das programmgesteuerte Erzeugen von XML-Dokumenten kann jedoch hervorragend auf das Konzept der Groovy-Builder zurückgegriffen werden.

Der MarkupBuilder[Bearbeiten]

Die Klasse groovy.xml.MarkupBuilder ermöglicht es, XML-Dokumente in der semi-deklaratorischen Manier der Groovy-Builder zusammenzusetzen, wie wir sie in Builder behandelt haben. Wenn Sie ein Dokument erzeugen möchten, das die Informationen unseres obigen Beispiels staaten.xml enthält, können Sie so vorgehen:

new groovy.xml.MarkupBuilder().staaten {
  kontinent(bez:'Afrika') {
    staat (bez:'Ägypten',vorwahl:20,feiertag:'1922-02-28') {
      stadt(bez:'Kairo',einwohner:'7734614')
      stadt (bez:'Alexandria',einwohner:'3811516')
      stadt (bez:'Gizeh',einwohner:'2443203')
      stadt (bez:'Schubra al-Chaima',einwohner:'991801')
    }
    staat (bez:'Algerien',vorwahl:'213',feiertag:'1962-03-18') {
      stadt (bez:'Algier',einwohner:'1518083')
      stadt (bez:'Oran',einwohner:'771066')
    }
    // Und so weiter...
  }
}

Wieder erscheinen Kontinente, Länder und Städte auf dem Bildschirm. Auch dem MarkupBuilder können Sie einen Writer mitgeben (ein PrintWriter ist nicht erforderlich), so dass Sie die Daten beispielsweise in eine Datei schreiben können.

new File('staaten3.xml').withWriter { writer ->
  new groovy.xml.MarkupBuilder(writer).staaten {
    ...
  }
}

Sie können an dem Beispiel leicht erkennen, dass der MarkupBuilder einfach Methodenaufrufe durch Elemente gleichen Namens ersetzt und aus dem benannten Parametern analoge Element-Attribute macht. Zwei spezielle Situationen wollen wir noch erwähnen, zu denen die Lösung vielleicht nicht so offensichtlich ist.

Mixed Content[Bearbeiten]

Erstens gibt es in XML auch reine Textelemente wie etwa der folgende kurze Absatz aus einem DocBook-Dokument:

<para>Dies ist ein kurzer Absatz.</para>

So etwas können Sie mit dem MarkupBuilder darstellen, indem Sie einfach den Text als String der Methode für das Tag übergeben:

para "Dies ist ein kurzer Absatz."

Schwieriger wird es, wenn eine Mischung aus Texten und Tags auftritt, wie etwas hier:

<para>Dies ist ein <emphasis>kurzer</emphasis> Absatz.</para>

Da kommen Sie gegenwärtig mit dem einfachen MarkupBuilder nicht weiter. Stattdessen bietet es sich an, auf den StreamingMarkupBuilder umzusteigen, auf den wir etwas weiter unten zu sprechen kommen.

para { mkp.yield 'Dies ist ein' emphasis 'kurzer' mkp.yiels ' Absatz.' }

Spezielle Zeichen in Namen[Bearbeiten]

Zweitens sind in XML Element- und Attributnamen erlaubt, die in Groovy-Namen nicht vorkommen können, z.B. Bindestriche, Punkte und Doppelpunkte. Zum Glück ist es aber in Groovy immer möglich, Member-Namen als Strings anzugeben, und die dürfen beliebige Zeichen enthalten. Das folgende Beispiel gibt einen Ausschnitt aus einer XSLT-Datei für DocBook-Dokumente aus:

builder = new groovy.xml.MarkupBuilder()
builder.'xsl:template' (match:'para') {
  'fo:block' ('xsl:user-attribute-sets':'normal-text') {
    'xsl:apply-templates'()
  }
}

Wenn Sie das Skript laufen lassen, erscheint ganz korrekt diese Ausgabe:

<xsl:template match='para'>
  <fo:block xsl:user-attribute-sets='normal-text'>
    <xsl:apply-templates />
  </fo:block>
</xsl:template>

Der StreamingMarkupBuilder[Bearbeiten]

Neben dem MarkupBuilder, den wir eben kennen gelernt haben, gibt es eine zweite Klassen, mit der XML-Dokument in ähnlicher Weise zusammengebaut werden können: der StreamingMarkupBuilder. Er ist in erster Linie dafür vorgesehen, XML-Code für die Verarbeitung durch Maschinen zu produzieren, und verfügt über einige zusätzliche Möglichkeiten. So gelingt es uns mit dem StreamingMarkupBuilder auch, XML-Code zu Produzieren, in dem Text und Elemente gemischt sind. Das Beispiel aus dem obigen Abschnitt »Mixed Content« etwa lässt sich so darstellen:

builder = new groovy.xml.StreamingMarkupBuilder()
writableClosure = builder.bind {
  para {
    out << 'Dies ist ein '
    emphasis 'kurzer'
    out << 'Absatz.'
  }
}

println writableClosure

Wie Sie sehen, ist das Vorgehen hier etwas anders: Sie rufen eine Methode bind() auf, der die XML-Struktur in der Form einer Closure übergeben werden muss. Das Ergebnis ist wiederum eine Closure. Anders als beim MarkupBuilder wird nämlich der XML-Code nicht sofort produziert und in einen Writer geschrieben, sondern es entsteht ein Closure-Objekt, das das Interface Writable implementiert (siehe Kapitel Das Groovy-JDK). Erst wenn dieses Objekt herausgeschrieben wird (wie hier in der println()-Methode), entsteht der eigentliche XML-Text. Das Ergebnis sieht so aus:

<para>Dies ist ein <emphasis>kurzer</emphasis>Absatz.</para>

Das Ergebnis sieht nicht so schön strukturiert aus wie beim einfachen MarkupBuilder. Dies liegt insofern nahe, als das Ergebnis von einer Maschine gelesen werden soll, die sich für ordentliches Einrücken nicht interessiert und die zusätzlichen Leer- und Tabulatorzeichen möglicherweise falsch verarbeitet.

Wie oben zu sehen ist, ermöglicht das Mischen von Text und Elementen eine spezielle Operation mit der Property out und dem <<-Operator. Es gibt noch einige weitere derartige Operationen, die es insgesamt ermöglichen, eine komplette XML-Datei zu generieren.

Spezielle Operationen im StreamingMarkupBuilder
Beispiel Bedeutung
comment << "Kommentartext" Fügt einen Text als XML-Kommentar ein.
namespaces << [Präfix:"URI"] Definiert XML-Namensräume. In der übergebenen Map werden die Schlüssel als Präfixe und die Werte als zugehörige URIs betrachtet.
out << "Text" Fügt den angegebenen Text mit spezieller Behandlung von XML-Sonderzeichen ein.
unescaped << "Text" Fügt den angegebenen Text ohne spezielle Behandlung von XML-Sonderzeichen ein.
pi << [PItarget:"Instruction"] Fügt eine oder mehrere XML-Verarbeitungsanweisungen (processing instruction) in die Ausgabe ein.
mkp.xmlDeclaration() Fügt eine vollständige XML-Deklaration als Kopfzeile ein.

Die letzte Zeile der Tabelle ##TQV 8-2## ist noch erklärungsbedürftig. Im StreamingMarkupBuilder können Namensraum-Präfixe wie Properties verwendet werden (d.h. pre.element() wird beispielsweise in <pre:element/> umgesetzt). Dabei ist mkp ein vordefinierter Namensraum mit einer speziellen Funktionalität: So führt der Aufruf mkp.xmlDeclaration() nicht dazu, dass das Tag <mkp:xmlDeclaration/> </nowiki>eingefügt wird, sondern dass dem XML-Dokument eine XML-Deklarationszeile (z.B. <?xml version="1.0" encoding="UTF-8"?>) vorangestellt wird.

In der Groovy-Distribution gibt es eine in Groovy geschriebene Klasse namens org.codehaus.groovy.tools.DocGenerator, die eine HTML-Seite mit einer Übersicht aller vordefinierten Methoden generiert und als Beispiel für die Verwendung des StreamingMarkupBuilder dienen kann.

Weitere Möglichkeiten[Bearbeiten]

Was man mit Groovy und XML noch alles anfangen kann, übersteigt bei weitem die Reichweite dieses Buchs. Aber zumindest wollen wir als Anregung darauf hinweisen, was es da noch so gibt.

  • Der XmlSlurper ist eine Alternative zum XmlParser. Er baut keine Struktur aus Node-Objekten auf, sondern liefert als Ergebnis ein GPathResult-Objekt, dessen Daten zwar effizienter ausgelesen werden können, das aber nur lesenden Zugriff erlaubt. Er lässt sich gut mit dem StreamingMarkupBuilder zur durchlaufenden Verarbeitung von XML-Datenströmen kombinieren.
  • Die Kategorienklasse DOMCategory erleichtert etwas die Arbeit mit klassischen DOM-Objektbäumen.
  • Der SAXBuilder ist eine Builder-Klasse, die XML-Dokumente derart erzeugt, dass sie SAX-Ereignisse auslöst. Ergänzend gibt es auch einen StreamingSAXBuilder.
  • DOMBuilder ist ein Builder, mit dem man DOM-Strukturen erzeugen kann. Auch hierzu gibt es einen StreamingDOMBuilder.

Weitere Informationen können der Groovy-Website entnommen werden, die zum Thema XML recht ausführliche, wenn auch englischsprachige, Informationen bietet.


  1. Alle Angaben stammen aus Wikipedia. Wenn Sie Fehler finden, korrigieren Sie diese am besten gleich an Ort und Stelle unter http://de.wikipedia.org/.