Galileo Computing < openbook >
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.


Kompendium der Informationstechnik
 von Sascha Kersken
EDV-Grundlagen, Programmierung, Mediengestaltung
Buch: Kompendium der Informationstechnik
gp Kapitel 15 XML
  gp 15.1 Der Aufbau von XML-Dokumenten
    gp 15.1.1 Die grundlegenden Bestandteile von XML-Dokumenten
    gp 15.1.2 Wohlgeformtheit
  gp 15.2 DTDs und XML Schema
    gp 15.2.1 Document Type Definitions (DTDs)
    gp 15.2.2 Namensräume
    gp 15.2.3 XML Schema
  gp 15.3 XSLT
  gp 15.4 Grundlagen der XML-Programmierung
    gp 15.4.1 SAX
    gp 15.4.2 DOM
  gp 15.5 Zusammenfassung

gp

Prüfungsfragen zu diesem Kapitel (extern)


Galileo Computing

15.4 Grundlagen der XML-Programmierung  downtop

Da XML innerhalb weniger Jahre zu einem der wichtigsten Datenformate geworden ist, wird es von fast allen Programmiersprachen unterstützt. Das wichtigste Instrument der XML-Programmierung ist ein XML-Parser, der die einzelnen Komponenten von XML-Dokumenten voneinander trennt und ihre Wohlgeformtheit überprüft. Die Ausgabe eines solchen Parsers kann anschließend durch ein selbst geschriebenes Programm verarbeitet werden.

XML-Parser

Wie man einen XML-Parser selbst schreiben könnte, soll an dieser Stelle nicht erörtert werden. Im Grunde handelt es sich um ein sehr komplexes Gefüge regulärer Ausdrücke, die ein wohlgeformtes XML-Dokument beschreiben. Da es für die meisten Programmiersprachen unzählige fertige Parser gibt, müssen Sie sich das nicht antun.

In diesem Abschnitt werden die beiden gängigsten XML-Programmiermodelle beschrieben. Die verwendete Programmiersprache ist Java, obwohl beide Modelle auch von anderen Sprachen wie Perl oder dem .NET-Framework unterstützt werden. Das erste Modell ist das ereignisbasierte SAX (Simple API for XML), das zweite das baumbasierte DOM (Document Object Model).

Wenn Sie SAX und DOM in Java-Programmen verwenden möchten, benötigen Sie zuerst einen Parser, der diese Modelle unterstützt und die Parsing-Ergebnisse an Ihr Programm weitergibt. Sehr empfehlenswert ist der Parser Apache Xerces, den Sie unter http://xml.apache.org/xerces2-j/index.html herunterladen können. Wenn Sie ihn für eine andere Programmiersprache als Java benötigen, können Sie sich selbst unter xml.apache.org umsehen. Die aktuelle Version von Xerces für Java ist zurzeit 2.4. Sein Vorteil ist, dass er sowohl DOM als auch SAX unterstützt und die notwendigen Java-Klassen für beide enthält.

Um Xerces und die XML-Klassen aus Ihrem Java-Programm heraus zu verwenden, müssen Sie den Pfad der Datei xercesImpl.jar (bei älteren Versionen xerces.jar) in Ihren Java-Classpath aufnehmen. Wie die Umgebungsvariable CLASSPATH manipuliert wird, steht in Kapitel 5, Grundlagen der Programmierung.


Galileo Computing

15.4.1 SAX  downtop

Wie bereits erwähnt, ist die Simple API for XML ein ereignisbasiertes Programmiermodell für XML-Anwendungen. Der Parser verarbeitet ein XML-Dokument und ruft an bestimmten Stellen, die Sie festlegen können, Ihre Verarbeitungsfunktionen auf. Ein solches Programmierverfahren, bei dem Ihre eigenen Methoden von außen aufgerufen werden, heißt Callback-Verfahren.

Um SAX zu verwenden, müssen Sie zunächst eine Reihe von Klassen importieren. Die schnellste und einfachste Importanweisung ist folgende:

import org.xml.sax.*;

Eine Parser-Instanz erzeugen

Als Nächstes müssen Sie eine Instanz Ihres bevorzugten SAX-Parsers anlegen. Das folgende Beispiel erzeugt eine Instanz des Xerces-SAX-Parsers:

