4Max/shutterstock.com

11. Oktober 2011 / von Benjamin Rank

Scala
&
XML

XML wird auch in Zeiten von JSON noch immer in vielen Projekten eingesetzt und wird es auch in Zukunft noch werden. Somit kommt es häufig vor, dass man XML Dateien erstellen, analysieren und verarbeiten muss. Scala ist eine der modernen Programmiersprachen auf Basis der JVM, die aktuell viel Zuspruch finden. Eine Besonderheit von Scala ist die Integration von XML als nativer Sprachbestandteil. Ein Grund sich die Möglichkeiten näher anzusehen, die Scala bei der XML Verarbeitung bietet, ohne in die kontroverse Diskussion einzusteigen, ob die Behandlung eines speziellen Dateiformats wie XML in eine Programmiersprache integriert sein sollte.

Integration von XML in Scala

Wirft man einen Blick auf die Integration von XML in Scala, stellt man fest, dass XML Literale direkt in den Compiler integriert sind, die Funktionen zur weiteren Bearbeitung aber in Bibliotheken implementiert werden. Schreibt man im Scala Code also Folgendes:

val xmlLiteral = <xml><content>Ein XML Literal</content></xml>

dann wird dem Wert

xmlLiteral

das XML zugewiesen. Der Compiler stellt dabei sicher, dass es sich um wohlgeformtes XML handelt und würde einen Fehler melden, wenn das XML Markup nicht korrekt wäre. In Scala geht man bei der Verarbeitung von XML nicht den Umweg über die Repräsentation von XML als String, bei der Fehler im XML erst während der Ausführung entdeckt werden können, wenn der String geparst wird.

Die XML Integration wird aber erst dadurch richtig interessant, dass in die XML Literale Scala Code eingebettet werden kann. Damit ist es möglich einen Template Mechanismus umzusetzen, bei dem das Ergebnis des Scala Codes zur Laufzeit in das XML Literal eingefügt wird. Das folgende Beispiel wandelt ein Mapping von IDs auf Filmtitel in ein XML Dokument um.

println(createMovies(
  Map(
    1 -> "Fluch der Karibik",
    2 -> "Die Bourne Identität",
    3 -> "Herr der Ringe: Die Gefährten",
    4 -> "Avatar - Aufbruch nach Pandora")))

def createMovies(input: Map[Int, String]): Node = {
  <movies>{
    input map ({ (entry) =>
      <movie id={ entry._1.toString }>
        <title>{ entry._2 }</title>
      </movie>
    })
  }</movies>
}

Im ersten Teil wird die Map definiert und mit den Testdaten gefüllt. Die Umwandlung geschieht in der Methode „createMovies“. Diese bekommt die Map übergeben und gibt das daraus erzeugte XML zurück.

Wie man sieht erfolgt die Einbettung des Scala Codes in das XML Template mit geschweiften Klammern. Die

createMovie

Funktion definiert im Prinzip nur ein XML Literal

movie

, das dynamisch mit Inhalt gefüllt wird. Zur Umwandlung der Map in eine Liste aus XML Knoten wird die Methode

map

verwendet.

map

wendet die übergebene Funktion auf jedes Element der Map an und fügt die Ergebnisse zu einer Liste zusammen. In diesem Beispiel ist das Ergebnis eine Liste von XML Literalen

movie

. Ein Element einer Scala Map ist ein Tupel der Form

(key, value)

. Mit

._1

greift man auf den ersten Wert (

key

) und mit

._2

auf den zweiten Wert (

value

) des Tupels zu.

Das Beispiel zeigt, dass sowohl Textknoten, Attributwerte als auch ganze Listen von Knoten dynamisch in ein XML Literal eingefügt werden können. Die Ausführung des beschriebenen Codes erzeugt das folgende XML:

<movies>
  <movie id="1">
    <title>Fluch der Karibik</title>
  </movie>
  <movie id="2">
    <title>Die Bourne Identität</title>
  </movie>
  <movie id="3">
    <title>Herr der Ringe: Die Gefährten</title>
  </movie>
  <movie id="4">
    <title>Avatar - Aufbruch nach Pandora</title>
  </movie>
</movies>

Das Package scala.xml

