XML ist in der heutigen Softwareentwickung fast nicht mehr wegzudenken. Einsatzgebiete von XML sind z.B.:
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.
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>
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>
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 }
Als Ergebnis des Programmablaufs wird
<x><y><z>9</z></y></x>
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 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) } }
Als Ergebnis erhalten wir eine XML-Datei mit folgendem Inhalt:
<?xml version='1.0' encoding='UTF8'?> <myTag>Save me, please</myTag>
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) } }
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>
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) } }
Als Ergebnis des Programmes wird der Inhalt der XML-Datei ohne Header auf der Systemausgabe ausgegeben:
<a> <b> Text </b> </a>
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.
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.
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) } }
Der Ausführung des Programmes führt zu folgender Ausgabe auf der Systemausgabe:
2 Text 1 Text 2
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) } }
Der Ausführung des Programmes führt zu folgender Ausgabe auf der Systemausgabe:
3 Text 1 Text 2 Text 3
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) } }
Der Ausführung des Programmes führt zu folgender Ausgabe auf der Systemausgabe:
1 base 1
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) } }
Der Ausführung des Programmes führt zu folgender Ausgabe auf der Systemausgabe:
5 base 1 TheText 1 TheText 2 TagC TheText 3
Nachdem wir XML Daten ausgewertet haben, wollen wir uns in diesem Abschnitt mit der Erzeugung von XML Daten beschäftigen.
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>
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.
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
:
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) } }
Der Ausführung des Programmes führt zu folgender Ausgabe auf der Systemausgabe:
<ttt id="123" name="theName"></ttt>
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) } }
Der Ausführung des Programmes führt zu folgender Ausgabe auf der Systemausgabe:
<ttt id="123" name="theName">TagText</ttt>
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) } }
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>
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) } }
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>