public String pclass =      "org.apache.xerces.parsers.SAXParser";
public XMLReader parser =      XMLReaderFactory.createXMLReader (pclass);

XMLReader ist ein Interface, das einen SAX-fähigen Parser beschreibt. Der Vorteil gegenüber einer konkreten Klasse liegt auf der Hand: Wenn Sie von Xerces auf einen anderen Parser umsteigen müssen oder wollen, müssen Sie lediglich den Wert der Variablen pclass ändern; alles andere bleibt bestehen. Aus demselben Grund wird kein Konstruktor aufgerufen, sondern eine Methode einer Factory-Klasse.

Ein XML-Dokument parsen

Damit der Parser etwas zu tun hat, müssen Sie ihm nun ein XML-Dokument vorsetzen. Dies geschieht mit Hilfe einer InputSource-Instanz. Wenn Sie die Datei xml-buecher.xml im aktuellen Verzeichnis als Eingabedokument verwenden möchten, sieht der entsprechende Code folgendermaßen aus:

InputSource source =
     new InputSource (new java.io.FileInputStream (
          new java.io.File ("xml-buecher.xml")));
source.setSystemId ("xml-buecher.xml");

Der InputSource-Konstruktor erwartet die Übergabe eines InputStreams, der im vorliegenden Fall aus einer lokalen Datei gebildet wird. Die Methode setSystemId() des InputSource-Objekts stellt die XML-Datei auch gleich als SYSTEM-ID ein, damit relative Pfadangaben innerhalb des XML-Codes korrekt aufgelöst werden.

Nachdem Sie nun den Parser und das XML-Eingabedokument eingerichtet haben, können Sie die Methode parse() aufrufen, um das Dokument tatsächlich zu verarbeiten:

parser.parse (source);

SAX-Event-Handler

Damit nun aber die vom Parser gemeldeten Ereignisse wie Beginn und Ende eines Elements oder Auftreten eines Attributs von Ihrem Programm verarbeitet werden können, müssen Sie es als Handler für die jeweiligen Ereignisse registrieren. Der wichtigste Handler für SAX-Ereignisse ist org.xml.sax.ContentHandler, der sich um XML-Ereignisse wie Elemente und Attribute kümmert.

Die drei anderen möglichen Handler werden hier nicht weiter besprochen; es handelt sich um org.xml.sax.EntityResolver, der – wie der Name vermuten lässt – Entity-Referenzen auflöst; ErrorHandler zur Bearbeitung von Parsing-Fehlern und DTDHandler zur Validierung des Dokuments anhand einer DTD.

Um Ihr eigenes Programm als ContentHandler zu registrieren, muss es dieses Interface und alle seine Methoden implementieren, ob Sie die Funktionalität dieser Methoden nun benötigen oder nicht. Diese spezielle Eigenschaft von Java-Interfaces wird in Kapitel 6, Konzepte der Programmierung, näher erläutert. Außerdem muss eine Instanz der Klasse vorliegen, die als Handler registriert wird – Sie müssen diese Instanz mit Hilfe der Methode setContentHandler() der XMLReader-Instanz angeben. Alternativ können Sie auch eine separate Klasse schreiben, eine Instanz davon erzeugen und als Handler registrieren. Schematisch sieht diese Registrierung, die noch vor dem parse()-Aufruf stehen muss, folgendermaßen aus:

parser.setContentHandler (handler);

In Tabelle 15.1 sehen Sie eine Liste aller Callback-Methoden, die Sie für einen ContentHandler implementieren müssen. Beachten Sie bitte, dass Sie keine dieser Methoden jemals selbst aufrufen werden. Stattdessen werden sie nacheinander vom XML-Parser aufgerufen, wenn er die betreffenden Komponenten eines XML-Dokuments antrifft.


Tabelle 15.3   Alle Callback-Methoden von ContentHandler

