Beispiel: XML lesen/schreiben


Inhalt:

Erzeugen
Parsen

Am Beispiel des vorgegebenen Formats der Sherd-Diagramme wird hier erklärt, wie das Erzeugen und Parsen von Diagramm-Dateien geschieht.
Das Beispiel basiert auf "sherd.xsd" Version 0.5.

Hier gibt es die Sourcen (schon in Package-Struktur): SherdXml.zip


Anmerkung: die im Code verwendeten XML-Element- und Attribut-Namen sollten natürlich alle als Konstanten deklariert sein, für mein eigenes Beispiel würde ich mir dick Punkte abziehen!

Erzeugen

Die Klasse de.knauf.fhw.swprojekt.xml.SherdWriter erzeugt eine Datei "sherd.xml" im Programmverzeichnis, wobei zwei Elemente "tabelle" erzeugt werden. Die erste Tabelle hat zwei Spalten, die zweite keine. Außerdem wird ein Kommentar erzeugt.

  1. Initialisierung
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import org.w3c.dom.Document;
    ...
    
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
    
        Document docSherd = documentBuilder.newDocument();
    
    Hier wird ein org.w3c.dom.Document erzeugt, indem zuerst eine DocumentBuilderFactory, daraus ein DocumentBuilder (beides sind abstrakte Klassen, deren Default-Implementierungen in der Java Runtime stecken, aber vor uns weggekapselt sind und durch reine Konfigurationsänderungen durch andere APIs ersetzt werden könnten)
    .
  2. Wurzelelement anlegen
    1. Ziel
      Das Ergebnis soll so aussehen:
      <?xml version="1.0" encoding="UTF-8" standalone="no"?>
      <sherd name="Testdiagramm" version="0.5" width="800" height="600"
      	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      	xsi:schemaLocation="http://www.informatik.fh-wiesbaden.de/~knauf/SWTProjekt2009/sherd http://www.informatik.fh-wiesbaden.de/~knauf/SWTProjekt2009/sherd.xsd" 
      	xmlns="http://www.informatik.fh-wiesbaden.de/~knauf/SWTProjekt2009/sherd">
      ...
      </sherd>
    2. Wurzelelement und Default-Namespace
      Um es zu erzeugen, ist folgender Code nötig:
      import org.w3c.dom.Element;
      ...
      
          Element nodeSherd = docSherd.createElementNS("http://www.informatik.fh-wiesbaden.de/~knauf/SWTProjekt2009/sherd", "sherd");
          docSherd.appendChild(nodeSherd);
      Die Document-Instanz nutzen wir, um neue Elemente oder sonstige Knotentypen zu erzeugen. Normalerweise reicht es, die Methode createElement mit dem Element-Namen als Parameter aufzurufen. Für das Wurzelelement soll allerdings auch ein Default-Namespace eingetragen werden. Der Namespace (beliebige URI) sieht hier aus wie eine Internet-URL.

      Das Ergebnis dieses Aufrufs würde so aussehen:
      <?xml version="1.0" encoding="UTF-8" standalone="no"?>
      <sherd xmlns="http://www.informatik.fh-wiesbaden.de/~knauf/SWTProjekt2009/sherd">
      ...
      </sherd>

    3. "schemaLocation"
      Jetzt wird die SchemaLocation für den Default-Namespace "http://www.informatik.fh-wiesbaden.de/~knauf/SWTProjekt2009/sherd" erzeugt. Dieses Attribut steckt im Standard-Namespace "http://www.w3.org/2001/XMLSchema-instance", der den Präfix "xsi" haben soll (d.h. wir müssen diesen Namespace ebenfalls einbinden).
      Attribute könnte man über das Document (Methode createAttribute bzw. createAttributeNS) erzeugen. Es gibt allerdings eine Vereinfachung, um ein Attribut direkt in das Element zu packen.
      
          nodeSherd.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance",
                  "xsi:schemaLocation",
                  "http://www.informatik.fh-wiesbaden.de/~knauf/SWTProjekt2009/sherd http://www.informatik.fh-wiesbaden.de/~knauf/SWTProjekt2009/sherd.xsd");
      Hier wird ein Attribut namens "xsi:schemaLocation" definiert, wobei der Namespace-Prefix "xsi" des Attributs mit dem Namespace "http://www.w3.org/2001/XMLSchema-instance" verbunden wird. Die "schemaLocation" verbindet den "sherd"-Namespace mit der XSD im Web. Für jeden der über "setAttributeNS" definierten Namespaces wird automatisch ein "xmls=..."-Attribut erzeugt.

      Quelle:
      http://bytes.com/groups/xml/468470-how-declare-namespace-prefix-java

    4. Sonstige Attribute
      Jetzt fehlen noch die "sherd"-Attribute "Diagrammname" und "Version":
          nodeSherd.setAttribute("name", "Testdiagramm");
          nodeSherd.setAttribute("version", "0.5");
          nodeSherd.setAttribute("width", "800");
          nodeSherd.setAttribute("height", "600");
    5. Elemente "tabellen", "relations" und "comments" anlegen
      Das ist einfach:
          Element elementTabellen = docSherd.createElement("tabellen");
          nodeSherd.appendChild(elementTabellen);
          Element elementRelations = docSherd.createElement("relations");
          nodeSherd.appendChild(elementRelations);
          Element elementComments= docSherd.createElement("comments");
          nodeSherd.appendChild(elementComments);
    6. Subelement "tabelle" zufügen
      Dem "tabellen"-Element wird ein Unterelement "tabelle" mit allen Attributen zugefügt:
          Element elementTabelle = docSherd.createElement("tabelle");
          elementTabelle.setAttribute("x", ... );
          elementTabelle.setAttribute("y", ... );
          elementTabelle.setAttribute("width", ... );
          elementTabelle.setAttribute("height", ... );
          elementTabelle.setAttribute("id", ... );
          elementTabelle.setAttribute("tabellenname", ... );
          
          elementTabellen.appendChild(elementTabelle);

      Die restlichen Unterelemente werden nach dem gleichen Schema erzeugt.

    7. Subelement "comment" mit Textinhalt zufügen
      Das Element "comment" hat eine Besonderheit: es kann Textinhalt haben. Hier gibt es zwei Möglichkeiten:

      Variante 1: Textinhalt
          Element elementKommentar = this.docSherd.createElement("comment");
          ...
          elementKommentar.setTextContent("Kommentar 2 mit Markup wie '<', '>', '&'");
          
          elementComments.appendChild(elementKommentar);
      Variante 2: "CDATA"-Sektion
          Element elementKommentar = this.docSherd.createElement("comment");
          ...
          CDATASection commentText = docSherd.createCDATASection("Kommentar 2 mit Markup wie '<', '>', '&'");
          elementKommentar.appendChild(commentText);
          
          elementComments.appendChild(elementKommentar);

      Den größten Unterschied zwischen den beiden Varianten erkennt man, wenn man (wie in meinem Beispielprogramm geschehen) "reservierte" Zeichen wie <, > && in den Kommentar hängt.
      In Variante 1 entsteht dieses XML:
      <comment id="501" ...>Kommentar 2 mit Markup wie '&lt;', '&gt;', '&amp;'</comment>
      Die reservierten Zeichen werden also automatisch durch Standard-Entities ersetzt.

      Variante 2 erzeugt dieses:
      <comment id="501" ...><![CDATA[Kommentar 2 mit Markup wie '<', '>', '&']]></comment>
      Durch die "<![CDATA[...]]>"-Klammerung müssen reservierte Zeichen nicht mehr maskiert werden. Allerdings darf der Stringinhalt nicht "]]>" enthalten, denn das würde die Sektion beenden!

  3. XML speichern
    import java.io.File;
    
    import javax.xml.transform.OutputKeys;
    import javax.xml.transform.Transformer;
    import javax.xml.transform.TransformerFactory;
    import javax.xml.transform.dom.DOMSource;
    import javax.xml.transform.stream.StreamResult;
    ...
    
        DOMSource domSource = new DOMSource(docSherd);
        File fileOutput = new File("sherd.xml");
        StreamResult streamResult = new StreamResult(fileOutput);
        TransformerFactory tf = TransformerFactory.newInstance();
    
        Transformer serializer = tf.newTransformer();
        serializer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        serializer.setOutputProperty(OutputKeys.INDENT, "yes");
        serializer.transform(domSource, streamResult);
    Es wird eine XML-Transformation durchgeführt, die das DOM in einen Stream (also die Zieldatei "sherd.xml") schreibt. Es werden zwei Konfigurationsparameter gesetzt: Encoding der XML-Datei (wichtig), außerdem bestellen wir uns Einrückung (unwichtig, nur für Lesbarkeit gedacht).

    Quelle: http://www.javazoom.net/services/newsletter/xmlgeneration.html (Beispiel 4 "JAXP + DOM + Serialization")
