Einstieg

XML ist in der heutigen Softwareentwickung fast nicht mehr wegzudenken. Einsatzgebiete von XML sind z.B.:

  • Konfigurationsdateien
  • Persistenzdateien (z.B. ODF Format)
  • Datenaustausch
  • ...

Die Verarbeitung von XML mit Scala ist Inhalt dieses Kapitels. Um XML mit Scala zu verarbeiten, sind keine zusätzlichen Bibliotheken notwendig, da Scala eine starke Unterstützung für die XML Verarbeitung mitbringt.

XML Literale

Eine wichtige Klasse in der Verarbeitung von XML mit Scala ist scala.xml.Elem . Objekte dieser Klasse sind unveränderlich und halten XML Elemente. Die Zuweisung der XML Daten kann über einfache Zuweisung eines XML Literals erfolgen.

Bei einem XML Literal schreiben wir einfach das XML in den Quelltext ohne Verwendung von Anführungszeichen. Das nachfolgende Beispiel ist ein einfaches XML Literal:

<a><b><c></c></b></a>
itmapa.de - X2H V 0.18

Weisen wir ein XML Literal einer Variablen, ohne Angabe des Typs zu, erhalten wir eine Variable des Typs scala.xml.Elem . Dies kann im Scala Interpreter nachvollzogen werden.

scala> val myXML = <a><b><c></c></b></a>
myXML: scala.xml.Elem = <a><b><c></c></b></a>
itmapa.de - X2H V 0.18

XML Literale können mithilfe von Scala Ausdrücken dynamisch gestaltet werden. Um einen Scala Ausdruck in ein XML-Literal einzubinden, ist dieser in geschweiften Klammern zu setzen (wie eine Blockanweisung in Scala). Die String-Representation des Ausdrucks wird dann in den XML Literal eingebaut.

object XMLLiteralMix {
  def main(args: Array[String]) {
    val myXML = <x><y><z>{calculate(3)}</z></y></x>
    println(myXML)
  }
  
  def calculate(value: Int) : Int = value * value
}
itmapa.de - X2H V 0.18

Als Ergebnis des Programmablaufs wird

<x><y><z>9</z></y></x>
itmapa.de - X2H V 0.18

auf der Systemausgabe ausgegeben.