SAX-Callback-Methode Erläuterung
public void setDocumentLocator (Locator locator) Wird zu Beginn des Parsings aufgerufen und richtet den Locator ein, der jeweils auf die Position im XML-Dokument verweist, an der sich der Parser gerade befindet.
public void startDocument () throws SAXException Wird beim eigentlichen Beginn des Parsing-Prozesses aufgerufen.
public void endDocument () throws SAXException Wird stets am Ende der Verarbeitung aufgerufen, sowohl wenn das Dokument fertig verarbeitet ist als auch bei Abbruch durch einen Fehler.
public void startPrefixMapping (String prefix, String uri) throws SAXException Wird vor dem Start eines Elements aufgerufen, das ein Namensraum-Präfix besitzt.
public void endPrefixMapping (String prefix) throws SAXException Wird nach dem Abschluss eines Elements mit Namensraum-Präfix aufgerufen.
public void startElement (String namespaceURI, String localName, String qName, Attributes attrs) throws SAXException Eines der wichtigsten SAX-Callbacks. Es wird bei jedem Antreffen eines öffnenden Tags aufgerufen und liefert die Namensraum-URI, den einfachen Elementnamen, den »qualified name« (Name mit Namensraum-Präfix) und eine Aufzählung der Attribute.
public void endElement (String namespaceURI, String localName, String qName) throws SAXException Wird bei jedem Antreffen eines schließenden Tags aufgerufen. Folgerichtig liefert die Methode dieselben Elementinformationen wie startElement() außer den Attributen.
public void characters (char ch[], int start, int length) throws
SAXException
Gibt einfachen Text aus dem Dokument als Array aus einzelnen Zeichen zurück. Zur Kontrolle werden die Nummer des Startzeichens und die Länge mit angegeben, weil keine Garantie besteht, dass ein ganzer Textabschnitt auf einmal zurückgegeben wird.
public void ignorableWhitespace (char ch[], int start, int length) throws SAXException Gibt ignorierbaren Whitespace als Array von Zeichen zurück. Wird nur aufgerufen, wenn das Dokument sich auf eine DTD oder ein Schema bezieht, deren Inhaltsdefinitionen Whitespace übrig lassen. Andernfalls wird Whitespace nämlich von characters() behandelt.
public void processingInstruction (String target, String data) throws SAXException Reagiert auf Steueranweisungen im Dokument, allerdings nicht auf die <?xml?>-Steueranweisung am Dokumentbeginn. Sonstige Steueranweisungen sind für Befehle an XML-Anweisungen vorgesehen.
public void skippedEntity (String name) throws SAXException Reicht alle Entity-Referenzen an den ContentHandler durch, die nicht vom Parser aufgelöst werden können.

Die Beispielanwendung in Listing 15.5 tut etwas recht Nützliches mit der Bücherliste aus Listing 15.1: Sie schreibt die meisten Informationen aus dem XML-Dokument über JDBC in eine Datenbanktabelle in der Datenbank buchliste, die über die JDBC-ODBC-Bridge (siehe Kapitel 7, Datenbanken) angebunden ist. Explizit weggelassen werden nur die Autoren: Ein Buch kann mehrere Autoren haben; dafür benötigt ein sauberes relationales Datenbankmodell mehrere Tabellen. Diese Komplexität wäre für ein einfaches Beispiel fehl am Platze.

Listing 15.5   Die Daten eines XML-Dokuments in eine Datenbank schreiben

import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.sql.*;
import java.io.*;