Zu beachten ist, dass das Dokument beim Erzeugen nicht validiert wird, d.h. es ist zwar syntaktisch korrektes XML, aber das Schema wird nicht geprüft! Es ist also möglich, Käse zu erzeugen.


Parsen

Die Klasse de.knauf.fhw.swprojekt.xml.SherdReader parst eine Datei "sherd.xml" im Programmverzeichnis und gibt alle Elemente "tabelle" und "comment" aus

  1. Initialisierung
    1. DocumentBuilderFactory
      Wie im letzten Beispiel wird eine DocumentBuilderFactory initialisiert, allerdings sind unsere Ansprüche jetzt höher:
      import javax.xml.parsers.DocumentBuilder;
      import javax.xml.parsers.DocumentBuilderFactory;
      ...
      
          DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
      
          documentBuilderFactory.setNamespaceAware(true);
          documentBuilderFactory.setValidating(false);
          
      Beim Parsen sollen Namespaces beachtet werden (setNamespaceAware(true)). Wichtig ist außerdem, dass wir setValidating(false) aufrufen. Grund für diese scheinbar unlogische Aktion: hiermit schalten wir die DTD-Validierung ab! Da unsere XML-Datei morderner ist und keine DTD verwendet, würde eine solche Prüfung nur zu einem Validierungsfehler führen.
          URL urlSchema = new URL("http://www.informatik.fh-wiesbaden.de/~knauf/SWTProjekt2009/sherd.xsd");
          Schema schemaSherd = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(urlSchema);
          documentBuilderFactory.setSchema(schemaSherd);
      Hier wird definiert, dass die Validierung gegen das "sherd"-Schema auf meiner Webseite erfolgen soll. Die "schemaLocation"-Deklaration im XML-Dokument selbst wird vom Validator ignoriert, wir müssen von außen sagen, welches Schema wir verwenden. Vorteil dieser Aktion: wir könnten auch eine Datei-URL angeben, und dann würde diese lokale XSD für die Validierung verwendet, statt ins Internet zu gehen.

    2. DocumentBuilder
      import javax.xml.parsers.DocumentBuilder;
      ...
      
          DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
          
          documentBuilder.setErrorHandler(new SherdErrorHandler() );
      Aus der DocumentBuilderFactory wird ein DocumentBuilder gezaubert. Diesem wird eine Implementation des Interfaces org.xml.sax.ErrorHandler übergeben. Grund hierfür: der Standard-Errorhandler spuckt nur Konsolenausgaben aus, läßt aber das Parsen weiterlaufen. Wir wollen allerdings bei jedem XML-Fehler sofort abbrechen.
      Deshalb wurde die Klasse de.fhw.knauf.swtprojekt.xml.SherdErrorHandler gebaut, die in ihren Callbacks jeden Parse-Fehler (als Exception-Parameter übergeben) als echte Exception weiterwirft. Eine Instanz hiervon wird als ErrorHandler an den DocumentBuilder übergeben.
      package de.knauf.fhw.swprojekt.xml;
      
      import org.xml.sax.ErrorHandler;
      import org.xml.sax.SAXException;
      import org.xml.sax.SAXParseException;
      
      public class SherdErrorHandler implements ErrorHandler
      {
        @Override
        public void error(SAXParseException arg0) throws SAXException
        {
          throw arg0;
        }
      
        @Override
        public void fatalError(SAXParseException arg0) throws SAXException
        {
          throw arg0;
        }
      
        @Override
        public void warning(SAXParseException arg0) throws SAXException
        {
          throw arg0;
        }
      
      }

      .
    3. Parsen
      Es wird die Datei "sherd.xml" im Programmverzeichnis geladen und dem DocumentBuilder übergeben:
      import java.io.File;
      import org.w3c.dom.Document;
      ...
      
          File fileSherd = new File("sherd.xml");
        
          Document docSherd = documentBuilder.parse(fileSherd);
      Jeder auftretende Fehler resultiert hier in einer org.xml.sax.SAXParseException, da diese von unserem SherdErrorHandler weitergeworfen werden.

    4. Elemente auslesen
      Folgender Codeblock holt sich das erste Element "tabellen" im Dokument, läuft über alle seine Subelemente "tabelle" und gibt deren Attribute aus.
      import org.w3c.dom.Element;
      import org.w3c.dom.NodeList;
      ...
      
          NodeList listTabellen = docSherd.getElementsByTagName("tabellen");
          Element elementTabellen = (Element) listTabellen.item(0);
      	
          //Über alle Childs laufen:
          NodeList listTabelle = elementTabellen.getElementsByTagName("tabelle");
          for (int intIndex = 0; intIndex < listTabelle.getLength(); intIndex++)
          {
            Element elementTabelle = (Element) listTabelle.item(intIndex);
            
            System.out.println ("Tabelle " + intIndex + ": Name = " + elementTabelle.getAttribute("tabellenname") +
                ", ID= " + elementTabelle.getAttribute("id") +
                ", x=" + elementTabelle.getAttribute("x") + ", y=" + elementTabelle.getAttribute("y") +
                ", width=" + elementTabelle.getAttribute("width") + ", height=" + elementTabelle.getAttribute("height") );
      Bei diesem Code ist zu beachten: da das Dokument gegen das Schema validiert wurde, wissen wir, dass es genau ein Element "tabellen" gibt. Wir können also in der Liste der Elemente namens "tabellen" direkt auf das Element an Index 0 zugreifen ohne weitere Prüfungen. Ohne Schema-Validierung wäre solcher Code ein No-Go!

      Des weiteren ist zu beachten, dass z.B. das Abrufen eines Attributs anhand des Namens keine Fehler wirft, wenn es kein Attribut dieses Namens gibt, sondern dann kommt NULL zurück! Hier ist die Gefahr von unentdeckten Tippfehler also groß!

    5. Textinhalt auslesen
      Folgender Codeschnipsel holt den Textinhalt eines Elements (wobei es egal ist, ob es einen String-Inhalt oder eine CDATA-Sektion enthält):
      String sComment = elementComment.getTextContent();



Stand 24.07.2009
Historie:
16.04.2009: Erstellt
23.04.2009: Angepaßt an sherd.xsd 0.3, Unterabschnitte "Textinhalt schreiben/lesen" zugefügt.
29.04.2009: Angepaßt an sherd.xsd 0.4 (optionales Attribut "primarykey" in Spalte)
06.05.2009: Angepaßt an sherd.xsd 0.5 (Diagrammhöhe/-breite)
24.07.2009: Angepaßt an sherd.xsd 0.6 (Relation-name, keine Änderung am Programmcode außer neuer Version)