Erzeugen und Manipulieren eines DOM-Trees
Stellen wir uns folgene Situation vor: Ihr wollt aus einem Programm heraus XML-Dokumente erzeugen, beispielsweise um Daten mit anderen Anwendungen auszutauschen. Die einfachste Methode das zu tun, ist, einen DOM-Tree zu erzeugen, der das XML-Dokument enthält, und diesen dann abzuspeichern.
An dieser Stelle folgt wie immer ein Beispiel, aber bevor ich das bringe, möchte ich noch ein paar Anmerkungen loswerden: In diesem Beispiel wir ein kleiner DOM-Tree automatisch erzeugt. Das bedeutet, daß der Benutzer keinen Einfluß auf den Inhalt des Dokuments hat. Das Dokument soll folgendermaßen aussehen:
<?xml version="1.0"?> <!--Kommentar--> <WURZEL ROOT="true"> <DURCHLAUF NUMMER="0" /> <KIND1> viel Text <KIND2>viel Text</KIND2> </KIND1> <DURCHLAUF NUMMER="1" /> <KIND1> viel Text <KIND2>viel Text</KIND2> </KIND1> </WURZEL>
Das erzeugte XML-Dokument wird dann in eine Datei abgespeichert. Die entsprechende Methode habe ich, um die Übersichtlichkeit zu wahren, nicht implementiert, aber es sollte euch nicht allzu schwer fallen, das nachzuholen.
import java.io.*; import org.w3c.dom.*; import com.ibm.xml.dom.DocumentImpl; public class XmlWriter { public static void main(String[] args) { XmlWriter writer = new XmlWriter(); Document doc = writer.createFile(); writer.writeFile(doc, args[0]); } public Document createFile() { Document doc = new DocumentImpl(); Text text = doc.createTextNode("viel Text"); Element child2 = doc.createElement("Kind2"); child2.appendChild(text.cloneNode(false)); Element child1 = doc.createElement("Kind1"); child1.appendChild(text.cloneNode(false)); child1.appendChild(child2); Element durchlauf = doc.createElement("Durchlauf"); Attr nr = doc.createAttribute("Nummer"); durchlauf.setAttributeNode(nr); Element root = doc.createElement("Wurzel"); root.setAttribute("root", "true"); for (int i = 0; i <= 1; i++) { nr.setValue((new Integer(i)).toString()); root.appendChild(durchlauf.cloneNode(false)); root.appendChild(child1.cloneNode(true)); } Comment kommentar = doc.createComment("Kommentar"); doc.appendChild(kommentar); doc.appendChild(root); return doc; } public void writeFile(Document doc, String file) {//Speichern des Dokuments} }
Das interessanteste an diesem Beispiel ist natürlich die Methode createFile. Der Rest sollte euch inzwischen geläufig sein. Was passiert hier nun im Einzelnen?
Document doc = new DocumentImpl();
Diese Zeile erzeugt einen (leeren) DOM-Tree. Document ist ein Interface, und DocumentImpl die entsprechende IBM-Implementierung. Mein doc-Objekt ist vom Typ Document, da auch der Sun-Parser dieses Interface implementiert. Das bedeutet, alle Methoden dieses Interfaces sind in beiden Parsern gleich, und ich habe relativ wenig Arbeit damit, das Beispiel auf den Sun-Parser zu portieren (Faulheit macht erfinderisch ;-).
Nun müßen wir den Tree mit Elementen, Attributen usw. füllen. Dafür werden Objekte wie Comment, Element, Text, Attr usw. zur Verfügung gestellt. All diese Objekte sind von Node abgeleitet, und haben damit einen gewissen Grundumfang an Methoden. Außerdem stellt Document entprechende Methoden zum Erzeugen all dieser Objekte bereit, zum Beispiel createElement, oder createTextNode.
Als nächstes müssen wir die Elemente des XML-Dokuments miteinander verknüpfen, damit ein Baum, der DOM-Tree, entsteht. Ich habe dazu die Methode appendChild aus dem Interface Node verwendet. Ihr könntet aber auch die Methoden insertBefore oder replaceChild benutzen. Schaut einfach mal in der Doku nach, welche Methoden Node noch bietet!
Mit den bisher vorgestellten Mitteln, könnt ihr nun also eure Elemente, Kommentare usw. erzeugen, und diese dann in die gewünschte Baumstrucktur bringen. Ich denke, die ensprechenden Methoden sind auch recht intuitiv benutzbar (falls doch nicht, bieten die API-Doks schnelle Hilfe). Einige der im obigen Beispiel verwendeten Methoden möchte ich aber doch noch etwas näher beleuchten:
Die Methode cloneNode stellt, wie der Name schon sagt, eine Kopie des Knotens her. Diese Kopie benötigt man, da man einen Knoten nicht an mehreren Stellen des DOM-Trees einhängen kann. Der Parameter dieser Methode gibt an, ob alle im Knoten enthaltenen Elemente (ein Knoten kann ja schon eingehängte Elemente haben, also einen eigenen Tree darstellen) mit kopiert werden sollen (true), oder ob diese beim Kopiervorgang ignoriert werden sollen (false).
Mit setAttribute(Attributname, Wert) könnt ihr, oh Wunder, ein Attribut erzeugen. Damit kommt ihr darum herum, ein Attr-Objekt zu erzeugen, und dieses dann beim entsprechenden Element einzuhängen. Allerdings könnt ihr auch nicht mehr so einfach auf das Attribut zugreifen. Da im Beispiel ja beide Varianten vorkommen, sind die Vor- und Nachteile ganz gut zu sehen, denke ich.
Nun, da ihr das Beispiel (hoffentlich) versteht, fragen sich vielleich einige von euch, warum ich mir das so kompliziert mache, erst einen DOM-Tree zu erzeugen, und diesen dann abzuspeichern. Es wäre doch viel einfacher gewesen, den XML-Code direkt in eine Text-Datei zu schreiben. Die Antwort ist einfach: Wenn ich das ganze einfach in eine Text-Datei geschrieben hätte, hätte ich ja kein Beispiel gehabt, um zu demonstrieren, wie man einen DOM-Tree im Speicher aufbaut und manipuliert! ;-) Außerdem hätte sowas wohl kaum in ein Tutorial zu XML-Parsern gepaßt.
Für die Freunde des Sun-Parsers kommt hier nochmal das komplette Beispiel mit diesem Teil:
import java.io.*; import org.w3c.dom.*; import com.sun.xml.tree.XmlDocument; public class XmlWriter { public static void main(String[] args) { XmlWriter writer = new XmlWriter(); Document doc = writer.createFile(); writer.writeFile(doc, args[0]); } public Document createFile() { Document doc = new XmlDocument(); Text text = doc.createTextNode("viel Text"); Element child2 = doc.createElement("Kind2"); child2.appendChild(text.cloneNode(false)); Element child1 = doc.createElement("Kind1"); child1.appendChild(text.cloneNode(false)); child1.appendChild(child2); Element durchlauf = doc.createElement("Durchlauf"); Attr nr = doc.createAttribute("Nummer"); durchlauf.setAttributeNode(nr); Element root = doc.createElement("Wurzel"); root.setAttribute("root", "true"); for (int i = 0; i <= 1; i++) { nr.setValue((new Integer(i)).toString()); root.appendChild(durchlauf.cloneNode(false)); root.appendChild(child1.cloneNode(true)); } Comment kommentar = doc.createComment("Kommentar"); doc.appendChild(kommentar); doc.appendChild(root); return doc; } public void writeFile(Document doc, String file) {//Speichern des Dokuments} }
Wie man sieht, ändert sich nichts, außer daß die Implementierung des Document-Interfaces XmlDocument heißt, und nicht DocumentImpl. Allerdings hat die Sun-Implementierung einen Vorteil, den ich nicht verschweigen möchte. XmlDocument stellt nämlich die Methode print zur Verfügung, mit der sich sehr leicht ein richtig formatiertes XML-Dokument ausgeben läßt.
Die writeFile-Methode aus dem obigen Beispiel, könnten wir dann wie folgt schreiben:
public void writeFile(XmlDocument doc, String file) throws IOException { FileOutputStream out = new FileOutputStream(file); doc.write(out); out.close(); }
Allerdings müssen wir nun auch den Rest des Programmes etwas umschreiben. Das komplette Beispiel sieht dann so aus:
import java.io.*; import org.w3c.dom.*; import com.sun.xml.tree.XmlDocument; public class XmlWriter { public static void main(String[] args) { XmlWriter writer = new XmlWriter(); XmlDocument doc = writer.createFile(); try { writer.writeFile(doc, args[0]); } catch (IOException e) { System.err.println("Fehler beim Schreiben: "); System.err.println(e); } } public XmlDocument createFile() { XmlDocument doc = new XmlDocument(); Text text = doc.createTextNode("viel Text"); Element child2 = doc.createElement("Kind2"); child2.appendChild(text.cloneNode(false)); Element child1 = doc.createElement("Kind1"); child1.appendChild(text.cloneNode(false)); child1.appendChild(child2); Element durchlauf = doc.createElement("Durchlauf"); Attr nr = doc.createAttribute("Nummer"); durchlauf.setAttributeNode(nr); Element root = doc.createElement("Wurzel"); root.setAttribute("root", "true"); for (int i = 0; i <= 1; i++) { nr.setValue((new Integer(i)).toString()); root.appendChild(durchlauf.cloneNode(false)); root.appendChild(child1.cloneNode(true)); } Comment kommentar = doc.createComment("Kommentar"); doc.appendChild(kommentar); doc.appendChild(root); return doc; } public void writeFile(XmlDocument doc, String file) throws IOException { FileOutputStream out = new FileOutputStream(file); doc.write(out); out.close(); } }