public class XMLBuecherDB implements ContentHandler
{

Connection conn; // Die Datenbankverbindung
Statement st; // Ein SQL-Statement-Objekt
public String isbn; // ISBN des aktuellen Buches
public String info; // Aktuell verarbeitete Info
public boolean relevant;
// z.Z. relevantes Element?
public String rtext; // Text => Datenfeld

public static void main (String args[])
{
XMLBuecherDB buecherdb = new XMLBuecherDB();
System.out.println ("Willkommen!");
try {
buecherdb.makeDB(buecherdb);
}
catch (Exception e) {
System.out.println
("Ein Fehler ist aufgetreten");
e.printStackTrace(System.err);
}
}

public void makeDB (XMLBuecherDB bdb)
throws Exception
{
// Datenbankverbindung herstellen
try {
Class.forName
("sun.jdbc.odbc.JdbcOdbcDriver ");
}
catch (ClassNotFoundException e) {
System.out.println
("JDBC-ODBC-Treiber nicht gefunden.");
return;
}
conn = DriverManager.getConnection
("jdbc:odbc:buchliste", "", "");
st = conn.createStatement();

// Datenbanktabelle BUECHER erzeugen
st.execute
("CREATE TABLE BUECHER (ISBN INT PRIMARY
KEY, TITEL VARCHAR(30), JAHR INT,
ORT VARCHAR(30), VERLAG VARCHAR(30))");

// SAX-Parser erzeugen
String pclass =
"org.apache.xerces.parsers.SAXParser";
XMLReader parser =
XMLReaderFactory.createXMLReader (pclass);

// XML-Input-Source erzeugen
InputSource source = new InputSource
(new java.io.FileInputStream
(new java.io.File ("xml-buecher.xml")));
// source.setSystemId ("xml-buecher.xml");

// Eine Instanz des Programms selbst als
// ContentHandler registrieren

parser.setContentHandler (bdb);
info = "";

// Parsing beginnen
parser.parse(source);
}

/* Implementierung der ContentHandler-Klassen */
public void setDocumentLocator (Locator locator)
{ // Wird nicht benötigt
}

public void startDocument () throws SAXException
{ // Wird nicht benötigt
}

public void endDocument () throws SAXException
{ // Wird nicht benötigt
}

public void startPrefixMapping
(String prefix, String uri) throws SAXException
{ // Wird nicht benötigt
}

public void endPrefixMapping (String prefix)
throws SAXException
{ // Wird nicht benötigt
}

public void startElement (String namespaceURI,
String localName, String qName,
Attributes attrs) throws SAXException
{
if (localName.equals ("buch")) {
// ISBN ermitteln => in neuen Datensatz
isbn = attrs.getValue ("isbn");
System.out.println ("Buch: ISBN " + isbn);
try {
st.execute ("INSERT INTO BUECHER
(ISBN) VALUES " + isbn);
}
catch (Exception e) {
}
relevant = false;
} else if (localName.equals ("titel") ||
localName.equals ("jahr") ||
localName.equals ("ort") ||
localName.equals ("verlag")) {
// Namen des alten u. neuen Elements speichern
relevant = true;
info = localName;
rtext = "";
} else {
// Unwichtiges Element
relevant = false;
}
}

public void endElement (String namespaceURI, String
localName, String qName) throws SAXException
{
if (relevant) {
// Whitespace am Anfang hinauswerfen
while (rtext.charAt (0) == ' ') {
rtext = rtext.substring
(1, rtext.length());
}
// Whitespace am Ende hinauswerfen
while (rtext.charAt (rtext.length() -1)
== ' ') {
rtext = rtext.substring
(0, rtext.length() - 1);
}
System.out.println ("------Element: " + info);
// Bisherigen Text => aktuelles Feld
try {
st.execute ("UPDATE BUECHER SET " + info
+ "=\"" + rtext + "\" WHERE ISBN=\""
+ isbn + "\"");
}
catch (Exception e) {
}
rtext = "";
relevant = false;
}
}

public void characters (char ch[], int start,
int length) throws SAXException
{
if (relevant) {
// Aktuelle Zeichen hinzufügen
String ntext = new String(ch, start, length);
rtext += ntext;
}
}

public void ignorableWhitespace (char ch[],
int start, int length) throws SAXException
{ // Wird nicht benötigt
}

public void processingInstruction (String target,
String data) throws SAXException
{ // Wird nicht benötigt
}

public void skippedEntity (String name)
throws SAXException
{ // Wird nicht benötigt
} }

Die Komplexität des Beispiels ergibt sich insbesondere daraus, dass die einzelnen Bestandteile des XML-Dokuments, die für die Datenbank relevant sind, in verschiedenen Methoden des Programms gefunden werden: startElement() leitet den Beginn des jeweiligen Elements ein und beginnt damit die Sammlung der nachfolgenden Zeichen in characters(), um das entsprechende Feld der Datenbank mit Inhalt zu füllen. Bei endElement() ist der entsprechende Text vollständig und wird in die Datenbank geschrieben. Da sich vor und hinter dem eigentlichen Inhalt Whitespace befinden kann, wird dieser zunächst zeichenweise entfernt.

Auch die etwas seltsame Aufgabenverteilung zwischen der Methode main() und dem eigentlichen »Arbeitstier« makeDB() ist sicherlich erklärungsbedürftig: Die globale Variable info, die den Namen des aktuellen Elements speichert, könnte nicht in der statischen Methode main() verwendet werden. Umgekehrt kann das Programm selbst nicht statisch sein, weil der registrierte ContentHandler eine Instanz sein muss. Deshalb wird in main() eine Instanz von XMLBuecherDB erzeugt und an makeDB() übergeben, da main() wiederum nicht auf ein globales XMLBuecherDB-Objekt zugreifen dürfte.


Galileo Computing

15.4.2 DOM  toptop

Das Document Object Model wurde vom W3C standardisiert und kann von vielen verschiedenen Programmiersprachen aus verwendet werden. Beispielsweise wird in Kapitel 19, JavaScript, die Anwendung von DOM in einem Browser zur Manipulation von HTML-Dokumenten besprochen.

In diesem kurzen Unterabschnitt wird dagegen gezeigt, wie Sie DOM in einem Java-Programm einsetzen können. Der große Unterschied zu SAX besteht darin, dass DOM beim Parsing zunächst ein vollständiges Baummodell des XML-Dokuments errichtet, das Sie anschließend in aller Ruhe durchqueren und modifizieren können. Die Äste, Zweige und Blätter des Baums entsprechen den Elementen, Attributen und Textinhalten eines XML-Dokuments.

DOM-Parser

Um DOM in einem Java-Programm zu verwenden, benötigen Sie zunächst einen DOM-fähigen Parser. Auch in diesem Fall ist Apache Xerces eine gute Wahl. Zu Beginn Ihres Programms müssen Sie die Parser-Klasse und die DOM-Klassen importieren:

import org.w3c.dom.*;
import org.apache.xerces.parsers.DOMParser;

Um ein XML-Dokument durch den DOMParser zu schicken, können Sie folgendermaßen verfahren:

DOMParser parser = new DOMParser();
String xmlFile = "meinedatei.xml";
parser.parse (xmlFile);

Anschließend kann der gesamte XML-Dokumentbaum vom Parser entgegengenommen werden. Er befindet sich in einem Document-Objekt:

Document doc = parser.getDocument ();

Das Objekt doc besteht aus einer Reihe ineinander verschachtelter Knoten; dies sind Objekte vom Typ Node. Am sinnvollsten lassen sie sich in einer rekursiven Prozedur durchwandern:

recurseNode (doc);

Die entsprechende Prozedur recurseNode() kann beispielsweise folgendermaßen aussehen:

public void recurseNode (Node knoten)
{
   int typ = node.getNodeType(); // Knotentyp
   switch (typ) {
      // Je nach Knotentyp reagieren;
      // bei Knoten mit Kindknoten recurseNode()
      // rekursiv aufrufen
   }
}

Erfreulicherweise brauchen Sie sich die verschiedenen Knotentypen nicht numerisch zu merken, sondern können auf eine Reihe symbolischer Konstanten zurückgreifen, die das Interface Node exportiert. Tabelle 15.4 enthält eine Übersicht über die verfügbaren Knotentypen.


Tabelle 15.4   Die verfügbaren DOM-Knotentypen

Kontentyp Bedeutung
Node.ELEMENT_NODE ein XML-Element
Node.TEXT_NODE einfacher Text
Node.CDATA_SECTION_NODE ein CDATA-Abschnitt
Node.COMMENT_NODE ein XML-Kommentar
Node.
PROCESSING_INSTRUCTION_NODE
eine Steueranweisung
Node.ENTITY_REFERENCE_NODE eine Entity-Referenz
Node.DOCUMENT_TYPE_NODE eine DOCTYPE-Deklaration

Die Rekursion über die Kindknoten eines Objekts ist übrigens auch keine schwierige Angelegenheit: Ein Aufruf der Methode getChildNodes() eines Knotens gibt eine Liste aller direkten Kindobjekte zurück. Diese Liste können Sie mit Hilfe einer einfachen for-Schleife bearbeiten:

NodeList kinder = knoten.getChildNodes();
if (nodes != null) {
   for (int i = 0; i < kinder.getLength(); i++) {
      recurseNode (kinder.item(i);
   }
}

Mit DOM lassen sich Unmengen sinnvoller Anwendungen schreiben. Einige Beispiele finden Sie in Kapitel 19, JavaScript, für die in Webbrowser eingebaute DOM-Variante.

  

Einstieg in PHP 5

Einstieg in Java

C von A bis Z

Einstieg in C++

Einstieg in Linux

Einstieg in XML

Apache 2




Copyright © Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de