Die Klassen, die für die XML-Verarbeitung mit Scala verwendet werden, sind im Package

scala.xml

definiert. Am häufigsten werden davon die Klassen

NodeSeq

,

Node

und

Elem

verwendet, die durch eine besondere zirkuläre Vererbungshierarchie verbunden sind:

Seq[Node] &lt;- NodeSeq &lt;- Node &lt;- Elem

Dadurch verhält sich ein einzelner Knoten (

Node

) immer so wie eine Liste von Knoten (

NodeSeq

), die nur ein einzelnes Element enthält. Angenehem ist dabei ist die Tatsache, dass die Vererbungshierarchie bei der Scala Collection

Seq

beginnt, so dass für die XML-Verarbeitung die mächtigen Funktionen der Scala Collection API zur Verfügung stehen (z.B.

filter

,

map

,

head

, usw.)

Sowohl

Node

als auch

NodeSeq

sind abstrakte Typen. Die Literale, die der Scala Compiler erzeugt, sind vom konkreten Typ

Elem

.

Verarbeiten von XML

Nachdem wir gesehen haben, wie XML mit Scala erstellt werden kann, widmen wir uns der Frage, wie XML geparst werden kann. Dazu betrachten wir das etwas erweiterte XML der Filme und filtern alle Filme heraus, die im Jahr 2003 oder später veröffentlicht worden sind.

<movies>
  <movie id="1">
    <title>Fluch der Karibik</title>
    <year>2003</year>
    <director>Gore Verbinski</director>
    <actors>
      <actor>Jonny Depp</actor>
      <actor>Orlando Bloom</actor>
      <actor>Keira Knightley</actor>
    </actors>
  </movie>
  <movie id="2">
    <title>Die Bourne Identität</title>
    <year>2002</year>
    <director>Doug Liman</director>
    <actors>
      <actor>Matt Damon</actor>
      <actor>Franka Potente</actor>
      <actor>Chris Cooper</actor>
    </actors>
  </movie>
  <movie id="3">
    <title>Herr der Ringe: Die Gefährten</title>
    <year>2001</year>
    <director>Peter Jackson</director>
    <actors>
      <actor>Elijah Wood</actor>
      <actor>Ian McKellen</actor>
      <actor>Orlando Bloom</actor>
    </actors>
  </movie>
  <movie id="4">
    <title>Avatar - Aufbruch nach Pandora</title>
    <year>2009</year>
    <director>James Cameron</director>
    <actors>
      <actor>Sam Worthington</actor>
      <actor>Zoe Saldana</actor>
      <actor>Sigourney Weaver</actor>
    </actors>
  </movie>
</movies>

Die folgende Scala Funktion

filterXml

setzt dies um. Als erstes parst sie die XML Datei und weist das XML dem Wert

xml

zu. Dabei wird das komplette XML baumstrukturähnlich als

NodeSeq

-Objekte im Speicher abgelegt.

xml

bekommt vom Compiler durch Type Inference den Typ

Elem

zugewiesen so als hätte man es direkt im Scala Code als XML Literal eingefügt. Der Übersichtlichkeit halber sind in diesem Beispiel die Typen einiger Variablen angegeben, was aber nicht unbedingt nötig ist, da sie der Scala Compiler bestimmen kann.

def filterXml() = {
  val file = Source.fromFile("movies.xml")("UTF-8")
  val xml:Elem = XML.load(file.reader)

  val movies:NodeSeq = xml  "movie"
  val filteredMovies:NodeSeq = movies filter ({ movie =>
    val y = movie  "year"
    y.head match {
      case <year>{ Text(year) }</year> => { year.toInt >= 2003 }
      case _ => false
    }
  })
  println(filteredMovies)
}

Die weitere Verarbeitung beginnt mit einer XPath-ähnlichen Schreibweise alle

movie

-Knoten innerhalb des XML zu suchen und in der Konstanten

movies

zu speichern (movies erhält dabei den Typ

NodeSeq

). Prinzipiell ist „“ nur eine Methode, die auf dem Typ

NodeSeq

definiert ist und den Parameter

"movie"

übergeben bekommt. Die Schreibweise ist möglich, da Scala es erlaubt bei Methoden mit nur einem Parameter den von Java gewohnten Punkt und die Klammern wegzulassen. Diese Zeile könnte also mit der gleichen Funktionalität auch folgendermaßen geschrieben werden:

val movies = xml.("movie")

Zur Filterung der XML Knoten wird auf die von

Seq

definierte Methode

filter

zurückgegriffen. Diese bekommt als Parameter eine Funktion vom Typ

(Node =&gt; Boolean)

, die auf jedes Element der Liste angewendet wird. Ergibt die Ausführung der Funktion

true

ist das Element in der resultierenden Liste enthalten.

In diesem Fall ist die Funktion so implementiert, dass sie für einen

movie

-Knoten genau dann

true

zurückgibt, wenn das Produktionsjahr größer oder gleich 2003 ist. Dazu wird als erstes mit dem „“-Operator (ebenfalls einer Methode von

NodeSeq

) das Element

year

unterhalb von

movie

selektiert und in

y

gespeichert. Auch hier wird eine

NodeSeq

zurückgegeben. Aus der Struktur des XML ergibt sich aber, dass diese Liste genau ein Element enthalten muss, auf das mit der Methode

head

(definiert auf

Seq

) zugegriffen wird.

Nebenbei sei erwähnt, dass diese Filterung auch mit Attributen statt Knotennamen funktioniert. Wollte man z.B. auf die ID eines Films statt auf das Jahr zugreifen, dann schriebe man vor dem Attributnamen ein „@“:

val id = movie  "@id"

Um den Wert aus dem Tag zu extrahieren wird Pattern Matching angewendet, bei dem in Scala auch XML Literale verwendet werden können. Der Matcher übernimmt es, die Variable

year

mit dem Wert zu füllen, wenn es möglich ist, so dass diese in dem behandelnden Code weiterverwendet werden kann. Sollte wieder erwarten das XML Literal

year

nicht gematcht werden können, dann wird in allen anderen Fällen

false

zurückgegeben. Die Ausführung der Methode erzeugt das folgende XML, das nur die

movie

-Knoten mit Filmen neuer als 2003 enthält.

<movie id="1">
  <title>Fluch der Karibik</title>
  <year>2003</year>
  <director>Gore Verbinski</director>
  <actors>
    <actor>Jonny Depp</actor>
    <actor>Orlando Bloom</actor>
    <actor>Keira Knightley</actor>
  </actors>
</movie>
<movie id="4">
  <title>Avatar - Aufbruch nach Pandora</title>
  <year>2009</year>
  <director>James Cameron</director>
  <actors>
    <actor>Sam Worthington</actor>
    <actor>Zoe Saldana</actor>
    <actor>Sigourney Weaver</actor>
  </actors>
</movie>

Ein paar Worte zum Schluss

Wir haben gesehen, wie XML in Scala integriert ist, wie XML mit Scala erzeugt werden kann und wie die Klassen aus dem Paket

scala.xml

verwendet werden können. Eine weitere Möglichkeit XML zu verarbeiten stetllt das XML Binding dar. Dies wird in Scala mit der externen Bibliothek scalaxb möglich. Damit können aus einem XML Schema Scala Klassen generiert werden, um damit XML zu parsen oder zu erzeugen.

Eine interessante Alternative zum in Scala integrierten

scala.xml

ist die Bibliothek Anti-XML. Die Entwickler haben sich zum Ziel gesetzt eine einfacher zu benutzende API und eine bessere Performance zu bieten als

. Außerdem bieten sie ein interessantes Konzept namens „Zippers“ an, mit dem auf einfache Weise Teile eines XML Baums ersetzt werden können.

Wenn die XML Dokumente größer werden, dann stoßen die Verfahren, bei denen das XML komplett in den Speicher geladen werden muss, an die (Speicher-)Grenzen. In diesem Fall muss man auf eine StAX ähnliche Verabeitung des XMLs umsteigen. Wie dies mit Scala-Bibliotheken möglich ist, schauen wir uns in einem folgenden Blogeintrag an.

Autor

Benjamin Rank Benjamin ist seit Januar 2011 bei der Accso GmbH tätig und beschäftigt sich aktuell mit den Möglichkeiten, die Scala bei der Softwareentwicklung bietet.
Weitere Artikel

Das könnte Sie auch interessieren