Möchte man eine geschweifte Klammer im XML Text angeben, so muss dieser im XML Literal doppelt angegeben werden. Der Text {{ führt zu { im XML.

XML in eine Datei speichern

XML in eine Datei zu speichern ist recht einfach. Scala bringt dafür eine einfach zu verwendende Methode von Hause aus mit: scala.xml.XML.save.

Im nachfolgenden Beispiel wird ein scala.xml.Node einfach durch Zuweisung eines XML-Literals erzeugt. Anschließ wird der Node mit der Methode scala.xml.XML.save in eine Datei geschrieben.

object SaveMe {
  def main(args: Array[String]) {
    val myXML : scala.xml.Node = <myTag>Save me, please</myTag>
    scala.xml.XML.save("c:\\ablage\\myXML.xml",myXML,"UTF8",true,null)
  }
}

itmapa.de - X2H V 0.18

Als Ergebnis erhalten wir eine XML-Datei mit folgendem Inhalt:

<?xml version='1.0' encoding='UTF8'?>
<myTag>Save me, please</myTag>
itmapa.de - X2H V 0.18

XML aus einem String generieren

Liegen XML Daten in Form eines String vor, so kann man mithilfe der Methode scala.xml.XML.loadString ein scala.xml.Elem Objekt aus dem String generieren. Dass nachfolgende Beispiel zeigt die Generierung eines Elem Objektes aus einem String.

object StringToXML {
  def main(args: Array[String]) {
    val myXMLString = "<a><b><c>Hello String</c></b></a>"
    val myXML = scala.xml.XML.loadString(myXMLString)
    println(myXML)
  }
}
itmapa.de - X2H V 0.18

Eine XML Datei einlesen

In einem ersten Schritt wollen wir nun eine XML Datei von der Festplatte mit Hilfe von Scala einlesen. Erzeugen Sie dazu folgende Datei namens "test.xml" im Root-Verzeichnis Ihrer Festplatte C:

<?xml version="1.0"?>
<a>
  <b>
    Text
  </b>
</a>
itmapa.de - X2H V 0.18

Das nachfolgende Scala Programm zeigt, wie einfach die soeben erstellte XML-Datei in Scala eingelesen werden kann.

object XMLTest {
  def main(args: Array[String]) {
    val xml = scala.xml.XML.loadFile("C:\\test.xml")
    println(xml)
  }
}

itmapa.de - X2H V 0.18

Als Ergebnis des Programmes wird der Inhalt der XML-Datei ohne Header auf der Systemausgabe ausgegeben:

<a>
  <b>
    Text
  </b>
</a>
itmapa.de - X2H V 0.18

Es ist zu beachten, dass die einzulesende Datei wohlgeformt sein muss. Ist dies nicht der Fall, wird eine org.xml.sax.SAXParseException beim einlesen ausgelöst.

XML auswerten

Grundsätzlich gibt es zwei verschieden Typen von Daten in XML Konstrukten. Der erste Typ ist der Tag Text oder Elementinhalt, der sich zwischen dem einleitenden Tag und dem beendenden Tag befindet. Der zweite Typ sind die Attribute, die im einleitenden Tag (oder in der Tagdefinition ohne explizites beendendes Tag (<Tag />)) definiert werden. Die Auswertung dieser Daten ist der Inhalt dieses Abschnittes.

Tag Text (Elementinhalt) auswerten

Dieser Abschnitt befasst sich mit grundlegenden Möglichkeiten, um XML mit Scala auszuwerten. Im nachfolgenden Beispiel wird zunächst ein Objekt des Typs scala.xml.Elem erzeugt und der Variablen myXml zugewiesen. Mithilfe der Funktion \ (engl. projection function; dt. Projektionsfunktion) ermitteln wir ein Objekt vom Typ scala.xml.NodeSeq, das alle Elemente mit dem Tag, welcher als Parameter übergeben wird, enthält. Abschließend geben wir zunächst die Anzahl der Elemente aus, worauf wir mithilfe einer for-Schleife über die einzelnen Elemente iterieren und mit Hilfe deren Funktion text den Textinhalt des jeweiligen Tags ausgeben.

object MyTest {
  def main(args: Array[String]) {
    val myXml = <a>
                  <b>Text 1</b>
                  <b>Text 2</b>
                  <c>
                    <b>Text 3</b>
                  </c>
                </a>      
      
    val v1 = myXml \ "b"

    println(v1.size)
    for (out <- v1) println(out.text)
  }
}
itmapa.de - X2H V 0.18

Der Ausführung des Programmes führt zu folgender Ausgabe auf der Systemausgabe:

2
Text 1
Text 2    
itmapa.de - X2H V 0.18

Auffallend ist, dass nur die Elemente der ersten Ebene von myXML enthalten sind. Möchten wir alle Tags finden, egal auf welcher Ebene diese sich befinden verwenden wir einfach die Projektionsfunktion \\ und erhalten alle Tags.

object MyTest {
  def main(args: Array[String]) {
    val myXml = <a>
                  <b>Text 1</b>
                  <b>Text 2</b>
                  <c>
                    <b>Text 3</b>
                  </c>
                </a>      
      
    val v1 = myXml \\ "b"

    println(v1.size)
    for (out <- v1) println(out.text)
  }
}
itmapa.de - X2H V 0.18

Der Ausführung des Programmes führt zu folgender Ausgabe auf der Systemausgabe:

3
Text 1
Text 2
Text 3   
itmapa.de - X2H V 0.18

Attribute auswerten

Um die Attribute in einem XML Konstrukt auszuwerten verwenden wir wieder die Projektionsfunktionen \ und \\ . Der Unterschied zur Auswertung des Textes liegt darin, dass wir nicht den Namen des gewünschten Tags übergeben sondern den Namen des gewünschten Attributs. Dieser Unterschied wird hergestellt, indem wir dem Attributnamen als Argument ein @ voranstellen. Möchten wir z.B. Attribute mit dem Namen name auswerten, so übergeben wir der Projektionsfunktion als Argument "@name". Mit der Projektionsfunktion \ werten wir die Attribute der aktuell obersten Ebene aus. Wollen wir alle Ebenen des XML auswerten verwenden wir die Projektionsfunktion \\ .

Im nachfolgenden Beispiel Werten wir das Attribut name auf der obersten Ebene mit der Projektionsfunktion \ aus.

object MyTest {
  def main(args: Array[String]) {
    val myXml = <a name="base 1">
                  <b name="TheText 1">Text 1</b>
                  <b name="TheText 2" id="nameText">Text 2</b>
                  <c name="TagC">
                    <b name="TheText 3">Text 3</b>
                  </c>
                </a>
      
    val v1 = myXml \ "@name"

    println(v1.size)
    for (out <- v1) println(out.text)
  }
}
itmapa.de - X2H V 0.18

Der Ausführung des Programmes führt zu folgender Ausgabe auf der Systemausgabe:

1
base 1
itmapa.de - X2H V 0.18

Das nachfolgende Beispiel ist mit einer Ausnahme identisch mit dem vorherigen. Anstatt der Projektionsfunktion \ wird die Projektionsfunktion \\ verwendet, um alle Attribute name auszuwerten.

object MyTest {
  def main(args: Array[String]) {
    val myXml = <a name="base 1">
                  <b name="TheText 1">Text 1</b>
                  <b name="TheText 2" id="nameText">Text 2</b>
                  <c name="TagC">
                    <b name="TheText 3">Text 3</b>
                  </c>
                </a>
      
    val v1 = myXml \\ "@name"

    println(v1.size)
    for (out <- v1) println(out.text)
  }
}
itmapa.de - X2H V 0.18

Der Ausführung des Programmes führt zu folgender Ausgabe auf der Systemausgabe:

5
base 1
TheText 1
TheText 2
TagC
TheText 3  
itmapa.de - X2H V 0.18

XML erzeugen

Nachdem wir XML Daten ausgewertet haben, wollen wir uns in diesem Abschnitt mit der Erzeugung von XML Daten beschäftigen.

Ein Tag erzeugen

Ein zentraler Bestandteil in der Erstellung von XML Daten spielt in diesem Abschnitt die Klasse scala.xml.Elem. Im nachfolgenden Beispiel erzeugen wir ein einfaches XML Element mit Hilfe dieser Klasse:

scala> import scala.xml._
import scala.xml._

scala> val myElement = Elem(null,"a",Null,TopScope)
myElement: scala.xml.Elem = <a></a>
itmapa.de - X2H V 0.18

Das erste Argument des Konstruktors gibt den Namensraum des zu erstellenden Tags an. Wenn kein expliziter Namensraum gegeben ist, darf kein leerer String übergeben werden, sondern es muss null als Argument übergeben werden. Das zweite Argument gibt den Namen des Tags (Elements) an. Im dritten Argument werden die Attribute des Tags definiert. Sollen keine Argumente im Tag enthalten sein, übergeben wir das Singleton Objekt scala.xml.Null. Das vierte Argument definiert den Gültigkeitsbereich des Tags in unseren Fall scala.xml.TopScope für den Top Level Namensraum.

Attribute verwenden

Im nächsten Schritt möchten wir unserem Tag Attribute mitgeben. Attribute werden mit Elementen des Typs scala.xml.MetaData definiert. MetaData ist eine abstrakte Klasse und muss zunächst konkretisiert werden. Im vorherigen Beispiel haben wir bereits das Singleton Objekt scala.xml.Null verwendet, welche die Klasse MetaData erweitert und in diesem Fall für keine Attribute steht. Folgende Elemente des Packages scala.xml konkretisieren die Klasse MetaData:

  • Attribute
  • Null
  • PrefixedAttribute
  • UnprefixedAttribute

Im nächsten Beispiel verwenden wir die Klasse UnprefixedAttribute, um unserem Tag Attribute mitzugeben. Die Definition eines Attributes ist recht einfach. Im Konstruktor übergeben wir einfach den Namen und den Wert des Attributes. Sollen weitere Attribute übergeben werden, geben wir das entsprechende Attribute im letzten Argument des Konstruktors an. Wenn keine weiteren Attriute vorhanden sind, übergeben wir als letztes Argument im Konstruktor das Singleton Objekt scala.xml.Null. Im nachfolgenden Beispiel definieren wir unser Tag mit zwei Attributen.

import scala.xml._

object XMLAttributes {
  def main(args: Array[String]) {
    
    val attribute2 = new UnprefixedAttribute("name","theName",Null)
    val attribute1 = new UnprefixedAttribute("id","123",attribute2)
    
    val myElem : Elem = Elem(null,"ttt",attribute1,TopScope)
    
    println(myElem)    
  }
}
itmapa.de - X2H V 0.18

Der Ausführung des Programmes führt zu folgender Ausgabe auf der Systemausgabe:

<ttt id="123" name="theName"></ttt>
itmapa.de - X2H V 0.18

Tag Text

In diesem Abschnitt wollen wir unserem Tag Text als Inhalt mitgeben. Das letzte Argument im Konstruktor von scala.xml.Elem ist eine variable Parameterliste des abstrakten Typs scala.xml.Node. Eine Klasse, welche diese Klasse konkretisiert, ist scala.xml.Text. Die Verwendung dieser Klasse ist recht einfach. Man übergibt im Konstruktor einfach den Text, welcher im Tag enthalten sein soll. Im nachfolgenden Beispiel fügen wir unserem Tag ein keinen Textinhalt hinzu.

import scala.xml._

object XMLText {
  def main(args: Array[String]) {
    
    val attribute2 = new UnprefixedAttribute("name","theName",Null)
    val attribute1 = new UnprefixedAttribute("id","123",attribute2)
    
    val text = Text("TagText")
    
    val myElem : Elem = Elem(null,"ttt",attribute1,TopScope,text)
    
    println(myElem)    
  }
}
itmapa.de - X2H V 0.18

Der Ausführung des Programmes führt zu folgender Ausgabe auf der Systemausgabe:

<ttt id="123" name="theName">TagText</ttt>
itmapa.de - X2H V 0.18

Kind Elemente

XML ist eine baumartige Datenstruktur. Ein Tag kann seinerseits wieder weitere Tags als Kindelemente haben. Da scala.xml.Elem von scala.xml.Node abgeleitet ist, können wir Objekte dieser Klasse in der variablen Parameterliste des letzten Arguments des Konstruktors von Elem mit aufnehmen. Diese Objekte werden dann als Kind Elemente mit in den Tag aufgenommen. Das nachfolgende Beispiel zeigt die Verwendung:

import scala.xml._

object XMLChilds {
  def main(args: Array[String]) {    
    val attribute2 = new UnprefixedAttribute("name","theName",Null)
    val attribute1 = new UnprefixedAttribute("id","123",attribute2)    
    val text = Text("TagText")
 
    val child2_1 = Elem(null,"c",Null,TopScope,Text("cValue1"))
    
    val child1_1 = Elem(null,"b",Null,TopScope,Text("bValue1"))
    val child1_2 = Elem(null,"b",Null,TopScope,Text("bValue2"),child2_1)
    
    val topElem = Elem(null,"a",attribute1,TopScope,text,child1_1,child1_2)
    
    println(topElem)    
  }
}
itmapa.de - X2H V 0.18

Der Ausführung des Programmes führt zu folgender Ausgabe auf der Systemausgabe:

<a id="123" name="theName">TagText<b>bValue1</b><b>bValue2<c>cValue1</c></b></a>
itmapa.de - X2H V 0.11

Formatierung

Die bisherigen Beispiele zur Erzeugung von XML haben gezeigt, dass das erzeugte XML in einer Zeile geschrieben wird. Diese Art der Darstellung ist für Computerprogramme problemlos zu lesen. Als Menschen wünschen wir uns aber (in der Regel) eine mehrzeilige Darstellung mit Einrückung. Abhilfe schafft hier die Klasse scala.xml.PrettyPrinter. Dem Konstruktor dieser Klasse geben wir die gewünschte Spaltenzahl und die Anzahl der Leerzeichen zum Einrücken vor. Anschließend können wir mit Hilfe einer der format Funktionen aus einem XML Node einen formatierten String generieren lassen. Das nachfolgende Beispiel zeigt die formatierte Ausgabe des XML Nodes aus dem XMLChild Beispiel.

import scala.xml._

object XMLChilds {
  def main(args: Array[String]) {    
    val attribute2 = new UnprefixedAttribute("name","theName",Null)
    val attribute1 = new UnprefixedAttribute("id","123",attribute2)    
    val text = Text("TagText")
 
    val child2_1 = Elem(null,"c",Null,TopScope,Text("cValue1"))
    
    val child1_1 = Elem(null,"b",Null,TopScope,Text("bValue1"))
    val child1_2 = Elem(null,"b",Null,TopScope,Text("bValue2"),child2_1)
    
    val topElem = Elem(null,"a",attribute1,TopScope,text,child1_1,child1_2)
    
    val prettyPrinter = new PrettyPrinter(30,2)
    val outString = prettyPrinter.format(topElem)
    println(outString)
  }
}

itmapa.de - X2H V 0.18

Der Ausführung des Programmes führt zu folgender Ausgabe auf der Systemausgabe:

<a id="123" name="theName">
  TagText
  <b>bValue1</b>
  <b>
    bValue2
    <c>cValue1</c>
  </b>
</a>
itmapa.de - X2H V 0.18