XML — Architektur, Werkzeuge, Techniken

Werbung
1
XML — Achitektur, Werkzeuge, Techniken
Axel-Tobias Schreiner
Fachbereich Mathematik-Informatik
Universität Osnabrück
Dieser Band enthält Kopien der OH-Folien, die in der Vorlesung verwendet werden. Diese
Information steht außerdem im World-Wide-Web online zur Verfügung; sie ist in sich und mit
Teilen der Systemdokumentation über Hypertext-Links verbunden. Die Beispielprogramme
werden maschinell in diesen Text eingefügt.
Zur Betrachtung auf anderen Systemen gibt es den Text auch als PDF-Dokument. Mit dem
Acrobat Reader von Adobe kann der Text damit auf Windows-Systemen ausgedruckt werden.
Der Band stellt kein komplettes Manuskript der Vorlesung dar. Zum Selbststudium müßten
zusätzlich Bücher über die Programmiersprache Java, über HTML und das World-Wide-Web
sowie einige Originalartikel konsultiert werden.
Inhalt
0
1
2
3
4
5
6
7
8
9
A
Einführung
Erste Schritte
Hypertext Markup Language
Cascading Style Sheets
Compilerbau
Extensible Markup Language
Quellen
Simple API for XML
Document Object Model
XSL Transformationen
XSL Formatting Objects
XML, CORBA und SOAP
Begriffe
1
3
17
23
31
51
63
77
89
93
103
2
Literatur
Dieser Text wird im Classic Environment mit Adobe Framemaker, Illustrator, PhotoShop
und Distiller sowie mit ConceptDraw unter MacOS X entwickelt. Er befindet sich im
Web.
Es gibt heute sehr viele Bücher über Java, HTML, das World-Wide-Web und über XML
und die verwandten Technologien. Die folgenden Bücher sind nützlich. Soweit
vorhanden, befinden sie sich in der Lehrsammlung.
Bradley
Behme, Mintert
Flanagan
Flanagan et al.
Flanagan
Flanagan
Harold, Means
Kay
McLaughlin
Niederst
Quin
0-201-67487-4
3-8273-1636-7
1-56592-487-8
1-56592-483-5
1-56592-371-5
1-56592-488-6
0-596-00058-8
1-861003-12-9
0-596-00016-2
1-56592-579-3
0-471-37522-5
The XSL companion
XML in der Praxis
Java in a Nutshell (3rd Edition)
Java Enterprise in a Nutshell
Java Examples in a Nutshell
Java Foundation Classes in a Nutshell
XML in a Nutshell
XSLT Programmer’s Reference
Java and XML
HTML Pocket Reference
Open Source XML Database Toolkit
3
1
Erste Schritte
Hinter dem Begriff XML verbergen sich eine ganze Reihe von Technologien. Ein zentrales
Thema ist dabei die Darstellung von Informationen im World Wide Web.
In diesem Kapitel wird das an sich triviale Problem, einen Text in der Mitte eines BrowserFensters darzustellen, auf verschiedene Weise gelöst, wobei einige der XML-Technologien
zum Einsatz gebracht werden.
Hier sollen nur die Ideen gestreift und zugleich einige Probleme aufgezeigt werden — die
einzelnen Technologien werden dann erst in den folgenden Kapiteln im Detail besprochen.
4
1.1 HTML
Mit der Hypertext Markup Language (HTML) beschreibt man, wie ein Web-Browser
Information darstellen soll. Ein Dokument enthält Information (Text), Verweise auf Information
(Netz-Adressen von Bildern und ähnlichem) sowie Auszeichnungen (markup), nämlich mehr
oder weniger präzise Hinweise, wie die Information darzustellen ist:
hello/1.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<!-- Hello, Osnabrück! in HTML
vertikal zentriert
mit jeder HTML version in IE 5.5 aber nur mit 3.2 in Netscape 6
ats 2000-03
-->
<html>
<head>
<title>Hello, Osnabrück!</title>
</head>
<body bgcolor="khaki">
<table height="100%" width="100%">
<tr>
<td align="center">
<font color="blue" size="7" face="sans-serif">
<b>
Hello, Osnabrück!
</b>
</font>
</table>
</body>
</html>
Um einen Text zu zentrieren, verwendet man eine Tabelle (table), die man auf das ganze
Fenster aufspannt. Die Tabelle enthält eine Zeile (tr) und diese eine Zelle (td) in der der Text
zentriert angeordnet wird. Es gibt prinzipielle Vorgaben für Schriftart, -farbe und -größe
(font); außerdem soll die Schrift fett sein (b). HTML wird in der Regel im ASCII-Zeichensatz
aufgeschrieben, deshalb muß ein Umlaut speziell dargestellt werden (ü).
Das Dokument hat einen Rahmen (html), bei dem im Kopfteil (head) ein Titel angegeben
werden kann (title), der möglichwerweise als Fenstertitel verwendet wird. body markiert
den Informationsteil und kann eine Hintergrundfarbe festlegen.
Als Auszeichnungen dienen Elemente, die in der Regel geöffnet <html> und abgeschlossen
werden </html>. Bei der Eröffnung kann man Attribute in beliebiger Reihenfolge angeben,
die aus Schlüsseln und Werten bestehen; die Werte sollten in einfache oder DoppelAnführungszeichen eingeschlossen sein, und sie können das benutzte Anführungszeichen
sowie < > nicht enthalten. Elemente können verschachtelt werden und Text enthalten. Großund Kleinschreibung ist (noch) nicht überall relevant. Zwischenraum trennt Information, ist
aber meistens nicht weiter signifikant.
HTML ist zunächst eine Anwendung der Standard Generalized Markup Language (SGML);
über den Dokumenttyp (<!DOCTYPE) können die Elemente und ihre Verschachtelung geprüft
werden. SGML erlaubt Kommentare (<!-- -->) an Stelle von Elementen.
5
Selbst dieses triviale Beispiel führt schon eine Schwachstelle vor: Der Internet Explorer 5.5
zentriert den Text unabhängig vom Dokumenttyp, Netscape 6 zentriert für Version 3.2 aber
nicht 4.0, OmniWeb 4.0 beta 9 zeigt immer einen Scroll-Balken.
1.2 HTML mit CSS
Vom Entwurf her sollte HTML Information logisch auszeichnen und die Darstellung dem
Browser überlassen. Das Beispiel zeigt, daß inzwischen Form und Inhalt sehr gründlich
vermengt werden, wobei eine einheitliche Form sehr schwer zu erreichen ist.
hello/2.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<!-- Hello, Osnabrück! mit CSS2
teilweise Trennung von Form und Inhalt
vertikal zentriert
mit jeder HTML version in IE 5.5 aber nur mit 3.2 in Netscape 6
ats 2000-03
-->
<html>
<head>
<title>Hello, Osnabrück!</title>
<style>
body { background: khaki }
table { height: 100%; width: 100% }
td
{ text-align: center;
font: bold xx-large serif;
color: blue
}
</style>
</head>
<body>
<table>
<tr>
<td>
Hello, Osnabrück!
</table>
</body>
</html>
Es empfiehlt sich, Cascading Style Sheets (CSS) zu verwenden. Dabei kann man zum
Beispiel mit Text in einem style-Element im Kopfteil eines HTML-Dokuments die
Darstellungseigenschaften praktisch aller Elemente setzen.
Man kann gemeinsame Eigenschaften für Elemente bündeln und manche Eigenschaften
innerhalb verschachtelter Elemente vererben. Baut man eine geeignete Struktur, kann man
den Darstellungsstil eines ganzen Dokuments sehr zentral kontrollieren.
Die CSS-Eigenschaften verwenden andere Namen und eine völlig andere Syntax als die
HTML-Attribute — Kommentare werden in /* */ eingeschlossen. Manche Aspekte der HTMLDarstellung lassen sich nur mit CSS beeinflussen.
Leider unterstützen nicht alle Browser den gleichen Sprachumfang von CSS und man kann
sich auch nicht darauf verlassen, daß der vorhandene Sprachumfang ähnlich interpretiert
wird. Trotzdem kommt man bei einigermaßen konservativer Anwendung von CSS zu
wesentlich pflegefreundlicheren HTML-Dokumenten.
6
1.3 Alternative ohne Tabellen
div ist ein HTML-Element, das die Darstellung nur unwesentlich beeinflußt. Man kann jedoch
mit CSS für div-Elemente ganz individuelle Darstellungen vorschreiben und damit praktisch
neue HTML-Elemente schaffen:
hello/3.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<!-- Hello, Osnabrück! mit CSS2
teilweise Trennung von Form und Inhalt
näherungsweise vertikal zentriert
mit jeder HTML version in IE 5.5 aber nur mit 3.2 in Netscape 6
ats 2000-03
-->
<html>
<head>
<title>Hello, Osnabrück!</title>
<style>
body
{ background: khaki }
#top
{ height: 45% }
div.center { text-align: center;
font: bold oblique xx-large serif;
color: blue;
}
</style>
</head>
<body>
<div id="top"></div>
<div class="center">Hello, Osnabrück!</div>
</body>
</html>
Ein id-Attribut muß im ganzen Dokument einen eindeutigen Wert besitzen und kann daher
eindeutig mit CSS-Eigenschaften verbunden werden. Ein class-Attribut kann bei vielen
Elementen den gleichen Wert besitzen; damit kann man gleiche Elemente gruppieren und
insgesamt mit CSS-Eigenschaften verbinden.
Das erste, leere div-Element hält hier (fast) die obere Hälfte des Fensters frei. Das zweite
Element zentriert darunter den Text.
Es gibt keine Arithmetik in CSS; folglich kann man so den Text nicht exakt im Zentrum des
Fensters anordnen.
Allein mit CSS kann man Form und Inhalt eines HTML-Dokuments nicht vollständig trennen:
Der Kopfteil eines Dokuments enthält eigentlich auch inhaltsrelevante Informationen (title).
Außerdem muß man sichtlich Elemente einfügen (table oder div), die zwar keinen Inhalt
beisteuern, aber für die Form entscheidend verantwortlich sind.
Letztlich wird hier Information doppelt verwendet (der Text als Inhalt und Titel), die auch
doppelt angegeben werden muß — das ist nicht pflegefreundlich.
7
1.4 XML mit CSS
Die reine Information kann man in Anlehnung an HTML wesentlich knapper formulieren:
hello/5.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="5.xsl" type="text/xsl"?>
<!DOCTYPE hello SYSTEM "5.dtd">
<!-- Hello, Osnabrück! mit XML, UTF-8, XSL und CSS2
in IE 5.5 mit MSXML3 und XMLINST, nicht auf MacOS X
ats 2000-03
-->
<hello>Hello, Osnabrück!</hello>
Die Extensible Markup Language (XML) stammt wie HTML von SGML ab. Auszeichnung,
Elemente und Attribute folgen sehr ähnlichen Regeln, jedoch wird Groß- und Kleinschreibung
unterschieden, Attributwerte müssen unbedingt in Anführungszeichen eingeschlossen sein,
und Elemente müssen immer geschlossen werden.
Eine XML processing instruction (PI) sollte ganz am Anfang des Dokuments stehen. Sie
beschreibt die Version von XML (derzeit nur 1.0) und den Zeichensatz, der für das Dokument
verwendet wird. Hier ist das UTF-8, eine Repräsentierung von Unicode, die im definierten
Bereich identisch zu ASCII ist, darüber hinaus beliebige Sonderzeichen enthält und zum
Beispiel mit TextEdit unter MacOS X bearbeitet werden kann.
In XML kann man beliebige Elemente, Attribute und Attributwerte erfinden und Elemente
beliebig verschachteln. Aus dieser Sicht ist XML erweiterbar.
Man kann sich in einem XML-basierten Dokument aber auch mit <!DOCTYPE auf eine
Document Type Definition (DTD) beziehen und festlegen, was erlaubt ist:
hello/5.dtd
<?xml version="1.0" encoding="UTF-8"?>
<!-- DTD für Hello, Osnabrück! mit XML, UTF-8, XSL und CSS2
ats 2001-03
-->
<!-- root -->
<!ELEMENT hello (#PCDATA)>
Auch eine DTD sollte mit einer xml PI beginnen, um den Zeichensatz zu dokumentieren. Der
Rest der DTD besteht aus Definitionen mit einer an Elemente erinnernden Syntax. Die
Kommentare folgen SGML-Regeln, aber eine DTD ist kein XML-basiertes Dokument.
ELEMENT definiert ein XML-Element und legt seinen Inhalt fest. #PCDATA (parsed character
data) ist Text, der keine Elemente oder Zeichen enthält, die zur Auszeichnung verwendet
werden.
Ohne eine ATTLIST-Definition darf das Element dann (fast) keine Attribute verwenden.
Eine ENTITY-Definition vereinbart Ersatzdarstellungen. In XML sind & > < "
und ' für & > < " und ’ vordefiniert.
8
Da CSS versucht, alle denkbaren Darstellungseigenschaften zu beschreiben und mit
Elementen einer auf SGML beruhenden Sprache zu verknüpfen, kann man vermuten, daß
man XML mit Hilfe von CSS in einem Web-Browser darstellen kann. Zur Zentrierung in der
Fenstermitte muß man allerdings künstliche Elemente einführen:
hello/4.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="4.css" type="text/css"?>
<!-- Hello, Osnabrück! mit XML, UTF-8 und CSS2
näherungsweise vertikal zentriert, kein Fenster-Titel
in IE 5.5 aber nicht in Netscape 6
ats 2000-03
-->
<document>
<vspace/>
<hello>Hello, Osnabrück!</hello>
<box> <!-- als table: funktioniert nicht -->
<horizontal>
<cell>
<hello>Hello, Osnabrück!</hello>
</cell>
</horizontal>
</box>
</document>
Die Lösung beruht zunächst auf der Alternative ohne Tabellen. Dazu muß man ein Element für
den freien Raum oberhalb des Texts einführen. vspace hat keinen Inhalt. Leere Elemente
kann man abkürzen, indem man statt Eröffnung und Schluß einfach / am Ende der Eröffnung
angibt.
Der Inhalt eines XML-basierten Dokuments muß ein einziges Element sein. Hier wurde es
document genannt. Es umgibt vspace und hello.
CSS kann theoretisch auch Elemente als Bausteine von Tabellen darstellen. Dazu benötigt
man mindestens die Tabelle selbst (hier box), eine Zeile (hier horizontal) und eine Zelle
(hier cell). hello wird wiederverwendet, um die Text-Eigenschaften einheitlich zu
repräsentieren.
9
Jetzt sind genügend Elemente vorhanden, so daß man CSS formulieren kann. Das XMLbasierte Dokument verweist auf CSS mit einer xml-stylesheet PI mit den Attributen href für
die Adresse (als URI) und type="text/css".
hello/4.css
/* CSS2 für 4.xml
display: table funktioniert nicht in IE 5.5 und Netscape 6
ats 2001-03
*/
*
{ background: khaki }
vspace { height: 45% }
hello { width: 100%;
text-align: center;
font: xx-large monospace;
color: blue;
}
box
{ display: table; height: 45% }
horizontal { display: table-row }
cell
{ display: table-cell }
Den Hintergrund kann man dadurch zentral einfärben, daß man background mit * für alle
Elemente festlegt.
Normalerweise wird ein Element als block dargestellt und Blöcke stehen untereinander. Mit
der display-Eigenschaft kann man aber auch bestimmen, daß ein Element neben anderen
stehen (inline) oder Baustein einer Tabelle sein soll.
Die Darstellung ohne Tabellen funktioniert wenigstens mit dem Internet Explorer 5.5.
Netscape 6 zeigt den Text schlicht links oben an, ignoriert also unbekannte Elemente, aber
nicht ihren Textinhalt, akzeptiert aber den Verweis auf CSS durch eine PI.
Auch der Internet Explorer 5.5 ist selektiv in seiner Realisierung: Die Alternative ohne Tabellen
funktioniert, die ursprüngliche Lösung auf der Basis von Tabellen funktioniert nicht — offenbar
sind nicht alle Werte der display-Eigenschaft implementiert. Es ist leider beabsichtigt, daß
unverständliche CSS-Anweisungen stillschweigend ignoriert werden.
10
1.5 XML mit XSLT und CSS
CSS legt die Eigenschaften der Darstellung einer SGML-basierten Sprache fest, kann aber
die Elemente selbst nicht beeinflussen und kann (derzeit) weder mit Strings noch mit Zahlen
rechnen.
Die Extensible Stylesheet Language (XSL) ist selbst eine XML-basierte Sprache und soll vom
Ansatz her CSS deutlich übertreffen. Zur Darstellung definiert sie Formatting Objects (XSLFO) mit sehr, sehr vielen Eigenschaften. Eine Objekthierarchie wird durch verschachtelte
Elemente beschrieben. Ein XSL-FO-Dokument besteht aus Layout- und Inhalts-Elementen,
die ein geeigneter Prozessor dann zusammen darstellt. XSL-FO ist bisher nicht abschließend
definiert worden.
Auch mit XSL-FO bleibt das Problem, wie man von reinen Daten in Form von XML-Elementen
zu darstellbaren Daten in Form von HTML- oder XSL-FO-Elementen kommt.
XSL definiert dafür als XML-basierte, extensible Sprache eine Programmiersprache zur
Transformation von Bäumen: XSLT ist abschließend definiert worden. Beispielsweise kann
man das früher betrachtete, XML-basierte Dokument
hello/5.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="5.xsl" type="text/xsl"?>
<!DOCTYPE hello SYSTEM "5.dtd">
<!-- Hello, Osnabrück! mit XML, UTF-8, XSL und CSS2
in IE 5.5 mit MSXML3 und XMLINST, nicht auf MacOS X
ats 2000-03
-->
<hello>Hello, Osnabrück!</hello>
in dieses HTML-Dokument
hello/5.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Hello, Osnabrück!</title>
<link href="5.css" rel="stylesheet" type="text/css">
</head>
<body>
<table>
<tr>
<td>Hello, Osnabrück!</td>
</tr>
</table>
</body>
</html>
11
mit folgendem XSLT-basierten Dokument umwandeln:
hello/5.xsl
<?xml version="1.0" encoding="UTF-8"?>
<!-- HTML/CSS2-Umwandlung für Hello, Osnabrück!
ats 2000-03
-->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="html"/>
<xsl:template match="/">
<html>
<head>
<title><xsl:value-of select="hello"/></title>
<link href="5.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<table>
<tr>
<td>
<xsl:value-of select="hello"/>
</td>
</tr>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Ein XSLT-basiertes Dokument besteht aus einem xsl:stylesheet-Element mit den
angegebenen Attributen. Das xsl:output-Element modifiziert die Ausgabe, so daß sie
bestimmte XML-Konventionen vermeidet, die in HTML-Dokumenten nicht erlaubt sind.
Ein XSLT-Programm versucht, xsl:template-Elemente aus dem XSLT-basierten Dokument
auf einen Dokumentbaum anzuwenden, der oft als XML-basiertes Dokument vorliegt.
match="/" erkennt den Wurzelknoten, unter dem sich der Dokumentbaum — also
insbesondere das äußerste Element des Dokuments — befindet. Dieses xsl:template
agiert also als Hauptprogramm.
Ein xsl:template enthält xsl-Elemente, die ausgeführt werden, und andere Elemente, die in
den Resultatbaum kopiert werden. Hier wird ein Baum aus HTML-Elementen konstruiert, in
den an zwei Stellen mit xsl:value-of der Inhalt des hello-Elements aus dem
Dokumentbaum kopiert wird.
Die Werte der match- und select-Attribute sind sogenannte XPath-Ausdrücke, die vor allem
Teile des Dokumentbaums auswählen. Leider besteht dieser Baum aus der Sicht von XML aus
den Elementen, aus der Sicht von XPath aber aus anderen Knoten — beispielsweise dem
Wurzelknoten / oder eigenen Knoten für Attribute und Textinhalte eines Elements.
Da ein XSLT-basiertes Dokument neben xsl-Elementen beliebige andere Elemente enthalten
kann, kann es keine DTD für XSLT-basierte Dokumente geben. XSLT ist nur well-formed und
erst der XSLT-Prozessor selbst entscheidet, ob die xsl-Elemente korrekt verschachtelt sind.
12
Ausführung
Der Internet Explorer wendet ein XSLT-basiertes Dokument auf ein XML-basiertes Dokument
an, wenn eine PI wie
<?xml-stylesheet href="5.xsl" type="text/xsl"?>
angegeben ist, und wenn eine neuere Version von MSXML3 installiert und mit XmlInst korrekt
in der Windows Registry eingetragen ist. Die Umwandlung findet im Browser statt; Fehler
werden im Browser-Window berichtet.
Der type-Wert der PI ist nicht standardisiert. Richtig sinnvoll ist diese Lösung vor allem auch
deshalb nicht, weil angeblich von Microsoft bisher nicht die endgültige Version von XSLT
unterstützt wird.
Mit aktuellen XSLT-Implementierungen wie Saxon oder Xalan und XML-Parsern wie Crimson,
Xerces oder dem in Saxon bereits integrierten Ælfred kann man die Umwandlung per
Kommando vornehmen:
$ java -classpath saxon.jar com.icl.saxon.StyleSheet 5.xml 5.xsl > 5.html
Auch andere Umwandlungen sind so möglich. Das folgende XSLT-Programm extrahiert nur
den Text und codiert ihn von UTF-8 in ISO-8859-1 um:
hello/6.xsl
<?xml version="1.0" encoding="UTF-8"?>
<!-- Text-Umwandlung für Hello, Osnabrück!
ats 2000-03
-->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="text" encoding="ISO-8859-1"/>
<xsl:template match="/">
<xsl:value-of select="hello"/><xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
$ java -classpath saxon.jar com.icl.saxon.StyleSheet 5.xml 6.xsl | od -c
0000000
H
e
l
l
o
,
O
s
n
a
b
r 374
c
k
0000020
! \n
Der Umgang mit Zwischenraum ist schwierig: Zwischenraum im Kontext von Text wird
normalisiert, reiner Zwischenraum zwischen Elementen wird ignoriert. Das xsl:textElement enthält hier einen Zeilentrenner, der dann in der Ausgabe erhalten bleibt. Hat man
beim Design eines XML-basierten Dokuments keine Rücksicht genommen, kann man die
Ausgabe von Zwischenraum mit XSLT kaum exakt kontrollieren.
Saxon oder Xalan verifizieren nicht, daß ein XML-basiertes Dokument seine DTD einhält.
Dazu muß man gegebenenfalls eine explizite Validierung durchführen:
$ java -classpath ..:xerces.jar -Derror=sax.Errors \
-Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser \
-Dtrue='http://xml.org/sax/features/validation
http://xml.org/sax/features/namespaces' sax.Main 5.xml
13
sax.Main enthält ein Hauptprogramm zum Aufruf von bestimmten XML-Parsern, sax.Errors
ist eine dazugehörige Klasse zum Fehlerbericht. Beide werden später besprochen.
1.6 XML mit XSLT im Server
Der Gedanke liegt nahe, XML erst bei Bedarf im Web-Server mit XSLT in HTML
umzuwandeln. Da sich ein Web-Browser dem Server normalerweise zu erkennen gibt, könnte
man sogar je nach Browser verschieden umwandeln.
tcp.Httpd ist ein sehr rudimentärer, in Java implementierter Web-Server, der für eine
Vorlesung über Verteilte Systeme entwickelt wurde und auf einem allgemeineren ServerFramework tcp.Server beruht.
Da XML-Parser und XSLT-Prozessoren in Java als Klassen implementiert wurden, kann man
sie leicht in einen derartigen Web-Server integrieren:
hello/XHttpd.java
protected void getFile (File file) throws IOException {
String path = file.getCanonicalPath();
if (!path.startsWith(root()))
throw new FileNotFoundException(file.getName());
if (!path.endsWith(".xml")) {
super.getFile(file); return;
}
Im Wesentlichen ersetzt man die Methode getFile(), die die Anfrage nach einer Datei
beantwortet. Endet der Dateiname nicht mit .xml, kümmert sich der ursprüngliche Server um
die Antwort.
hello/XHttpd.java
Exception ex = null;
try {
final String xsl[] = { null };
SAXParserFactory.newInstance()
.newSAXParser().parse(file, new DefaultHandler() {
public void processingInstruction (String target, String data) {
if (xsl[0] == null && target.equals("xml-stylesheet")
&& data.indexOf("text/xsl") != -1) { // need not be with type=
int a = data.indexOf("href="); // could be inside "" or ''
if (a == -1 || a+6 >= data.length()) return;
int b = data.indexOf(data.charAt(a+5), a+6);
if (b == -1 || b == a+6) return;
xsl[0] = data.substring(a+6, b);
}
}
});
if (xsl[0] == null) {
super.getFile(file); return;
}
Andernfalls erzeugt man einen neuen XML-Parser, übergibt ihm die XML-Datei, läßt sich für
jede PI zurückrufen und untersucht, ob sie für xml-stylesheet bestimmt ist und text/xsl
enthält. Falls ja, entnimmt man ihr den Wert eines href-Attributs — die Analyse ist etwas
rudimentär. Findet man nichts, ist wieder der ursprüngliche Server gefragt.
14
hello/XHttpd.java
URL url = file.toURL();
String xml = url.toString(); // file:/path
Transformer transformer =
TransformerFactory.newInstance().newTransformer(
new StreamSource(new URL(url, xsl[0]).openStream()));
if (argc > 2) {
out.println("HTTP/1.0 200 ok");
out.println("Content-Type: text/html\n");
out.flush();
}
transformer.transform(new StreamSource(xml), new StreamResult(out));
catch (ParserConfigurationException e) { ex = e;
catch (SAXException e) { ex = e;
catch (TransformerConfigurationException e) { ex = e;
catch (TransformerException e) { ex = e;
}
}
}
}
}
if (ex != null) throw new IOException(ex.toString()); // yuck...
}
Hat man einen Attributwert, wandelt man ihn im Kontext des Namens der XML-Datei in eine
URL um und öffnet eine Verbindung zu dieser Information im Netz. Man erzeugt einen neuen
XSLT-Prozessor, lädt das XSLT-Programm, wandelt damit schließlich die XML-Datei um, und
liefert das Resultat über den global vorgegebenen OutputStream out beim Auftraggeber von
getFile() ab.
Natürlich kann es eine Reihe von Fehlern geben, die getFile() leider nur auf dem Umweg
als IOException liefern darf.
Die Lösung ist wenig professionell und sehr rudimentär. Im Ernstfall würde man Java Servlets
in einem geeigneten Web-Server oder gleich ein Web-Publishing-Framework wie Cocoon
einsetzen. Hier sollte nur skizziert werden, mit welch geringem Aufwand man die Technologie
in eigenen Programmen zum Einsatz bringen kann. Die Details des Java API for XML
Processing (JAXP) werden später besprochen.
Wenn man den Server übersetzt und startet
$ export CLASSPATH=.:httpd.jar:xerces.jar:saxon.jar:unsealed-jaxp.jar
$ javac XHttpd.java
$ java -Dhttp.server.port=8080 \
-Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl \
-Djavax.xml.transform.TransformerFactory=com.icl.saxon.TransformerFactoryImpl \
XHttpd `pwd`
dann kann man auch zum Beispiel in Netscape 6 bei http://localhost:8080/5.xml den
Text in der Mitte des Browser-Fensters sehen, da ein HTML-Dokument geliefert wird.
Man kann beim Start verschiedene Parser und Transformer kombinieren. Da jedoch die jarDateien für JAXP und Crimson im Manifest Sealed: true enthalten, stören sich manche
Kombinationen, weil Klassen aus dem gleichen package in verschiedenen jar-Dateien
gefunden werden. Man umgeht das Problem, indem man man die versiegelten Archive mit jar
extrahiert und ohne ihr Manifest neu einpackt.
15
1.7 Fazit
Eine auch nur einigermaßen portable Darstellung von Informationen in einem Web-Browser
ist nur über HTML möglich.
Man sollte unbedingt CSS verwenden, um Form und Inhalt der Information einigermaßen zu
trennen, und um speziell die Form zentral manipulieren zu können.
Wenn man nicht kontrollieren kann, daß alle Klienten ausschließlich eine aktuelle Version von
Internet Explorer samt korrekt installiertem XML/XSLT System verwenden — und das kann
auch in einem Intranet kaum garantiert werden — sollte man XML im Server umwandeln und
nicht zum Klienten schicken. (MSXML3 gilt bisher als wenig kompatibel zu der weiteren
Entwicklung von XSLT.)
XML bietet offensichtlich den Vorteil, daß man modernere Zeichensätze verwenden und
Information mehrfach nutzen und verschieden präsentieren kann.
Man kann sich streiten, ob XSLT — mindestens von der Form her — eine benutzbare Sprache
darstellt, aber Handarbeit ist deutlich aufwendiger. XSLT spielt für XML die Rolle, die eine
Sprache wie awk für Text spielt: Man kann Teile eines Dokuments wählen und per Programm
daraus ein neues Dokument erzeugen.
16
17
2
Hypertext Markup Language
Man kann XML schnell in einem Browser darstellen, wenn man es auf HTML abbildet. In
diesem Kapitel (aus der Vorlesung über Internet-Protokolle) wird deshalb der relevante Teil
von HTML kurz zusammengefaßt.
2.1 Begriffe
Unter Hypertext versteht man ein Dokument mit Markierungen, die auf andere Dokumente
verweisen. Ein Browser realisiert normalerweise eine mehr oder weniger elegante Navigation
mit Buchzeichen und Rückverfolgung zwischen den verbundenen Dokumenten. Satz- und
Hilfe-Systeme verwenden häufig Hypertext.
Die Hypertext Markup Language HTML (RFC 1866, veraltet), unter Entwicklung beim WorldWide-Web Consortium, ist unter anderem ein Protokoll, das zwischen Web-Server und
Browser vermittelt. HTML ist im Prinzip nicht vom Hypertext Transfer Protokoll HTTP (RFC
2616) abhängig.
Vom Server zum Browser beschreibt HTML die zumeist logische Auszeichnung eines
Dokuments mit Abschnitten, Tabellen, Typographie, eingebetteten Multimedia-Objekten,
Verweisen und Formularen. HTML dient also zur Repräsentierung von multimedialem
Hypertext.
Ein Verweis wird mit einem Uniform Resource Identifier (RFC 2396) formuliert, der TransportProtokoll und Adresse — aber nicht unbedingt die Art — einer Ressource definiert. Ein
Verweis kann an eine Textstelle oder ein Bild geklammert sein und sich je nach Ressource
auch auf eine Position in der Ressource beziehen.
Ein Formular wird in HTML spezifiziert und dient zur Erfassung von Eingaben im Browser.
Abhängig vom Formular wird die Eingabe codiert und dann vom Browser zum Server
geschickt. Aus dieser Sicht ist HTML auch ein Protokoll, das zwischen Web-Browser und
Server vermittelt. Je nach Transport-Protokoll antwortet der Server, normalerweise mit HTML.
Die Extensible Markup Language XML, unter Entwicklung beim World-Wide-Web Consortium,
ist eine Verallgemeinerung von HTML zur logisch gegliederten Repräsentierung beliebiger
Daten — oft unter Kontrolle einer Grammatik, die als Document Type Definition DTD oder in
Zukunft als XML Schema spezifiziert wird. HTML ist fast eine Ausprägung von XML. Mit Tools
wie einer Implementierung der Extensible Style Language XSL können XML-basierte
Dokumente umgewandelt und zum Beispiel am Bildschirm oder Ducker dargestellt werden.
Andere Tools wie XPATH ermöglichen Verweise zwischen XML-basierten Dokumenten.
Die nachfolgende, sehr rudimentäre Beschreibung orientiert sich an HMTL 4.0.
18
2.2 Struktur
Ein HTML-Dokument hat insgesamt folgende Struktur:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<!-- Beschreibung des Dokuments -->
<title>
Dokument Titel
</title>
<base href=" basis-URL " target=" name|_blank|_self|_parent|_top ">
</head>
<body>
<!-- Inhalt des Dokuments -->
</body>
</html>
Die Auszeichnung erfolgt mit Tags, die einen Namen und Attribute enthalten können. Großund Kleinschreibung ist dabei ziemlich äquivalent, Zwischenraum wird weitgehend ignoriert,
Attributwerte müssen in " ... " eingeschlossen sein oder nur aus Buchstaben, Ziffern, Punkten
und Minuszeichen bestehen. Statt body kann auch ein frameset definiert werden:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"http://www.w3.org/TR/html4/frameset.dtd">
<html>
<frameset rows="33%,1*">
<!-- zwei Zeilen -->
<frameset cols="200,1*">
<!-- oberes Drittel: zwei Spalten... -->
<frame name="a">
<!-- 200 Pixel breit... -->
<frame name="b">
<!-- und der Rest der Breite -->
</frameset>
<frame name="c">
<!-- unteres Drittel, ganze Breite -->
</frameset>
</html>
So entstehen zum Beispiel drei benannte Teilfenster, die unabhängig als target verwendet
werden können. Der Nachteil ist, daß Buchzeichen nicht gut funktionieren.
HTML dient zur Definition von strukturierten Dokumenten, das heißt, zu sehr vielen Tags
<name> gibt es End-Tags </name>; die dadurch entstehenden Elemente können nur ganz
begrenzt (und selten rekursiv) verschachtelt werden.
Primäres Ziel ist natürlich, Form und Inhalt zu trennen und die Form nur annähernd im
Dokument zu spezifizieren. Der Inhalt eines Dokuments sollte logisch gegliedert sein:
<address> ... </address>
<blockquote> ... </blockquote>
<br>
<div> ... </div>
<dl> ... </dl>
<h1> ... </h1> ...
<h6> ... </h6>
<hr>
<ol> ... </ol>
<p>
<pre> ... </pre>
<table> ... </table>
<ul> ... </ul>
begrenzt eine Adreßangabe, kann a, p enthalten
begrenzt ein Zitat, kann a, p enthalten
Break: Zeilenende ohne Freiraum, nicht in pre
begrenzt Teil einer Seite (für Style-Sheet)
Liste mit Definitionen, enthält paarweise dt und dd;
Listen können geschachtelt werden
begrenzen Überschriften mit zunehmendem Detail,
können Schriftwechsel enthalten (unlogisch)
horizontale Linie
numerierte Liste, enthält li
Absatz: Zeilenende mit Freiraum
vorformatierter Text in dicktengleicher Schrift, kann
unter anderem a, p, und Schriftwechsel enthalten
Tabelle
unnumerierte Liste, enthält li
19
2.3 Schriftzeichen
HTML soll in ASCII aufgeschrieben werden; man kann allerdings bei Verweisen(!) den
Zeichensatz des Ziel-Dokuments angeben.
Es gibt eine Reihe von sehr wichtigen Ersatzdarstellungen (entities), die alle mit & beginnen
und mit ; aufhören:
&
>
<
"
ß
ä
&
>
<
"
ß
ä und analog alle anderen großen und kleinen Umlaute
Analog zu uml gibt es die Auszeichnungen acute, circ, grave, ring, tilde und cedil, die
jedoch nur auf die üblichen Zeichen angewendet werden dürfen, sowie die isländischen
großen und kleinen Zeichen eth und thorn und viele andere Zeichen, aber zum Beispiel
keine griechischen Buchstaben.
Mit &#ddd; kann man Zeichen auch als dezimale Bytes repräsentieren.
2.4 Tabellen
Tabellen werden häufig zur zweidimensionalen Anordnung von Dingen mißbraucht. Die
Struktur ist einfach:
<table>
<tr>
<th>
<td>
</table>
<!-- leitet jede Zeile ein -->
<!-- leitet Daten für eine Titel-Zelle ein -->
<!-- leitet Daten für eine normale Zelle ein -->
Es gibt aber sehr viele Attribute, zum Beispiel colspan, rowspan, align und valign, um
Zellen zusammenzufassen und die Anordnung innerhalb einer Zelle zu steuern.
Wie bei br, li und p entfallen normalerweise die End-Tags der Zeilen und Zellen.
2.5 Eingebettete Objekte
Je nach Browser können verschiedene Grafik-Formate dargestellt werden:
<img src="url" alt="text" height=pixel width=pixel>
img kann als Verweis in a geschachtelt werden. Außerdem kann man Verweise mit
Teilgebieten einer Grafik verknüpfen:
<map name="x">
<area shape=circle coords="x,y,rad" href="url"> <!-- Click-Fläche -->
...
</map>
<img ... usemap="x">
Mit object (früher applet und embed) gibt man Flächen im Browser für andere Applikationen
frei. Sun verschenkt einen HTML-Konverter, der applet-Tags übersetzt.
<object ...>
<param ...>
</object>
Mit script soll man zum Beispiel JavaScript einfügen.
20
2.6 Typographie
Es gibt logische Tags wie strong zur Betonung, physische Tags wie b für Fett- und tt für
Schreibmaschinenschrift und schließlich (nicht mehr) font zur expliziten Angabe von
Schriftmerkmalen und Farben. Die Namen stammen angeblich von texinfo.
abbr
acronym
cite
code
div
em
kbd
q
samp
strong
var
b
big
i
small
sub
sup
tt
Abkürzung
Akronym
Zitat
Programmtext, dicktengleich
kann auf Style-Sheet verweisen
betont
Eingabe, dicktengleich, oft fett
Zitat
Ausgabe, dicktengleich
starke Betonung
Variablenname, oft kursiv
fett
größer
kursiv
kleiner
Subskript
Superskript
dicktengleich
Zur typographischen Auszeichnung sollten wenn irgend möglich nur die logischen Tags
benutzt werden. Browser sind nicht verpflichtet, dies alles wirklich zu unterscheiden.
Schriftwechsel können geschachtelt werden (nicht unbedingt logisch) und img und br aber
zum Beispiel nicht p oder Listen enthalten.
21
2.7 Verweise
<a name="Marke">
<!-- definiert Ziel im Dokument -->
<a href="url#Marke">Text oder Image</a> <!-- bindet Text/Bild mit Verweis -->
Als Verweis wird — wie auch in img, frame und form — ein Universal Resource Identifier URI
(RFC 2396) verwendet, mit dem prinzipiell jede Ressource im Internet beschrieben werden
kann. URI ist der Oberbegriff für Universal Resource Locator URL — damit soll eine
Ressource beschafft werden können — und Universal Resource Name URN — dies soll ein
persistenter, protokoll-unabhängiger Name für eine Ressource sein.
Damit man alles beschreiben kann, beginnt ein URI mit einem registrierten Schema-Namen,
von dem dann die Syntax des Rests abhängt:
absoluteURI
scheme
= scheme ":" ( hierarchical | opaque )
= ALPHA *( ALPHA | DIGIT | "+" | "-" | "." )
opaque kann praktisch alle Zeichen enthalten. Ausgenommen ist fast nur der Schrägstrich /,
der als Pfadtrenner verwendet wird. Ein Beispiel für einen nicht-hierarchischen URI für eine
nur schreibbare Ressource ist
mailto:[email protected]
Der Rest dieser Ideen orientiert sich stillschweigend an Protokollen wie FTP und HTTP, die
zugleich als Schema-Namen verwendet werden:
hierarchical
net_path
authority
= ( net_path | abs_path ) [ "?" query ]
= "//" authority [ abs_path ]
= server | reg_name
reg_name ist ein anderweitig definierter, registrierter Name ohne Schrägstriche.
server
hostport
host
= [ [ userinfo "@" ] hostport ]
= host [ ":" port ]
= hostname | IPv4address
userinfo besteht aus einem Benutzernamen, der zur Authentifizierung dienen soll. Zwar
kann man ein Paßwort nach Doppelpunkt angeben, aber das würde im Klartext übertragen
werden. hostname ist ein DNS-Name, port besteht aus Ziffern und die IPv4address benutzt
die übliche dezimale Darstelliung.
abs_path
path_segments
segment
param
pchar
escaped
char
=
=
=
=
=
=
=
"/" path_segments
segment *( "/" segment )
*pchar *( ";" param )
*pchar
char | escaped | ":" | "@" | "&" | "=" | "+" | "$" | ","
"%" hex hex
alphanum | "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
Ein abs_path kann wie ein ungesäuberter Unix-Pfad aussehen. param soll nichts weiter
bedeuten — könnte aber eine Versionsnummer in einem entsprechenden Dateisystem sein.
In einer query sind ; / ? : @ & = + , und $ reserviert.
Neben dem absoluteURI gibt es auch noch einen relativeURI mit sehr aufwendigen
Regeln, wie er kontextabhängig zu einem absoluteURI umgerechnet werden soll.
relativeURI
rel_path
rel_segment
= ( net_path | abs_path | rel_path ) [ "?" query ]
= rel_segment [ abs_path ]
= 1*( char | escaped | ";" | "@" | "&" | "=" | "+" | "$" | "," )
Ein segment darf : und ; param enthalten, ein rel_path muß mit einem rel_segment
beginnen, das : und ; param nicht, dafür aber ; enthalten darf — eine lex Mac?
22
2.8 Werkzeuge
Von Dave Raggett, dem Author der neueren HTML-Spezifikationen, stammt tidy, ein HTMLParser und -Pretty-Printer, mit dem man Fehler finden und HTML standardisiert repräsentieren
kann:
$ tidy -config tidyrc -quiet ../hello/1.html
Can't open "/Users/axel/.tidyrc"
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<!-- Hello, Osnabrück! in HTML
vertikal zentriert
mit jeder HTML version in IE 5.5 aber nur mit 3.2 in Netscape 6
ats 2000-03
-->
<html>
<head>
<meta name="generator" content="HTML Tidy, see www.w3.org">
<title>Hello, Osnabrück!</title>
</head>
<body bgcolor="khaki">
<table height="100%" width="100%">
<tr>
<td align="center"><font color="blue" size="7" face=
"sans-serif"><b>Hello, Osnabrück!</b></font> </td>
</tr>
</table>
</body>
</html>
You are recommended to use CSS to specify the font and
properties such as its size and color. This will reduce
the size of HTML files and make them easier maintain
compared with using <FONT> elements.
tidy ist für sehr viele Plattformen verfügbar. Speziell für Windows gibt es neben dem einfachen
Kommando auch frei verfügbare und kommerzielle Versionen der Editoren notetab und htmlkit, in die unter anderem tidy integriert ist.
23
3
Cascading Style Sheets
Cascading Style Sheets (CSS) wurden eingeführt, um die Darstellung von HTML in WebBrowsern genau zu kontrollieren und dabei Form (CSS) und Inhalt (HTML) weitgehend zu
trennen.
Level 1 wird von den Web-Browsern zumeist unterstützt, Level 2 anscheinend nur von
Netscape 6 — deshalb bleibt Level 2 im Folgenden unberücksichtigt.
Will man XML-basierte Dokumente in Web-Browsern darstellen, kann man prinzipiell CSS
verwenden — wobei man allerdings alle Details spezifizieren muß — dies funktioniert erst ab
Version 5.5 im Internet Explorer.
Vernünftiger ist es, die Dokumente dafür vorher auf HTML zu transformieren — dabei kann
man auch die Information selbst noch bearbeiten. Damit Form und Inhalt getrennt bleiben,
sollte man aber auch in diesem Fall CSS einsetzen.
Prinzipiell könnte auch die Transformation mit XSLT im Internet Explorer erfolgen, aber selbst
Version 5.5 realisiert nur eine ziemlich veraltete Definition von XSLT.
Validator
Das World-Wide-Web-Consortium bietet einen CSS-Validierer an — derzeit nicht sehr robust
und nur zum upload.
24
3.1 Achitektur
Ein Dokument wird als Baum betrachtet, dessen Knoten man Properties zur Formatierung
zuweist. Dazu dienen konstante Anweisungen der Form
selektor { property-name : wert ! important ; ... } /* comment */
CSS ist nicht zeilenorientiert. Zwischenraum dient zur Trennung. Kommentare folgen den
Konventionen von C.
Als selektor dient ein Muster, das einige Baumknoten erfüllen; beispielsweise wird ein Name
von allen Knoten erfüllt, die Dokument-Elemente dieses Namens repräsentieren. In HTML
dürfen manche Elemente fehlen — im Baum werden sie aber stillschweigend eingefügt.
Properties und Werte werden weiter unten erklärt. Nicht alle Properties werden bei allen
Knoten in der Darstellung berücksichtigt. Um aufwärts kompatibel zu sein, darf eine CSSImplementierung beliebige Properties stillschweigend ignorieren.
Ein Dokument muß seine CSS-Anweisungen enthalten oder auf sie verweisen:
<html>
<head>
<link rel=stylesheet type="text/css" href=" URI "> <!-- extern -->
<style type="text/css">
<!-- intern -->
@import url( URI );
<!-- importiert (zuerst) -->
h1 { color: blue }
</style>
</head>
<body>
<h1> blue </h1>
<p style="color: green">green.
<!-- lokal -->
</body>
</html>
Vererbung
Manche Properties werden vererbt, das heißt, vom umgebenden Baumknoten übernommen,
wenn sie im aktuellen Baumknoten nicht explizit definiert sind.
Beispiel: Die Schrift-Spezifikation font und die Vordergrund-Farbe color werden vererbt. Die
Hintergrund-Spezifikation background wird nicht vererbt sondern scheint durch, weil ihre
Voreinstellung transparent ist.
Kaskadierung
Ein Dokument in einem Web-Browser kann auf eine Reihe von Stylesheets (Dokumenten mit
CSS-Anweisungen) verweisen. Gewichtung regelt dann, welche Anweisung wirklich gilt:
Anweisungen gelten, wenn die Muster passen; sonst gilt Vererbung und schließlich die
Voreinstellung der Property.
important hat Vorrang.
Dokument hat Vorrang vor Browser-Benutzer (wie?) und dann vor Browser-Voreinstellung.
Mehr id in Muster, class in Muster und Anzahl Namen im Muster haben je mehr Vorrang.
Schließlich haben später definierte Regeln Vorrang. @import muß am Anfang stehen.
Da Unbekanntes stillschweigend ignoriert wird, ist die Fehlersuche sehr mühsam.
25
3.2 Patterns
body
*
#x24
p#x24
.numerisch
td.numerisch
a:link
a:visited
a:active
p:first-letter
p:first-line
a img
p, td
wählt ein Element
wählt alle Elemente
wählt nach (eindeutigem) id-Attribut
wählt nur, wenn Element dieses id-Attribut hat
wählt nach class-Attribut
wählt nur, wenn Element dieses class-Attribut hat
wählt einen unberührten Link
wählt einen berührten Link
wählt aktivierten Link
wählt erstes Zeichen
wählt erste Zeile
wählt ein (tief) verschachteltes Element
oder-Verknüpfung
Man kann beliebige einfache Muster mit , oder-verknüpfen.
Level 2 bietet weitere Muster, zum Beispiel für direkte Verschachtelung oder zur Wahl
benachbarter Elemente. Außerdem kann man bei einigen Pseudo-Elementen mit einer
content-Property Inhalt einfügen.
3.3 Box-Modell
Jedes Element wird als Box betrachtet, um die herum mit Properties zusätzliche Flächen
definiert werden können:
margin
border
padding
content
element width
box width
content und padding haben beide den background des Elements. border kann zum Beispiel
als Linie in einer anderen Farbe ausgeführt werden. margin ist immer durchsichtig, wird also
vom umgebenden Element kontrolliert.
Der Report beschreibt, wie Elemente relativ zueinander angeordnet werden. CSS2 erlaubt
auch fixed, eine nicht im Browser-Fenster verschiebbare position.
26
3.4 Properties
css/css1
/* CSS1 properties, ordered as per REC-css1
*inherited, default (if any) first, ?preceding restricted to
*color: <color>
Die Datei code/css/css1 ist ein Extrakt aus dem Report und beschreibt die CSS1-Properties,
ihre möglichen Werte und Voreinstellungen.
* vor einer Property zeigt an, daß sie vererbt wird.
Der erste angegebene Wert ist normalerweise die Voreinstellung. Eine Angabe wie <color>
bedeutet, daß es dafür eine besondere Syntax gibt: Namen von Farben oder explizite RGBAngaben der Form #rrggbb mit hexadezimalen oder rgb(r,g,b) mit Werten in 0..255 oder
Prozent.
color ist die Vordergrund-Farbe, die für Text verwendet wird. Sie sollte zusammen mit
background definiert werden.
css/css1
background:
background-color:
background-image:
background-repeat:
background-attachment:
background-position:
-color -image -repeat -attachment -position
transparent | <color>
none | <url>
repeat | repeat-x | repeat-y | no-repeat
scroll | fixed
0% 0% | <percentage>.. | <length>..
| top|center|bottom left|center|right
?block-level and replaced elements
background beschäftigt sich mit dem Hintergrund und sollte zusammen mit color definiert
werden. Man kann eine Farbe oder ein Bild angeben.
block-level sind Elemente mit der Property display: block, also alle, die sich nicht mit der
Typographie einzelner Zeichen beschäftigen. replaced sind Elemente wie img, die durch
externes Material ersetzt werden.
css/css1
*font:
*font-style:
*font-variant:
*font-weight:
*font-size:
*font-family:
[-style -variant -weight] -size [/line-height] -family
normal | italic | oblique
normal | small-caps
normal | bold | bolder | lighter
| 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
medium | xx-|x-|small|large | larger | smaller
| <length> | <percentage>
<name> ,...
font legt die Schrift fest. serif, sans-serif, monospaced, cursive und fantasy
überlassen dem Browser die exakte Schriftwahl.
<length> kann man in em, ex und px relativ zur Schrift (bei font zur umgebenden Schrift)
und zum Schirm angeben. Absolute Einheiten sind in, cm, mm, pt und pc (Pica, 12pt).
Angaben in Prozent sind relativ, werden dann aber als absolute Werte vererbt.
27
css/css1
*word-spacing:
normal | <length>
*letter-spacing: normal | <length>
text-decoration: none | underline overline line-through blink ..
vertical-align: baseline | sub | super | top | middle | bottom
| text-top | text-bottom | <percentage>
?inline elements
*text-transform: none | capitalize | uppercase | lowercase
*text-align:
left | right | center | justify
?block-level elements
*text-indent:
0 | <length> | <percentage>
?block-level elements
*line-height:
normal | <number> | <length> | <percentage>
word- und letter-spacing kann prinzipiell auch negativ sein.
text-decoration kann man kombinieren. Sie soll sich auch auf verschachtelte Elemente
beziehen.
vertical-align ist relativ zum umgebenden Element. Nur top und bottom beziehen sich
auf die formatierte Zeile, dürfen aber nicht zirkulär verwendet werden.
text-align gilt für den Text relativ zu seinem Element; center wirkt sich also erst aus, wenn
width ebenfalls beeinflußt wird.
text-indent kann prinzipiell auch negativ sein.
line-height kann auch als (vererbter) Faktor der Schriftgröße definiert werden.
css/css1
padding:
padding-top:
padding-right:
padding-bottom:
padding-left:
-top -right -bottom -left
0 | <length> | <percentage>
0 | <length> | <percentage>
0 | <length> | <percentage>
0 | <length> | <percentage>
padding vergrößert die Fläche eines Elements, wobei background übernommen wird. Man
kann auch einen gemeinsamen Wert angeben, oder zwei Werte, die dann symmetrisch
verwendet werden.
css/css1
border border-top|right|bottom|left: -color -style -width
border-width: -top -right -bottom -left
border-color: <color> ..
border-style: none | dotted | dashed | solid | double | groove
| ridge | inset | outset
border-top|right|bottom|left-width: medium | thin | thick | <length>
border umgibt das Resultat. Damit kann man zum Beispiel auch Linien erzeugen.
28
css/css1
margin:
margin-top:
margin-right:
margin-bottom:
margin-left:
-top -right -bottom -left
0 | <length> | <percentage>
0 | <length> | <percentage>
0 | <length> | <percentage>
0 | <length> | <percentage>
|
|
|
|
auto
auto
auto
auto
margin umgibt wieder das Resultat, ist aber durchsichtig und übernimmt folglich background
aus der Umgebung.
css/css1
width: auto | <length> | <percentage>
?block-level and replaced elements
height: auto | <length>
?block-level and replaced elements
float:
none | left | right
clear:
none | left | right | both
display: block | inline | list-item | none
white-space: normal | pre | nowrap
?block-level elements
list-style: -type -image -position
?all only to elements with 'display' value 'list-item'
list-style-type:
disc | circle | square | decimal | lower-roman | upper-roman
| lower-alpha | upper-alpha | none
list-style-image:
none | <url>
list-style-position: outside | inside
width und height legen die Elementfläche explizit fest, zum Beispiel bei img.
float legt ein Element als display: block fest und läßt den Text auf der
gegenüberliegenden Seite vorbeifließen. Das muß nicht beliebig tief funktionieren.
clear schiebt ein Element so weit nach unten, bis sich auf den angegebenen Seiten keine
float-Elemente befinden.
display differenziert Abschnitte und Typographie sowie Listem, kann aber auch die Ausgabe
unterdrücken. In CSS2 gibt es weitere Werte für Tabellen.
white-space kann bei normal modifiziert dargestellt werden, muß aber bei pre exakt
erhalten bleiben. Bei nowrap muß man mit br-Elementen explizit umbrechen — dieser Aspekt
von br kann in CSS nicht ausgedrückt werden.
29
3.5 Beispiele
CSS1 schlägt folgende Voreinstellung für HTML vor:
BODY {
margin: 1em;
font-family: serif;
line-height: 1.1;
background: white;
color: black;
}
H1, H2, H3, H4, H5, H6, P, UL, OL, DIR, MENU, DIV,
DT, DD, ADDRESS, BLOCKQUOTE, PRE, BR, HR, FORM, DL {
display: block }
B, STRONG, I, EM, CITE, VAR, TT, CODE, KBD, SAMP,
IMG, SPAN { display: inline }
LI { display: list-item }
H1, H2, H3, H4 { margin-top: 1em; margin-bottom: 1em }
H5, H6 { margin-top: 1em }
H1 { text-align: center }
H1, H2, H4, H6 { font-weight: bold }
H3, H5 { font-style: italic }
H1 { font-size: xx-large }
H2 { font-size: x-large }
H3 { font-size: large }
B, STRONG { font-weight: bolder } /* relative to the parent */
I, CITE, EM, VAR, ADDRESS, BLOCKQUOTE { font-style: italic }
PRE, TT, CODE, KBD, SAMP { font-family: monospace }
PRE { white-space: pre }
ADDRESS { margin-left: 3em }
BLOCKQUOTE { margin-left: 3em; margin-right: 3em }
UL, DIR { list-style: disc }
OL { list-style: decimal }
MENU { margin: 0 }
LI { margin-left: 3em }
/* tight formatting */
DT { margin-bottom: 0 }
DD { margin-top: 0; margin-left: 3em }
HR { border-top: solid }
/* 'border-bottom' could also have been used */
A:link { color: blue }
A:visited { color: red }
A:active { color: lime }
/* unvisited link */
/* visited links */
/* active links */
/* setting the anchor border around IMG elements
requires contextual selectors */
A:link IMG { border: 2px solid blue }
A:visited IMG { border: 2px solid red }
A:active IMG { border: 2px solid lime }
30
Die Vorlesungsunterlagen beruhen auf XML, XSLT und CSS:
Homepage
index.xml
index.xsl
Code
code/index.xml
code/index.xsl
ftp/index.xml
ftp/index.xsl
rec/index.xml
rec/index.xsl
(FrameMaker)
FTP-Bereich
Artikel
Skript
Farbtabelle
RGB-Tabelle
index.html
home.html
title-frame.html
code/index.html
code/css/common.css
ftp/index.html
code/css/common.css
rec/index.html
code/css/common.css
html/skript.html
html/images/skript.css
code/css/common.css
code/css/common.css
code/xslt/colors.xsl
code/xslt/colors.23.html
code/xslt/doColors.xsl code/xslt/colors.127.html
code/xslt/makefile
code/xslt/rgb.txt
code/xslt/rgb.html
code/xslt/rgb2xml
code/xslt/rgb.xsl
Navigation und CSS2
Die Homepage der Vorlesung demonstriert, daß man mit frameset und frame eine
Navigation im Stil der Bookmarks von PDF anbieten kann, die beim Verschieben des
Seiteninhalts nicht verschwindet.
Der Nachteil dieser Konstruktion ist, daß Bookmarks und History im Browser ziemlich sicher
nicht den beabsichtigten Effekt — Wiederherstellung einer kompletten Position — erreichen.
Mit den CSS2-Properties position: fixed und einem mit z-level: 1 nach vorne
verschobenen Bereich kann man eine vergleichbare Navigation auch innerhalb einer einzigen
Seite realisieren. Mit einem iframe müßte die Navigation immer noch zugänglich bleiben,
auch wenn sie größer ist, als der sichtbare Bereich.
Mindestens beim Internet Explorer 5.1 unter MacOS X ist diese Lösung noch nicht optimal.
31
4
Compilerbau
Mit XML kann man Sprachen entwerfen und implementieren. Dies ist normalerweise die
Aufgabe des Compilerbaus — allerdings mit einer deutlich benutzerfreundlicheren
Darstellung.
Dieses Kapitel skizziert Begriffe und Grundlagen des Compilerbaus, um für die nachfolgende
Definition von XML eine solide Unterlage und ein vergleichbares Konzept zu liefern.
4.1 Sprachbeschreibung
Eine Sprache ist die Gesamtheit aller Sätze, das heißt, ausgewählter endlicher Folgen, die
man aus einer endlichen Menge von Eingabesymbolen (terminals) bilden kann.
Eine Grammatik besteht aus dieser endlichen Menge von Eingabesymbolen sowie einer
endlichen Menge von Grammatikbegriffen (nonterminals), einem Grammatikbegriff als
Startsymbol und einer endlichen Menge von Regeln, nämlich Paaren von endlichen Folgen
von Eingabesymbolen und Grammatikbegriffen.
Nach Chomsky unterscheidet man vier Arten von Grammatiken an der Form
ihrer Regeln. Zwei Arten sind im Compilerbau besonders wichtig. Kontextfreie Grammatiken haben Regeln der folgenden Form:
nonterminal: Folge von terminals und nonterminals
Reguläre Grammatiken verwenden noch einfachere Regeln:
nonterminal: terminal
nonterminal: nonterminal terminal
Regeln gibt man oft in Backus-Naur-Form (BNF) an. Eine typische Grammatik für
arithmetische Ausdrücke sieht dann so aus:
sum:
product | sum "+" product | sum "-" product;
product: term | product "*" term | product "/" term | product "%" term;
term:
Number | "(" sum ")" | "+" term | "-" term;
Grammatikbegriffe treten links auf. Andere Namen wie Number stehen für Kategorien von
Eingabesymbolen (token). Strings stellen Eingabesymbole direkt dar (literal). Für alle Regeln
zum gleichen Grammatikbegriff schreibt man nur eine Regel, in der man alternative rechte
Seiten durch | trennt. Der erste Grammatikbegriff ist zugleich das Startsymbol.
In der Extended-Backus-Naur-Form (EBNF) vermeidet man Rekursion weitgehend durch eine
besondere Syntax für Wiederholungen:
oops/expr.ebnf
// grammar for arithmetic expressions
// written in modified EBNF
lines:
sum:
product:
term:
( sum? "\n" )*;
product ( "+" product | "-" product )*;
term ( "*" term | "/" term | "%" term )*;
Number | "(" sum ")" | "+" term | "-" term;
32
Klammern dienen zum Gruppieren und * folgt einem Element, das beliebig oft wiederholt
werden kann. Analog wird für + ein- oder mehrmals wiederholt und ein Element, dem ? folgt,
kann auftreten oder fehlen.
Wirth hat dafür sogenannte Syntaxgraphen eingeführt, in denen Eingabesymbole in runden
und Grammatikbegriffe in rechteckigen Knoten stehen:
lines
sum
product
sum
\n
+
term
product
term
Number
*
sum
(
/
+
term
%
-
term
)
Etwas disziplinierter kann man das auch auf der Basis von Nassi-Shneiderman-Diagrammen
ausdrücken:
lines
sum
product
sum
"\n"
product
"+"
"-"
product
product
term
term
"("
Number
"*"
"/"
"%"
")"
term
term
"+"
"-"
term
term
sum
term
Beide Arten von Grammatik-Bildern suggerieren, daß man offenbar aus einer Grammatik sehr
leicht ein Erkennungsprogramm erzeugen kann — die Methode des rekursiven Abstiegs:
sum: product ( "+" product | "-" product )*;
ergibt etwa
sum() {
product();
while (true) {
if (input.equals("+")) product();
else if (input.equals("-")) product();
else break;
}
33
Eine Grammatik produziert einen Syntaxbaum. Man beginnt mit dem Startsymbol als Wurzel,
wählt eine seiner rechten Seiten und erzeugt für jedes Symbol darin einen Unterknoten. Das
Verfahren kann fortgesetzt werden, bis alle Blätter Eingabesymbole sind.
sum
sum
+
product
product
term
term
Number
Number
2
3
product
term
*
Number
4
Die Zeichnung zeigt einen Syntaxbaum für den arithmetischen Ausdruck 2 + 3 * 4, wenn man
als Startsymbol sum und für die Number-Blätter konkret die Eingaben 2, 3 und 4 wählt. Die
Folge der Blätter nennt man einen Satz über der Grammatik.
Eine Grammatik ist mehrdeutig, wenn es für irgendeinen Satz mehr als einen Syntaxbaum
gibt. Wandelt man die bisher betrachtete Grammatik ganz leicht ab, erhält man zwar die
gleichen Sätze, aber man kann zum Beispiel für 2 + 3 + 4 zwei verschiedene Syntaxbäume
konstruieren:
sum
sum
sum
product
sum
sum
product
product
term
term
Number
Number
term
Number
2
+
3
term
term
product
product
+
4
term
sum
sum
product
sum
sum
sum
34
Für arithmetische Ausdrücke erzeugt man häufig auch Formelbäume. Sie gleichen den
Syntaxbäumen, reduzieren die Knoten aber auf die, die für eine Bewertung wesentlich sind:
+
*
2
3
4
Formelbäume stellen die Bedeutung eines Ausdrucks dar. Sie ergeben sich offensichtlich aus
den Syntaxbäumen und daher kann man zwei verschiedene Syntaxbäume für den gleichen
Satz nicht tolerieren. Es ist also für den Compilerbau sehr wesentlich, daß eine Grammatik
nicht mehrdeutig ist.
Allgemein ist das nicht zu entscheiden, aber man kann hinreichende Bedingungen angeben,
wie zum Beispiel LL(1):
lines
sum
product
sum
\n
+
term
product
term
*
Number
sum
(
/
+
term
%
-
term
)
In den Syntaxgraphen muß man an jeder Abzweigung (hier in sum) mit einem Eingabesymbol
entscheiden können, wie der rekursive Abstieg weitergehen soll.
sum
product
"+"
"-"
product
product
Anhand der Nassi-Shneiderman-Diagramme sieht man, daß man das für EBNF bei jedem
Baustein entscheiden muß, und daß die Bausteine einander bei der Entscheidung helfen
müssen — hier muß zum Beispiel die verschachtelte Alternative der sie umgebenden
Wiederholung, erklären, daß sie nur mit + und - etwas anfangen kann.
35
4.2 Spracherkennung
Die Nassi-Shneiderman-Diagramme haben folgende Grundelemente:
Lit
"\n"
Alt |
Token
Id
Number
Opt ?
Seq
sum
Some +
Many *
oops.parser ist eine Klassenhierarchie, die entsprechende Klassen enthält, aus denen man
Bäume zur Repräsentierung von Grammatiken bauen und serialisieren kann, die in EBNF
spezifiziert sind. Boot baut und serialisiert den Baum aus einer Grammatik:
$ java -classpath .:oops.jar Boot expr.ebnf > expr.ser
Die Klassen implementieren Methoden, mit denen man die LL(1)-Bedingung für einen Baum
prüfen kann, bevor man den Baum serialisiert. Die folgende Grammatik beschreibt Bit-Folgen,
ist aber nicht LL(1):
oops/bits.ebnf
// a grammar which is not LL(1)
bits: ( "0" | "1" )* ( "0" | "1" );
$ java -classpath .:oops.jar Boot bits.ebnf > /dev/null
oops.parser.Many, warning:
[{ ( "0" | "1" ) }]: ambiguous, will shift
lookahead {[empty], "1", "0"}
follow {"1", "0"}
Hier kann die Wiederholung Many nicht entscheiden, ob sie aufhören soll.
Die Klassen implementieren auch Methoden auf der Basis vom rekursiven Abstieg, mit denen
man untersuchen kann, ob eine Eingabe ein Satz zu der Grammatik ist, die ein Baum
repräsentiert.
oops.Compile definiert ein Hauptprogramm, das einen serialisierten Grammatik-Baum
einliest, einen Scanner instantiiert, und dann eine Eingabe untersucht.
oops/a.expr
# example of an arithmetic expression
3 + 4 * 5
$ java -classpath .:oops.jar oops.Compile expr.ser Scanner a.expr > /dev/null
No suitable Goal for rule lines found; will use a GoalAdapter.
No suitable Goal for rule sum found; will use a GoalAdapter.
No suitable Goal for rule product found; will use a GoalAdapter.
No suitable Goal for rule term found; will use a GoalAdapter.
Da ein Satz vorliegt, erfolgt keine weitere Ausgabe.
36
Man benötigt allerdings einen Scanner, der bei advance() Eingabezeichen liest, Kommentare
und insignifikante Zeichen entfernt, und die restlichen Zeichenfolgen als tokenSet() abliefert.
oops/Scanner.java
/** lexical analyzer for arithmetic expressions.
*/
public class Scanner implements oops.parser.Scanner {
protected StreamTokenizer st; // to read input
protected Parser parser;
// to get Lit/Token tokenSet values
protected Set NUMBER;
// tokenSet for Number
protected Set tokenSet;
// null or lookahead identifying token
protected Object node;
// Double/Long value for Number
public Set tokenSet () { return tokenSet; }
public Object node () { return node; }
public String toString () { return st.toString(); }
public void scan (Reader r, Parser parser) throws IOException {
st = new StreamTokenizer(new FilterReader(new BufferedReader(r)) {
boolean addSpace; // kludge to duplicate \n
public int read () throws IOException {
int ch = '\n';
addSpace = addSpace ? false : (ch = in.read()) == '\n';
return ch;
}
});
st.resetSyntax();
st.commentChar('#');
// comments from # to end-of-line
st.wordChars('0', '9');
// parse decimal numbers as words
st.wordChars('.', '.');
st.whitespaceChars(0, ' '); // ignore control-* and space
st.eolIsSignificant(true);
// need '\n'
this.parser = parser;
NUMBER = ((Token)parser.getPeer("Number")).getLookahead();
advance();
// one token lookahead
}
public boolean atEnd () { return st.ttype == st.TT_EOF; }
public boolean advance () throws IOException {
if (atEnd()) return false;
switch (st.nextToken()) {
case StreamTokenizer.TT_EOF: node = null; tokenSet = null; return false;
case StreamTokenizer.TT_EOL: node = null; tokenSet = parser.getLitSet("\n");
break;
case StreamTokenizer.TT_WORD: node = st.sval.indexOf(".") < 0
? (Number)new Long(st.sval)
: (Number)new Double(st.sval);
tokenSet = NUMBER;
break;
default:
node = ""+(char)st.ttype;
tokenSet = parser.getLitSet((String)node);
break;
}
return true;
}
}
JLex kann Scanner aus Mustern generieren (aber oops.parser.Scanner ist nötig).
37
oops.parser.Scanner verlangt folgende Methoden:
scan() wird einmal aufgerufen und liefert an den Scanner einen Reader, von dem die
Eingabezeichen gelesen werden müssen. Außerdem erhält der Scanner eine Verbindung zum
Grammatik-Baum (parser), mit der man bestimmte Werte berechnen kann.
advance() fordert den Scanner auf, das nächste Eingabesymbol zu berechnen; dies sollte
scan() selbst schon für das erste Symbol veranlaßt haben.
advance() liefert true, wenn ein Symbol verfügbar ist.
Umgekehrt liefert atEnd() dann true, wenn der Scanner das Ende der Eingabe erreicht hat.
tokenSet() wird aufgerufen, um das Eingabesymbol vom Scanner zu holen, und zwar in
einer Repräsentierung, die auf die Erkennung zugeschnitten ist.
Diese Repräsentierung kann man für Literale beim parser mit getLitSet() erfahren. Das
Argument ist der String, mit dem der Literal in der Grammatik stand, und das Resultat sollte
als Resultat von tokenSet() abgeliefert werden. null würde anzeigen, daß es sich bei dem
String nicht um einen Literal gehandelt hat — das darf tokenSet() nicht abliefern.
Ein Token wie Number vertritt in einer Grammatik eine Kategorie von Eingabesymbolen. Der
Scanner für arithmetische Ausdrücke sollte als tokenSet() immer dann die Repräsentierung
von Number abliefern, wenn er in der Eingabe eine Ziffernfolge findet.
Die Repräsentierung für einen Token wie Number kann man vom parser mit
((Token)parser.getPeer("Number")).getLookahead() erfahren, wobei man bei
getPeer() den in der Grammatik für den Token verwendeten Namen als String angibt. Es
wäre nicht klug, hier einen unbekannten Namen zu verwenden.
Der Scanner kann über tokenSet() Literale und Tokens abliefern. Speziell bei Tokens
interessiert in der Regel, um welche konkrete Eingabe es sich handelt. Parallel zum Aufruf von
tokenSet() sollte der Scanner beim Aufruf von value() daher ein Objekt abliefern, das das
Eingabesymbol näher charakterisiert. Bei Number bietet sich an, aus den Eingabezeichen
dafür einen Double- oder Long-Wert zu erzeugen.
[Diese Schnittstelle ist aus der Sicht von parser effizient, aus der Sicht des Scanners etwas
mühsam. Wir arbeiten an einer Revision, die die Implementierung vom Scanner vereinfacht.]
38
4.3 Sprachverarbeitung
Konstruiert man aus einer Grammatik etwas wie expr.ser, mit dem man in Verbindung mit
einem Scanner untersuchen kann, ob eine Eingabe ein Satz für die Grammatik ist, dann
möchte man mindestens schrittweise erfahren, welche Symbole sich in der Eingabe befinden.
Dies läßt sich prinzipiell mit einem Observer-Muster einrichten:
lines
sum
"\n"
product
term
"-"
"/"
product "*"
product
"+"
term
term
term
"("
Number
Lit:
found *
"%"
"+"
"-"
term
term
Goal:
found term
sum
")"
Token: found Number
Token- und Lit-Knoten in den aktivierten Regeln senden shift-Nachrichten an einen
Observer, wenn sie ein Eingabesymbol akzeptiert haben:
public interface Goal {
void shift (Lit sender, Object value);
void shift (Token sender, Object value);
Als value wird dabei der value() vom Scanner weitergeleitet; der Observer kann also zum
Beispiel sehen, welche Zahl für eine Number in der Eingabe stand.
Ist eine Regel komplett erkannt, erhält der Observer eine reduce-Nachricht:
Object reduce ();
Er kann mit null oder einem Objekt antworten, das dann dem Observer mit einer shiftNachricht zugestellt wird, der diese Regel aktiviert hat:
void shift (Goal sender, Object value);
}
Dieses Observer-Muster ist zweimal in ein Factory-Muster verschachtelt:
public interface GoalMaker { Goal goal (); }
public interface GoalMakerFactory { GoalMaker goalMaker (String ruleName); }
Beim Aufruf von oops.Compile gibt man nach -f eine GoalMakerFactory-Klasse an. Davon
wird ein einziges Objekt erzeugt, das für jedes Nonterminal einen GoalMaker liefern muß, der
dann seinerseits jedesmal ein Goal liefert, wenn eine Regel im rekursiven Abstieg aktiviert
wird.
Zur Vereinfachung gibt es eine DefaultGoalMakerFactory mit einem GoalAdapter, der sich
den ersten angelieferten value merkt und ihn bei reduce() wieder abliefert sowie eine
DebuggerGoalMakerFactory zur Ablaufverfolgung, die durch -d als Argument von
oops.Compile eingesetzt wird.
39
Die folgenden Beispiele zeigen, daß man mit dieser Architektur drei prinzipiell verschiedene
Observer-Muster realisieren kann: einen Handler, bei dem es nur ein einziges Goal-Objekt
gibt; einen Listener mit einem Goal-Objekt pro Regel, egal ob die Regel rekursiv verwendet
wird oder nicht; und schließlich einen Goalie mit je einem Goal pro Regel-Aktivierung. Die
Muster sind verschieden nützlich...
Trace
Trace ist ein Observer zur Ablaufverfolgung, der seine Beobachtungen zusätzlich in einem
StringBuffer aufzeichnet, den er selbst allerdings nicht als Resultat liefert:
oops.Trace.java
/** base class for tracing Goals.
*/
public class Trace implements Goal {
/** preserves information.
*/
protected StringBuffer result = new StringBuffer();
/** recognized a literal.
*/
public void shift (Lit sender, Object value) {
if (sender.toString().equals("\"\n\""))
System.err.println(this+"\tshift\t\"\\n\"\t"+value);
else
System.err.println(this+"\tshift\t"+sender+"\t"+value);
result.append(' ').append(value);
}
/** recognized a literal from a category.
*/
public void shift (Token sender, Object value) {
System.err.println(this+"\tshift\t"+sender+"\t"+value);
result.append(' ').append(value);
}
/** satisfied a nonterminal.
*/
public void shift (Goal sender, Object value) {
System.err.println(this+"\tshift\t"+sender+"\t"+value);
result.append(" [").append(value).append(']');
}
public Object reduce () {
System.err.println(this+"\treduce\t"+result);
return "reduced";
}
}
Beim Literal "\n" muß man ein bißchen tricksen, damit die Ablaufverfolgung vernünftig
aussieht.
40
Handler
Handler ist eine GoalMakerFactory, die nur einen einziges Trace-Objekt erzeugt:
oops/Handler.java
/** provides a single, global handler for all events.
*/
public class Handler implements GoalMakerFactory {
/** called once per rule during parser initialization.
@return singleton.
*/
public GoalMaker goalMaker (String ruleName) {
return handlerMaker;
}
/** singleton, provides singleton Goal.
*/
protected static GoalMaker handlerMaker = new GoalMaker () {
public Goal goal () { return handler; }
};
/** singleton, traces all events.
*/
protected static Goal handler = new Trace () {
public void shift (Goal sender, Object value) {
if (sender != this) System.err.println("not a singleton: "+this+" "+sender);
super.shift(sender, value);
}
public String toString () { return "handler"; }
};
}
shift() verifiziert, daß wirklich nur ein einziger Observer im Spiel ist. Die Ablaufverfolgung
der Bearbeitung von 3+4*5 läßt ahnen, daß man daraus einen Formelbaum konstruieren
könnte:
$ java -classpath .:oops.jar oops.Compile -f Handler
GoalMakerFactory is Handler
3+4*5
handler shift
Number 3
handler reduce
3
handler shift
handler reduced
handler reduce
3 [reduced]
handler shift
handler reduced
handler shift
"+"
+
handler shift
Number 4
handler reduce
3 [reduced] [reduced] + 4
handler shift
handler reduced
handler shift
"*"
*
handler shift
Number 5
handler reduce
3 [reduced] [reduced] + 4 [reduced]
handler shift
handler reduced
handler reduce
3 [reduced] [reduced] + 4 [reduced]
handler shift
handler reduced
handler reduce
3 [reduced] [reduced] + 4 [reduced]
handler shift
handler reduced
handler shift
"\n"
null
handler shift
"\n"
null
handler reduce
3 [reduced] [reduced] + 4 [reduced]
[reduced] null null
expr.ser Scanner > /dev/null
* 5
* 5 [reduced]
* 5 [reduced] [reduced]
* 5 [reduced] [reduced]
Das zusätzliche \n stammt vom Scanner (der sonst ein Zeichen vorauslesen müßte).
41
Listener
Listener ist eine GoalMakerFactory, die je ein einziges Trace-Objekt pro Regel erzeugt:
oops/Listener.java
/** provides a single listener for each rule.
*/
public class Listener implements GoalMakerFactory {
/** called once per rule during parser initialization.
@return GoalMaker providing singleton.
*/
public GoalMaker goalMaker (final String ruleName) {
return new GoalMaker () {
int goals; // count instances for trace
public Goal goal () { return handler; }
/** singleton, traces events for all instantiations of this rule.
*/
Goal handler = new Trace () {
int goal = ++ goals;
{ System.err.println(this+"\tnew"); }
public Object reduce () {
super.reduce();
String result = ruleName+this.result;
this.result = new StringBuffer(); // this botches recursive calls
return result;
}
public String toString () { return ruleName+goal; }
};
};
}
}
Die Trace-Objekte zeigen den Namen ihres Grammatikbegriffs und den (konstanten) Index 1.
reduce() liefert jeweils den aktuellen StringBuffer und legt einen neuen an.
Die Ablaufverfolgung von 3+4*5 scheint anzudeuten, daß man mit dieser Art von Observer
ganz trivial zu einem Formelbaum kommt:
$ java -classpath .:oops.jar oops.Compile -f Listener expr.ser Scanner > /dev/null
GoalMakerFactory is Listener
lines1 new
sum1
new
product1
new
term1
new
3+4*5
...
lines1 shift
sum1
sum [product [term 3]] + [product [term 4] * [term 5]]
Leider zeigt die Ablaufverfolgung von 3*(4+5), bei der sum rekursiv aktiviert wird, daß der
Listener damit nicht zurechtkommt:
3*(4+5)
...
lines1 shift
sum1
sum [product [term [sum [product [term 3] * [term ( 4]] +
[product [term 5]]] )]]
42
Bewertung mit einem Stack
Stack wird um einige Methoden zur Integer-Arithmetik erweitert:
oops/Cpu.java
public class Cpu extends Stack {
public void add () {
Number right = (Number)pop();
push(new Integer(((Number)pop()).intValue() + right.intValue()));
}
// ...
public void minus () {
push(new Integer(- ((Number)pop()).intValue()));
}
}
Die Grammatik ändert man etwas ab
oops/eval.ebnf
lines:
( expr | "\n" )*;
expr:
sum "\n";
sum:
add:
sub:
product ( add | sub )*;
"+" product;
"-" product;
product:
mul:
div:
mod:
term ( mul | div | mod )*;
"*" term;
"/" term;
"%" term;
term:
minus:
Number | "(" sum ")" | "+" term | minus;
"-" term;
Die neuen Regeln wie expr, add oder minus enden genau dann, wenn man zur Bewertung
aktiv sein muß.
43
So kann man mit einem Listener-Muster sehr elegant Formeln bewerten:
oops/Eval.java
/** listener/evaluator for arithmetic expressions.
*/
public class Eval implements GoalMakerFactory {
/** called once per rule during parser initialization.
@return GoalMaker providing singleton.
*/
public GoalMaker goalMaker (final String ruleName) {
return new GoalMaker () {
public Goal goal () { return handler; }
/** singleton from map or GoalAdapter.
*/
Goal handler = (Goal)map.get(ruleName);
{ if (handler == null) handler = new GoalAdapter(); }
};
}
/** handlers connecting events to stack-based Cpu.
Cpu's class is taken from property cpu, by default Cpu.
*/
protected final Hashtable map = new Hashtable();
protected final Cpu cpu;
{ try {
cpu = (Cpu)Class.forName(System.getProperty("cpu", "Cpu")).newInstance();
} catch (Exception e) {
System.err.println(e+": cannot create Cpu");
throw new ThreadDeath();
}
map.put("expr", new GoalAdapter() {
public Object reduce () {
System.err.println("\t"+cpu); cpu.removeAllElements(); return null;
}
});
map.put("add", new GoalAdapter() {
public Object reduce () { cpu.add(); return null; }
});
// ...
map.put("term", new GoalAdapter() {
public void shift (Token sender, Object value) { cpu.push(value); }
});
map.put("minus", new GoalAdapter() {
public Object reduce () { cpu.minus(); return null; }
});
}
}
Bei term muß man dafür sorgen, daß der value einer Number auf den Stack kommt. Davon
abgesehen kann man die trivialen Implementierungen von GoalAdapter akzeptieren und nur
in reduce() an den kritischen Stellen auf dem Stack rechnen. Bei expr liegt dann jeweils ein
Resultat auf dem Stack.
$ java -classpath .:oops.jar oops.Compile -f Eval eval.ser Scanner
GoalMakerFactory is Eval
3+4*5
[23]
3*(4+5)
[27]
+ (1 + 22 - 33 * 4 / 5 % + 6)
[21]
44
Goalie
Goalie ist eine GoalMakerFactory, die ein regelspezifisches Trace-Objekt bei jeder
Aktivierung erzeugt:
oops/Goalie.java
/** provides a listener for each activation of each rule.
*/
public class Goalie implements GoalMakerFactory {
/** called once per rule during parser initialization.
@return GoalMaker providing new Goal for each call.
*/
public GoalMaker goalMaker (final String ruleName) {
return new GoalMaker () {
int goals; // count instances for trace
public Goal goal () {
return new Trace () {
int goal = ++ goals;
{ System.err.println(this+"\tnew"); }
public Object reduce () {
super.reduce();
return ruleName+this.result;
}
public String toString () { return ruleName+goal; }
};
}
};
}
}
Jetzt entstehen lauter neue StringBuffer, und die kann man schlicht als Resultate von reduce()
liefern. Die Ablaufverfolgung zeigt, daß man trivialerweise einen Formelbaum erhält:
$ java -classpath .:oops.jar oops.Compile -f Goalie expr.ser Scanner
GoalMakerFactory is Goalie
3*(4+5)
...
lines1 shift
sum1
sum [product [term 3] * [term ( [sum [product [term 4]] +
[product [term 5]]] )]]
45
XML für arithmetische Ausdrücke
Das Element Construction Set enthält unter anderem Klassen, mit denen man einen Baum
aufbauen und in XML darstellen kann.
Xml ist ein Goalie-artiger Observer für expr.ebnf, der diese Klassen dazu benützt,
arithmetische Ausdrücke in XML darzustellen:
oops/Xml.java
/** goalie/XML tree builder for arithmetic expressions.
*/
public class Xml implements GoalMakerFactory {
/** called once per rule during parser initialization.
@return GoalMaker providing new Goal for each call.
*/
public GoalMaker goalMaker (final String ruleName) {
return new GoalMaker () {
public Goal goal () {
try {
return (Goal)((Adapter)map.get(ruleName)).clone();
} catch (Exception e) { e.printStackTrace(); System.exit(1); }
return null; // not reached
}
};
}
/** handlers connecting events to XML building classes.
*/
protected final Hashtable map = new Hashtable();
protected static class Adapter extends GoalAdapter implements Cloneable {
public Object clone () throws CloneNotSupportedException {
return super.clone();
}
}
{ map.put("lines", new Adapter() { // ( sum? "\n" )*
public void shift (Goal sender, Object value) {
if (result == null) result = new XML("expr").addElement((Element)value);
else ((XML)result).addElement((Element)value);
}
public Object reduce () {
if (result != null)
new XMLDocument().addElement((XML)result).output(System.out);
return null;
}
});
expr heißt das Wurzelelement des Dokuments. Es enthält nacheinander die Elemente, die
jeweils von sum produziert werden.
Bei reduce() wird ein XMLDocument erzeugt und als Text ausgegeben, falls irgendwelche
Ausdrücke erkannt wurden.
result definiert der GoalAdapter, der dort den ersten, jeweils mit irgendeinem shift()
angelieferten value hinterlegt und diesen Wert bei reduce() wieder abliefert. lines liefert
jedoch null, damit oops.Compile kein serialisiertes Objekt ausgibt.
map enthält die Goal-Objekte für die Regelaktivierungen. clone() ist nötig, da die Regeln ja
mehrfach und rekursiv aktiviert werden können.
46
oops/Xml.java
map.put("sum", new Adapter() { // product ( "+" product | "-" product )*
public void shift (Goal sender, Object value) {
if (result == null) result = value;
else ((XML)result).addElement((Element)value);
}
public void shift (Lit sender, Object value) {
if (value.equals("+")) result = new XML("add").addElement((Element)result);
else result = new XML("sub").addElement((Element)result);
}
});
Das erste shift(Goal) merkt sich das Resultat vom ersten product. Ein shift(Lit) für +
oder - verschachtelt dieses Resultat in einem XML-Element mit Namen add oder sub. Jedes
weitere shift(Goal) fügt ein weiteres product-Resultat zum aktuellen XML-Element hinzu.
product funktioniert analog und erzeugt Elemente mit Namen div, mod oder mul.
oops/Xml.java
map.put("term", new Adapter() { // Number | "(" sum ")" | "+" term | "-" term
public void shift (Token sender, Object value) {
result = new XML("literal", false)
.addXMLAttribute("type", "Integer")
.addXMLAttribute("value", value.toString());
}
public void shift (Lit sender, Object value) {
if (value.equals("-")) result = new XML("minus");
}
public void shift (Goal sender, Object value) {
if (result == null) result = value;
else ((XML)result).addElement((Element)value);
}
});
}
}
term ist etwas trickreicher. Für Number erzeugt man ein XML-Element literal mit Attributen,
die den Wert beschreiben. shift(Lit) sorgt dafür, daß nur für - ein Element mit Namen
minus angelegt wird. shift(Goal) fügt dann entweder ein term-Resultat in dieses Element
ein, oder es merkt sich das sum- oder term-Resultat. reduce() liefert in jedem Fall result
ab.
Das Observer-Muster führt hier zu einer sehr einfachen und zugleich sicheren Codierung. Die
Differenzierung nach Lit, Token oder Goal als Absender vermeidet explizite, subtile
Fallunterscheidungen.
47
$ java -classpath .:oops.jar:ecs-1.4.1.jar oops.Compile -f Xml expr.ser Scanner |
tidy -xml | grep -v '^$'
GoalMakerFactory is Xml
3 + 4 * 5
3 * ( 4 + 5 )
+ (1 + 22 - 33 * 4 / 5 % + 6)
<?xml version="1.0" standalone="yes"?>
<expr>
<add>
<literal type="Integer" value="3" />
<mul>
<literal type="Integer" value="4" />
<literal type="Integer" value="5" />
</mul>
</add>
<mul>
<literal type="Integer" value="3" />
<add>
<literal type="Integer" value="4" />
<literal type="Integer" value="5" />
</add>
</mul>
<sub>
<add>
<literal type="Integer" value="1" />
<literal type="Integer" value="22" />
</add>
<mod>
<div>
<mul>
<literal type="Integer" value="33" />
<literal type="Integer" value="4" />
</mul>
<literal type="Integer" value="5" />
</div>
<literal type="Integer" value="6" />
</mod>
</sub>
</expr>
Man muß die Ausgabe noch durch tidy filtern und einige Leerzeilen entfernen, damit man ein
lesbares XML-basiertes Dokument erhält.
48
4.4 Bootstrap
Boot hat aus einer in EBNF formulierten Grammatik einen Objekt-Baum serialisiert, der
zusammen mit einem Scanner Sätze für diese Grammatik erkennen und verarbeiten konnte.
Xml hat demonstriert, daß man Bäume sehr leicht mit einer geeigneten Klassenbibliothek
erzeugen kann.
Der Gedanke liegt nahe, EBNF in EBNF zu beschreiben, mit Boot zu serialisieren, mit dem
Resultat in oops.Compile die Beschreibung selbst zu bearbeiten und einen Baum über dem
gleichen Paket oops.parser zu erzeugen, das Boot selbst verwendet:
oops/ebnf.ebnf
// grammar for modified EBNF
parser:
rule:
alt:
seq:
term:
item:
rule+;
Id ":" alt ";";
seq ( "|" seq )*;
( term )*;
item ( "?" | "+" | "*" )?;
Id | Lit | "(" alt ")";
$ java -classpath .:oops.jar Boot ebnf.ebnf > ebnf.ser
Die Factory Ebnf funktioniert analog zu Xml, verwendet aber oops.parser-Klassen:
oops/Ebnf.java
map.put("alt", new Adapter() { // seq ( "|" seq )*
public void shift (Goal sender, Object value) {
Node seq = ((Node)value).node();
if (result == null) result = new Alt(seq);
else ((Alt)result).add(seq);
}
});
map.put("seq", new Adapter() { // ( term )*
public void shift (Goal sender, Object value) {
Node term = ((Node)value).node();
if (result == null) result = new Seq(term);
else ((Seq)result).add(term);
}
});
map.put("term", new Adapter() { // item ( "?" | "+" | "*" )?
public void shift (Lit sender, Object value) {
Node item = ((Node)result).node();
if (value.equals("?"))
result = new Opt(item);
else if (value.equals("+")) result = new Some(item);
else if (value.equals("*")) result = new Many(item);
else throw new RuntimeException("term "+value);
}
});
map.put("item", new Adapter() { // Id | Lit | "(" alt ")"
public void shift (Lit sender, Object value) { // ignore ( )
}
});
Wenn man bei shift(Lit) nichts in result einträgt, liefert der GoalAdapter bei reduce()
die von Id und Lit bei shift(Token) und von alt bei shift(Goal) angelieferten Werte.
49
oops/Ebnf.java
{ map.put("parser", new Adapter() { // rule+
public void shift (Goal sender, Object value) {
Rule rule = (Rule)value;
if (result == null) result = new Parser(rule);
else ((Parser)result).add(rule);
}
public Object reduce () {
Parser parser = (Parser)result;
parser.setSets(scanner.lits(), scanner.ids()); // checks tree
return parser;
}
});
map.put("rule", new Adapter() { // Id ":" alt ";"
public void shift (Goal sender, Object value) {
Node alt = ((Node)value).node();
result = new Rule((Id)result, alt);
}
});
Regeln werden durch Rule-Objekte beschrieben und in einem Parser-Objekt gesammelt.
Diesem Objekt muß man zum Schluß mit setSets() je eine Enumeration der Literal-Strings
und Namen liefern, die in der Grammatik verwendet wurden.
Diese Information sammelt der Tokenizer, den man sich mit Boot teilt und in Ebnf als innere
Klasse ein bißchen anpaßt.
setSets() prüft zugleich die LL(1)-Bedingung.
oops.Compile schreibt das letzte reduce()-Resultat zur Standard-Ausgabe, also den
serialisierten Baum für die in EBNF formulierte Grammatik für EBNF. Man kann mit cmp
kontrollieren, daß das die gleichen Objekte sind, die Boot erzeugt:
$ java -classpath .:oops.jar oops.Compile -f Ebnf ebnf.ser Ebnf.Scanner ebnf.ebnf |
cmp - ebnf.ser
GoalMakerFactory is Ebnf
50
4.5 Fazit
Man kann eine kontext-freie Grammatik in EBNF beschreiben, als Baum repräsentieren und
zur Erkennung der Sätze verwenden, die die Grammatik produziert. Man muß allerdings im
Zuge der Repräsentierung maschinell prüfen, daß sich die Grammatik für das Vorgehen
eignet, also die LL(1)-Bedingung erfüllt.
Für einen Erkenner benötigt man lediglich die Grammatik und einen Scanner, den man in
vielen Fällen schon mit einem StreamTokenizer realisieren kann.
Das Goal-Interface dient dazu, im Rahmen von verschiedenen Observer-Mustern einen Satz
schrittweise zu verarbeiten. Man kann zum Beispiel eine Formel bewerten oder für einen Satz
einen Baum generieren und ausgeben.
Zur Verarbeitung muß man eine GoalMakerFactory implementieren. Die Grammatik und der
Erkennungsprozeß führen jedoch dazu, daß man nur sehr kleine Methoden in jeweils einem
eng begrenzten, genau absehbaren Kontext realisieren muß. Einen XML-Baum könnte man
durchaus automatisch generieren.
Das Verfahren ist immerhin mächtig genug, daß man das Programm zur Übersetzung einer
Grammatik mit dem Verfahren selbst implementieren kann, folglich kann man die Syntax für
EBNF — also zur Formulierung von Grammatiken — sehr leicht ändern.
Hier wurde deshalb eine an DTDs angepaßte Syntax an Stelle der ursprünglichen oopsSyntax verwendet. Es gibt auch noch andere Klassen in oops.parser, die wesentlich
erweiterte Formen von EBNF unterstützen.
oops trennt Grammatik und Aktionen vollständig. Es sollte sehr leicht möglich sein, oops in
anderen Objekt-orientierten Sprachen wie C# oder Objective C zu realisieren und zum
Beispiel die Grammatikprüfung allein in Java vorzunehmen.
51
5
Extensible Markup Language
Dieses Kapitel beschreibt im Detail, wie man XML-basierte Dokumente aufschreiben muß, so
daß sie well-formed und gegebenenfalls valid sind. Letzteres ist der Fall, wenn eine Document
Type Definition (DTD) erfüllt ist; deshalb wird hier auch erklärt, wie man eine DTD formuliert.
Sieht man die Erkennung von Elementen, Attributen, Entitites und Tags als Aufgabe der
lexikalischen Analyse an, dann bezieht sich well-formed — und manchmal parsed — darauf,
daß das gelingt, und valid bedeutet, daß die in der DTD beschriebene Syntax für die Elemente
und rudimentäre Semantik für die Attribute erfüllt ist.
Eine gute Quelle für diese Information ist das XML Handbook von Goldfarb und Prescod sowie
die XML Recommendation des W3C. Englische Begriffe lassen sich hier kaum vermeiden. Die
Syntax-Regeln stammen aus der Recommendation.
Leider wirft die Recommendation praktisch ungegliedert vom Präprozessor (Entities) bis zur
Semantik (DTD) alles in eine Pseudo-Grammatik zusammen — vermutlich ein Erbteil von
SGML. Das führt zum Beispiel dazu, daß man für manchen Entity-Ersatz verlangen muß, daß
er dann nochmals bestimmten Regeln der Grammatik genügt.
5.1 Ziele
The design goals for XML are:
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
XML shall be straightforwardly usable over the Internet.
XML shall support a wide variety of applications.
XML shall be compatible with SGML.
It shall be easy to write programs which process XML documents.
The number of optional features in XML is to be kept to the absolute minimum, ideally zero.
XML documents should be human-legible and reasonably clear.
The XML design should be prepared quickly.
The design of XML shall be formal and concise.
XML documents shall be easy to create.
Terseness in XML markup is of minimal importance.
[Aus der Recommendation].
Dem ist kaum etwas hinzuzufügen — allerdings steht die maschinelle Bearbeitung im
Vordergrund und die Erzeugung von Dokumenten bleibt fast unberücksichtigt, sie rangiert
nach der Überlegung, daß XML eher schnell als formal definiert werden soll.
Aus der Beschreibung geht auch nicht hervor, was man mit XML eigentlich machen soll:
strukturierte Daten portabel beschreiben.
52
5.2 Repräsentierung
Die Recommendation bezieht sich auf Unicode (exakt ISO/IEC 10646):
[2]
Char
::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
| [#x10000-#x10FFFF]
Ein Dokument kann mit einem anderen Zeichensatz repräsentiert werden, der aus dem
Prolog des Dokuments ersichtlich ist. Groß- und Kleinschreibung wird unterschieden.
Markup
Ein Dokument (document entity)
[1]
document
::= prolog element Misc*
besteht aus Markup, das heißt, start tags, end tags, empty-element tags, entity references,
character references, comments, CDATA section delimeters, document type declarations und
processsing instructions, sowie aus character data.
References realisieren einen Präprozessor und dienen zur Repräsentierung von
Sonderzeichen; sie beginnen mit &, der Rest des Markups beginnt mit < und kann < nicht
enthalten.
Zwischenraum
Zwischenraum besteht aus Leerzeichen, Tabs, RETURN und LINEFEED:
[3]
S
::= (#x20 | #x9 | #xD | #xA)+
Innerhalb von Markup wird Zwischenraum verarbeitet, außerhalb ist er signifikant. Theoretisch
muß ein Parser allen content abliefern. Nur wenn ein Parser validiert, muß er auch anzeigen,
ob Zwischenraum Bedeutung hat oder nicht — für Bedeutung soll das Attribut xml:space
definiert werden. Es bleibt unklar, ob ein leeres Element noch Zwischenraum enthält.
Zeilentrenner sollen bei der Erkennung auf LINEFEED normalisiert werden.
Namen
Für Name (Bezeichner) gilt:
[4]
NameChar
[5]
[6]
Name
Names
::= Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar |
Extender
::= (Letter | '_' | ':') (NameChar)*
::= Name (S Name)*
Letter, Digit, CombiningChar und Extender kommen aus bestimmten Bereichen im
Unicode. Man kann aber mindestens von den ASCII-Ideen ausgehen. Die Zeichenfolge XML ist
am Anfang von Namen in jeder Schreibweise reserviert. Der Doppelpunkt wird im Rahmen
von Namespaces verwendet, die jedoch nicht als Teil von XML definiert werden.
53
Strings
Markup selbst enthält nur Zwischenraum und NameChar sowie literal data, nämlich beliebige
Zeichenketten in einfachen oder Doppelanführungszeichen:
[10]
AttValue
::= '"' ([^<&"] | Reference)* '"'
| "'" ([^<&'] | Reference)* "'"
::= EntityRef | CharRef
::= '&' Name ';'
::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';'
[67] Reference
[68] EntityRef
[66] CharRef
Die Zeichenketten dürfen < & und das jeweilige Anführungszeichen nicht enthalten, können
aber references verwenden:
"
&
'
<
>
Doppelanführungszeichen
"
&
'
<
>
&
einfaches Anführungszeichen
<
>
Kommentare
Kommentare dürfen beliebige Zeichen enthalten, aber nur exakt mit --> enden:
[15]
Comment
::= '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->'
Kommentare können nicht verschachtelt werden.
Processing Instructions
PIs wenden sich an die Verarbeiter von XML und dürfen bis auf ?> alles enthalten:
[16]
[17]
PI
PITarget
::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>'
::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l'))
XML darf wieder in jeder Schreibweise nicht als Target verwendet werden — wird aber doch,
bei xml-stylesheet.
CDATA Sections
CDATA dient dazu, absolut beliebigen Text doch noch well-formed unterzubringen:
[18]
[19]
[20]
[21]
CDSect
CDStart
CData
CDEnd
::=
::=
::=
::=
CDStart CData CDEnd
'<![CDATA['
(Char* - (Char* ']]>' Char*))
']]>'
Char kann keine Reference enthalten, das heißt, man kann die abschließende Folge ]]> in
CDATA selbst nicht unterbringen. Folglich kann man CDATA nicht schachteln.
54
5.3 Logische Struktur
Prolog
Im Prolog der document entity steht, ob sie external entitites verwendet. Außerdem wird dort
mit der DTD verknüpft:
prolog
XMLDecl
VersionInfo
Eq
VersionNum
Misc
[80] EncodingDecl
[81] EncName
[32] SDDecl
[22]
[23]
[24]
[25]
[26]
[27]
::=
::=
::=
::=
::=
::=
::=
::=
::=
XMLDecl? Misc* (doctypedecl Misc*)?
'<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'
S 'version' Eq ("'" VersionNum "'" | '"' VersionNum '"')
S? '=' S?
([a-zA-Z0-9_.:] | '-')+
Comment | PI | S
S 'encoding' Eq ('"' EncName '"' | "'" EncName "'" )
[A-Za-z] ([A-Za-z0-9._] | '-')*
S 'standalone' Eq
(("'" ('yes' | 'no') "'") | ('"' ('yes' | 'no') '"'))
VersionNum soll derzeit 1.0 sein. Mögliche EncName sind vordefiniert; die Voreinstellung ist
UTF-8.
Die Angabe standalone=’yes’ soll versprechen, daß das Dokument keine externen
Markup-Deklarationen benutzt, die das Dokument tatsächlich betreffen — das spart einem
Prozessor unnötiges Laden.
Die Document Type Declaration steht im Prolog und enthält oder verweist auf Markup
Declarations, die die Document Type Definition in Form einer Grammatik für ein Dokument
liefern.
Elemente
Ein Dokument enthält ein Element, in das weitere Elemente verschachtelt sein können.
[39]
[40]
[41]
[42]
[43]
element
STag
Attribute
ETag
content
[44]
[14]
EmptyElemTag
CharData
::=
::=
::=
::=
::=
EmptyElemTag | STag content ETag
'<' Name (S Attribute)* S? '>'
Name Eq AttValue
'</' Name S? '>'
CharData? ((element | Reference | CDSect | PI
| Comment) CharData?)*
::= '<' Name (S Attribute)* S? '/>'
::= [^<&]* - ([^<&]* ']]>' [^<&]*)
Ein Name (generic identifier) muß unmittelbar am Anfang eines Tags stehen, er verbindet STag
und ETag und er legt den element type in der DTD fest.
Jeder Attributname darf nur einmal angegeben werden.
55
5.4 Physikalische Struktur
Ein Dokument wird in einer oder mehreren Dateien gespeichert — den sogenannten entities.
Die document entity und der externe Teil der DTD (falls es den gibt) haben keine Namen, alle
anderen Entities werden per Namen aufgerufen.
Parsed entities haben einen Ersatztext, der aus ihrem Inhalt besteht und im Zug der
Verarbeitung abgeliefert wird.
Es gibt general entities, die im content aufgerufen werden, und parameter entities, die nur in
der DTD aufgerufen werden dürfen.
[69] PEReference
::= '%' Name ';'
Unparsed entities haben nur einen Namen und dazu eine Notation. Sie werden nur in
bestimmten Attributen aufgerufen.
Parsed Entities
External parsed entities können mit einer TextDecl beginnen, die selbst keine parsed entities
benutzen darf:
[78] extParsedEnt
[77] TextDecl
::= TextDecl? content
::= '<?xml' VersionInfo? EncodingDecl S? '?>'
Interessanterweise darf diesmal die Version fehlen, aber das Encoding nicht!
Ersatz
Es gibt leider komplizierte Kontextregeln, was wie verwendet und ersetzt wird:
Verweis in content
Parameter
Intern
Extern
%name;
&name;
&name;
name
{
validating?
Fehler
ersetzt
Fehler
Fehler
nicht erkannt
Fehler
Fehler
Fehler
validating?
Fehler
ersetzt
nicht erkannt
ersetzt
Fehler, je
nach Kontext
nicht erkannt ersetzt,
bearbeitet
Verweis in Attributwert nicht erkannt ersetzt
Name als Attributwert nicht erkannt Fehler
Verweis in entity value ersetzt
nicht erkannt
Verweis in DTD
ersetzt, plus Fehler
Leerzeichen
Unparsed
Character
Nicht erkannt bedeutet, daß die Entity nicht als solche erkannt und ersetzt wird; sie bleibt also
unverändert stehen.
Nur ein validierendes System ist verpflichtet, externe Entities zu beschaffen; ein anderes
System darf es, muß aber nicht!
56
5.5 Document Type Definition
Wenn sie vorhanden ist, steht die Document Type Definition (DTD) gegen Ende des Prologs in
der document entity.
[28]
doctypedecl
[75] ExternalID
[11]
[12]
[13]
SystemLiteral
PubidLiteral
PubidChar
::= '<!DOCTYPE' S Name (S ExternalID)?
S? ('[' (markupdecl | DeclSep)* ']' S?)? '>'
::= 'SYSTEM' S SystemLiteral
| 'PUBLIC' S PubidLiteral S SystemLiteral
::= ('"' [^"]* '"') |("'" [^']* "'")
::= '"' PubidChar* '"' | "'" (PubidChar - "'")* "'"
::= #x20 | #xD | #xA |[a-zA-Z0-9] |[-'()+,./:=?;!*#@$_%]
Syntaktisch ist Markup merkwürdig eingebunden: In der document entity dürfen parameter
entity references nur wie Zwischenraum zwischen Markup vorkommen:
[28a] DeclSep
[29] markupdecl
::= PEReference | S
::= elementdecl | AttlistDecl | EntityDecl | NotationDecl
| PI | Comment
Ein SystemLiteral ist eine URI, die auch relativ sein kann — mit je nach Verarbeitung
merkwürdigen Resultaten. Hier gelten Zeichenersatzregeln auf der Basis von %xx mit
Hexziffern für UTF-8 Bytes. Wird ein SystemLiteral im DOCTYPE verwendet, muß er sich auf
ein extSubset beziehen.
Wird eine PEReference, also %name;, als DeclSep verwendet, muß ihr Ersatztext ebenfalls
ein extSubset sein:
[30]
[31]
extSubset
extSubsetDecl
::= TextDecl? extSubsetDecl
::= ( markupdecl | conditionalSect | DeclSep)*
Obgleich die Reihenfolge umgekehrt sein kann, steht die interne DTD logisch vor einer
externen. Nur in der externen DTD darf man conditionalSect verwenden und PEReference
dürfen dann auch innerhalb von markupdecl verwendet werden.
Präprozessor
Es gibt einen sehr rudimentären Mechanismus, um Teile eines Dokuments auszublenden:
[61]
[62]
[63]
[64]
[65]
conditionalSect
includeSect
ignoreSect
ignoreSectContents
Ignore
::=
::=
::=
::=
::=
includeSect | ignoreSect
'<![' S? 'INCLUDE' S? '[' extSubsetDecl ']]>'
'<![' S? 'IGNORE' S? '[' ignoreSectContents* ']]>'
Ignore ('<![' ignoreSectContents ']]>' Ignore)*
Char* - (Char* ('<![' | ']]>') Char*)
IGNORE kann einen INCLUDE-Bereich enthalten und umgekehrt. Steht ein derartiger Abschnitt
im content einer parameter entity, muß er komplett darinstehen.
IGNORE und INCLUDE werden typischerweise über parameter entities gesteuert:
<!ENTITY % draft 'INCLUDE' >
<!ENTITY % final 'IGNORE' >
<![%draft;[
<!ELEMENT book (comments*, title, body, supplements?)>
]]>
<![%final;[
<!ELEMENT book (title, body, supplements?)>
]]>
57
Elemente
Eine elementdecl definiert zu einem Elementnamen (generic identifier) seinen element type,
das heißt, sie legt den möglichen content des Elements fest. Ein Name darf nur einmal
deklariert werden.
[45]
[46]
[47]
[48]
[49]
[50]
[51]
elementdecl
contentspec
children
cp
choice
seq
Mixed
::=
::=
::=
::=
::=
::=
::=
'<!ELEMENT' S Name S contentspec S? '>'
'EMPTY' | 'ANY' | Mixed | children
(choice | seq) ('?' | '*' | '+')?
(Name | choice | seq) ('?' | '*' | '+')?
'(' S? cp ( S? '|' S? cp )+ S? ')'
'(' S? cp ( S? ',' S? cp )* S? ')'
'(' S? '#PCDATA' (S? '|' S? Name)* S? ')*'
| '(' S? '#PCDATA' S? ')'
Bei EMPTY muß das Element leer sein (es kann, muß aber nicht, mit EmptyElemTag
repräsentiert werden), bei ANY kann es beliebige Elemente (aber vermutlich keinen Text)
enthalten.
Mixed erlaubt Text (parsed character data), gemischt mit beliebigen Elementen. Hier hat man
leider keinen Einfluß auf die Anzahl oder Reihenfolge der Elemente.
<!ELEMENT text ( #PCDATA ) >
<!ELEMENT mixed ( #PCDATA | a | b | c )* >
Zur Beschreibung von verschachtelten Elementen wird eine Variante von EBNF verwendet, bei
der Folgen durch Kommata ausgedrückt werden. Alternativen wie Folgen müssen in
Klammern stehen.
<!ELEMENT nest (( a, b ) | ( c, d ))+ >
Das kann mehrdeutig sein:
<!ELEMENT bits (( n, e )*, (n, e )*) >
58
Attribute
Eine AttlistDecl legt die möglichen Attributnamen fest, impliziert aber keine Reihenfolge.
Außerdem kann man die Werte einschränken und man muß Voreinstellungen angeben.
Diesmal gilt im Zweifelsfall die erste Deklaration:
AttlistDecl
AttDef
AttType
[55] StringType
[56] TokenizedType
[52]
[53]
[54]
[57] EnumeratedType
[58] NotationType
[59] Enumeration
::=
::=
::=
::=
::=
'<!ATTLIST' S Name AttDef* S? '>'
S Name S AttType S DefaultDecl
StringType | TokenizedType| EnumeratedType
'CDATA'
'ID' | 'IDREF' | 'IDREFS' | 'ENTITY' | 'ENTITIES'
| 'NMTOKEN' | 'NMTOKENS'
::= NotationType | Enumeration
::= 'NOTATION' S '(' S? Name (S? '|' S? Name)* S? ')'
::= '(' S? Nmtoken (S? '|' S? Nmtoken)* S? ')'
Ein Attributwert kann ein String, ein oder mehrere eindeutige oder definierte Namen oder ein
Name aus einer Liste sein.
Bei ID muß das Attribut einen im Dokument eindeutigen Wert haben. Ein Element darf
höchstens ein Attribut mit diesem Typ haben.
IDREF und IDREFS sind Typen für Attribute, die auf Attribute mit ID-Typ verweisen, also
Querverweise in einem Dokument.
ENTITY und ENTITIES sind Typen, mit denen man auf unparsed entities verweist, die als
notations vereinbart werden müssen.
NMTOKEN und NMTOKENS sind beliebige Namenslisten mit etwas allgemeinerer Syntax:
[7]
[8]
Nmtoken
Nmtokens
::= (NameChar)+
::= Nmtoken (S Nmtoken)*
Ein EnumeratedType definiert die gleichen Typen, legt aber exakte Wertelisten fest.
Voreinstellung
Für jedes Attribut muß man eine Voreinstellung angeben:
[60] DefaultDecl
::= '#REQUIRED' |'#IMPLIED' | (('#FIXED' S)? AttValue)
Bei REQUIRED muß das Attribut angegeben werden, bei IMPLIED darf es fehlen. Ein AttValue
ist eine Voreinstellung, die verwendet wird, wenn das Attribut nicht explizit angegeben wurde;
FIXED bedeutet, daß nur dieser Wert verwendet werden darf.
59
Entities
Mit ENTITY vereinbart man Namen für global entities und für parameter entities.
[70]
[71]
[72]
[73]
[74]
[9]
EntityDecl
GEDecl
PEDecl
EntityDef
PEDef
EntityValue
[76] NDataDecl
::=
::=
::=
::=
::=
::=
GEDecl | PEDecl
'<!ENTITY' S Name S EntityDef S? '>'
'<!ENTITY' S '%' S Name S PEDef S? '>'
EntityValue | (ExternalID NDataDecl?)
EntityValue | ExternalID
'"' ([^%&"] | PEReference | Reference)* '"'
| "'" ([^%&'] | PEReference | Reference)* "'"
::= S 'NDATA' S Name
Global entities werden mit &name; und nur außerhalb der DTD aufgerufen; dabei kann man
auch auf unparsed entities verweisen.
Parameter entities werden mit %name; und nur innerhalb der DTD aufgerufen. Sie können auf
external entities verweisen, die aber passen müssen.
Notations
Eine NotationDecl verknüpft einen Namen mit einer unparsed entity. Der Name kann dann
als Wert nach NDATA in einer Entity-Definition vorkommen, deren Name dann in ENTITY oder
ENTITIES Attributen benutzt werden darf.
[82] NotationDecl
[83] PublicID
::= '<!NOTATION' S Name S (ExternalID | PublicID) S? '>'
::= 'PUBLIC' S PubidLiteral
60
5.6 Namespaces
Die Beispiele zu XSLT im ersten Kapitel deuten an, daß es zu Konflikten kommen kann, wenn
man Elemente aus verschiedenen Dokumenten mischt: Ein XSLT-Programm enthält auch
Elemente, die es einfach nur ausgeben soll; es wird problematisch, wenn ein XSLT-Programm
ein XSLT-Programm erzeugen soll.
Namespaces wurden eingeführt, um Elemente und Attribute durch Verbindung mit URIs
eindeutig zu machen.
Das reservierte Attribut xmlns verbindet einen Präfix mit einer URI — damit kann auch eine
URI als default namespace eingeführt werden, die gilt, wenn später kein Präfix angegeben ist:
<ats:scope xmlns:ats="http://www.inf.uos.de/axel" xmlns="http://www.w3.org/">
<!-- hier ist der Präfix ats sichtbar -->
<html/> <!-- hat den default Namespace -->
</ats:scope>
Der Präfix ist in dem Element sichtbar, in dem er vereinbart wird, und in allen Elementen, die
darin verschachtelt sind. Der default namespace wird entsprechend angewendet.
Der default namespace ist leer, wenn man xmlns="" angibt. Elemente ohne Präfix sind dann
in keinem Namespace.
Der default namespace bezieht sich nicht auf Attribute — Attribute sind a priori in keinem
Namespace.
Namen bestehen aus Präfix und lokalem Namen — getrennt von genau einem Doppelpunkt.
Namen sind gleich, wenn ihre lokalen Namen gleich sind und wenn, falls vorhanden, die
Präfixe zeichengleiche URIs bezeichnen (die Präfixe selbst dürfen verschieden sein).
<zb xmlns:n1="http://www.w3.org/" xmlns:n2="http://www.w3.org/">
<ok a="A" b="B"/>
<no n1:a="A" n2:a="B"/>
<ok n1:a="A" a="B"/>
</zb>
DTDs wissen nichts über Namespaces. Für dieses Beispiel muß man alle vier Attribute
deklarieren:
<!ELEMENT zb ( ok | no )+>
<!ATTLIST zb
xmlns:n1 CDATA #FIXED "http://www.w3.org/"
xmlns:n2 CDATA #FIXED "http://www.w3.org/"
>
<!ENTITY % atts "
a
CDATA #IMPLIED
b
CDATA #IMPLIED
n1:a CDATA #IMPLIED
n2:a CDATA #IMPLIED
">
<!ELEMENT ok EMPTY>
<!ATTLIST ok %atts;>
<!ELEMENT no EMPTY>
<!ATTLIST no %atts;>
Das Beispiel ist dann valid — ist aber fehlerhaft, wenn man Namespaces berücksichtigt.
Es wird besonders brenzlig, wenn ein Namespace in einem externen, voreingestellten Attribut
eingeführt wird...
61
5.7 XML Schema
Im Mai 2001 wurde die XML Schema Recommendation des W3C verabschiedet, die die DTD
zur Beschreibung von XML-basierten Dokumenten ersetzen soll. Der Primer ist eine ziemlich
lesbare Einführung.
Von der Language Technology Group in Edinburgh gibt es einen Schema-basierten XMLValidierer xsv, der in Python implementiert ist und derzeit für Windows und als Web-Service
verfügbar ist:
c> xsv po.xml po.xsd
<?xml version='1.0'?>
<xsv docElt='{None}purchaseOrder' instanceAssessed='true' instanceErrors='0'
rootType='{None}:PurchaseOrderType' schemaDocs='po.xsd' schemaErrors='0'
schemaLocs='' target='file:/e:/Documents/Vorlesungen/xml01/code/xsd/po.xml'
validation='strict' version='XSV 1.180/1.88 of 2001/03/17 12:11:13'
xmlns='http://www.w3.org/2000/05/xsv'/>
Ein Schema wird in XML formuliert und muß sich im Namespace http://www.w3.org/2001/
XMLSchema befinden.
XML Schema enthält ein aufwendiges, erweiterbares System von Typen, die für Attribute und
Elementinhalte verwendet werden können. Manche DTD-Prozessoren verwenden die TrickAttribute a-dtype mit Paaren von Attribut- und Typ-Name sowie e-dtype mit einem Typ-
Namen, um Datentypen für Attribute und Elementinhalte zu vereinbaren. Diese Attribute
werden #FIXED vereinbart.
Außerdem kann man bei verschachtelten Elementen angeben, wie oft Elemente vorkommen
dürfen oder müssen. Ein Attribut mixed regelt, ob Elemente zusätzlich Text enthalten dürfen
oder nicht, das heißt, ein mixed content model kann jetzt wesentlich präziser spezifiziert
werden.
Leider ist ein XML Schema unglaublich verbal und kaum noch überschaubar zu formulieren.
Man kann zwar Typen erweitern — durch Einschränken von Wertebereichen, auch mit
Textmustern, und durch Aggregatbildung — aber man kann offenbar keine konstanten
Ausdrücke verwenden.
Man kann zwar mit dem Attribut xsi:schemaLocation Paare von Namespace und SchemaAdressen vereinbaren, aber das gilt nur als Hinweis.
XML Schema wird in XML formuliert, hat aber auch noch eigene include- und import-
Mechanismen.
Zusätzlich zu den XML-Kommentaren und PIs gibt es auch noch annotation- und appInfoElemente.
62
5.8 Beispiele
Homepage
Code
FTP-Bereich
Artikel
kleine Beispiele
XML Schema
index.xml
index.dtd
code/index.xml
code/index.dtd
ftp/index.xml
ftp/index.dtd
rec/index.xml
rec/index.dtd
code/xml/makefile
code/xsd/po.xml
code/xsd/po.xsd
63
6
Quellen
Man kann XML von Hand schreiben, mit speziellen Editoren bearbeiten, von Textsystemen
erzeugen lassen, oder zum Beispiel aus einer Datenbank-Anfrage generieren. Im Folgenden
werden einige Produkte in alphabetischer Reihenfolge vorgestellt, die relativ typisch
erscheinen.
6.1 Editoren
Amaio xeddy
Kommerzielles Produkt, Java-basiert, benutzt DTD, editiert Textanzeige und Property-Sheet;
verwendet Xerces und hat deshalb Probleme mit relativer URL für DTD.
AnnArbor epic / arbortext
Kommerzielles Produkt, im Vollausbau sehr teuer, editiert verschiedene Ansichten [siehe
Vorstellung durch Olaf Müller].
64
Edinburgh Language Technology Group xed
Frei verfügbar, getestet unter Windows, editiert Text mit Tags; bewahrt Prolog in read-only
Fenster auf, wird vermutlich sehr bald mit dem aktuellen Schema arbeiten können.
IBM alphaWorks Xeena
Frei verfügbar zum Test, Java-basiert, editiert Baum mit Attributen; interessante BaumOperationen, kann validieren und XSL anwenden.
65
Merlot
Open source, Java-basiert, editiert Baum mit Property-Sheets; dürfte sehr unübersichtlich
werden, hat offenbar Probleme mit UTF-8 und mit der Baumdarstellung unter MacOS X.
Microsoft XML Notepad
Frei verfügbar für Windows, kompakt, editiert Baum mit Attributen, die zeilenweise neben den
Baumknoten stehen; Zwischenraum scheint beim Speichern zu explodieren, Entwicklung ist
offenbar eingefroren.
66
Morphon
Kommerzielles Produkt, Java-basiert, benutzt DTD und CSS, editiert Baum und Textanzeige;
hat Probleme mit der Baumdarstellung unter MacOS X.
SoftQuad XMetal
Kommerzielles Produkt, recht teuer, editiert verschiedene Ansichten, darunter formatierten
Text durch Selektion mit Baum verbunden.
67
XMLSpy
Kommerzielles Produkt, editiert verschachtelte Strukturen und Text mit Tags; elegant, aber bei
tiefer verschachtelten Strukturen reicht die Schirmbreite nicht.
68
6.2 Textsysteme
Adobe FrameMaker
FrameMaker kann Textdokumente als HTML-Dokumente und XML-basierte Dokumente
speichern und hat Tabellen zur detaillierten Kontrolle über die Abbildung der Paragraph- und
Character-Styles auf Elemente.
HTML Mapping Table
FrameMaker Source Item XML Item
Element
P:Body
P
P:Heading1
H*
P:Indented
P
Parent = UL
Depth = 0
P:Numbered
LI
Parent = OL
Depth = 0
C:Emphasis
EM
Include Comments
New Web Page? Auto#
N
N
N
N
N
N
N
N
N
N
XML Mapping Table
FrameMaker Source Item XML Item
Element
P:Body
Body
P:Heading1
Heading1
P:Indented
Indented
P:Numbered
Numbered
Parent = NumberedList
Depth = 0
C:Emphasis
Emphasis
Include Auto# Comments
New Web Page?
N
N
N
N
N
N
N
N
N
N
Für HTML wird der Style-Name als CLASS Attribut eingesetzt, für XML wird der Style-Name
zum Elementnamen. Der Style wird dann in CSS für HTML und XML verschieden
nachgebildet.
FrameMaker+SGML kann XML auch importieren, wobei eine DTD auf Objekte abgebildet wird,
die in FrameMaker+SGML die Darstellung steuern.
69
Ich verwende FrameMaker und HTML zur Produktion dieser Unterlagen, muß aber vor allem
für Navigation und bei der Abbildung der Grafiken mit Skripten nachträglich editieren.
Adobe FrameMaker und Quadralay WebWorks Publisher
Publisher liest FrameMaker-Dokumente im Maker Interchange Format (MIF). Es gibt dann —
prinzipiell wohl editierbare — Vorlagen, nach denen ein Dokument in HTML oder XML mit CSS
oder XSL abgebildet wird. Dabei werden Styles mit vordefinierten Darstellungen verknüpft:
Unter MacOS X funktioniert Publisher nicht gut; außerdem fand ich keine Vorlage, die meinen
Zielen genügt, aber die Firma möchte die Gestaltung von Vorlagen als Dienstleistung
verkaufen.
Microsoft Word
Word kann HTML editieren, verwendet dazu aber einen Satz von Styles, die nur die
Fähigkeiten von HTML verfügbar machen.
Ich fand keine Möglichkeit, die Abbildung in HTML zu kontrollieren und bevorzuge daher
FrameMaker. Ich fand auch keine Möglichkeit, XML zu erzeugen.
70
6.3 Oracle XML-SQL-Utility
XSU verwendet JDBC und liefert das Resultat einer SQL-Anfrage als ziemlich naives XML-
basiertes Dokument.
Zur Demonstration kann man mysql und einen passenden JDBC-Treiber verwenden und
zunächst ein einfaches Kommando db implementieren, das SQL-Anfragen von der StandardEingabe akzeptiert und an die Datenbank schickt:
xsu/db.java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
/** trivial sql client.
*/
public class db {
/** utility to connect to a database; requires property driver with the
JDBC driver's class name and property db with a url to access the
database.
*/
public static Connection connection () throws ClassNotFoundException,
IllegalAccessException, InstantiationException, SQLException {
String driver = System.getProperty("driver");
if (driver == null)
throw new IllegalArgumentException("no driver property");
String db = System.getProperty("db");
if (db == null)
throw new IllegalArgumentException("no db property");
// register driver
DriverManager.registerDriver((Driver)Class.forName(driver).newInstance());
// connect to database
return DriverManager.getConnection(db);
}
/** execute standard input lines as SQL statements; no results...
*/
public static void main (String args []) {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String line;
// connect and create statement
Connection connection = db.connection();
Statement statement = connection.createStatement();
while ((line = in.readLine()) != null)
statement.execute(line); // result??
// disconnect
connection.close();
} catch (Exception e) { e.printStackTrace(); }
}
}
connection() verkapselt, wie man zu einer Datenbankverbindung kommt. Mit einem
Statement-Objekt kann man dann Strings ausführen lassen, ohne Resultate zu sehen.
71
get baut mit db.connection() eine Verbindung zu einer Datenbank auf, akzeptiert eine SQLAnweisung auf der Kommandozeile, schickt sie an die Datenbank und läßt das Resultat von
XSU bearbeiten:
xsu/get.java
import java.sql.Connection;
import java.sql.Statement;
import oracle.xml.sql.OracleXMLSQLException;
import oracle.xml.sql.query.OracleXMLQuery;
/** run SQL query and convert result to XML.
*/
public class get {
/** output result of a command line query to stdout.
*/
public static void main (String args []) throws Exception {
if (args == null || args.length == 0) {
System.err.println("usage: java [-Ddriver=class] [-Ddb=url] get SELECT...");
System.exit(1);
}
try {
// register driver and connect to database
Connection connection = db.connection();
// create SQL statement
Statement statement = connection.createStatement();
// concatenate query
StringBuffer query = new StringBuffer(args[0]);
for (int a = 1; a < args.length; ++ a) query.append(' ').append(args[a]);
// execute query and create xml source
OracleXMLQuery xml = new OracleXMLQuery(connection, query.toString());
// print out the result
System.out.println(xml.getXMLString());
// disconnect
connection.close();
} catch (OracleXMLSQLException e) {
System.err.println("errcode "+e.getErrorCode()+
", parent "+e.getParentException()+
", xml "+e.getXMLErrorString()+
", sql "+e.getXMLSQLErrorString());
throw e;
}
}
}
72
Ausführung
Hat man mysql installiert, kann man mit db oder einem mysql-Klienten eine Tabelle erzeugen
und betrachten:
c> mysqld-opt
c> mysql -u guest -p
Enter password: guest
mysql> use test
mysql> create table company (
coid integer not null,
name varchar (254),
addr varchar (254),
primary key (coid)) \g
mysql> insert into company values (90, 'CISCO', 'CA'); ... \g
mysql> select * from company \g
+------+------------------+------+
| coid | name
| addr |
+------+------------------+------+
|
10 | IBM
| NY
|
|
20 | CITIBANK
| NY
|
|
30 | American Express | NY
|
|
40 | GM
| MI
|
|
50 | Microsoft
| WA
|
|
60 | IBM IGS
| FL
|
|
70 | HP
| CA
|
|
80 | Intel
| CA
|
|
90 | CISCO
| CA
|
| 100 | Sun Microsystem | CA
|
+------+------------------+------+
10 rows in set (0.00 sec)
Mit get gibt man dann eine Anfrage an diese Tabelle als XML-Dokument aus:
$ java -Ddb="jdbc:mysql://venus:3306/test?user=guest&password=guest" \
>
-Ddriver="org.gjt.mm.mysql.Driver" \
>
-classpath .:xsu12.jar:xmlparserv2.jar:mm.mysql-2.0.4-bin.jar:classes12.zip \
>
get 'select * from company where name = "CISCO"'
<?xml version = '1.0'?>
<ROWSET>
<ROW num="1">
<coid>90</coid>
<name>CISCO</name>
<addr>CA</addr>
</ROW>
</ROWSET>
Man kann dabei auch noch Attribute der Datenbank ausblenden und die Elemente
beeinflussen, aber das XML-Dokument bleibt sehr flach.
Umgekehrt soll auch ein geeignetes XML-basiertes Dokument über JDBC in eine Datenbank
geladen werden können. Mindestens der mysql-JDBC-Driver von Mark Matthews versteht
aber die generierte SQL-Syntax savepoint SYS_XSU_hope_0001000 nicht.
Erzeugt man ein XML-Dokument aus der Datenbank und lädt es zurück, würde aus dem numAttribut von ROW ein neues Datenbank-Attribut entstehen — man kann also down- und upload
nicht naiv kombinieren.
73
6.4 IBM alphaWorks XML Lightweight Extractor
XLE verwendet eine DTD with source annotation (DTDSA), die man mit dem dtdsaCreator in
einer grafischen Oberfläche editieren kann.
Der Ansatz ist, daß eine DTD ein XML-basiertes Dokument dann eindeutig beschreibt, wenn
man bei jeder Wiederholung mit ?, + und * sowie bei jeder Auswahl mit | und bei PCDATA und
CDATA weiß, wieviele und welche Werte eingesetzt werden.
Eine DTDSA erweitert die DTD mit value und binding specifications an den entsprechenden
Stellen, die man zweckmäßigerweise mit dem dtdsaCreator bearbeitet, indem man die DTD
lädt, die nötigen Zusätze editiert und dann als DTDSA speichert.
xle/hello.dtd
<!DOCTYPE root [
<!ELEMENT root (#PCDATA)>
]>
hello.dtdsa
<!DOCTYPE root [
<!ELEMENT root (#PCDATA : "Hello World")>
]>
Technisch betrachtet ist die DTD inkorrekt, denn als externe DTD darf sie nicht in DOCTYPE
stehen. XLE benötigt aber diesen Rahmen, um die Wurzel des Dokuments zu entdecken, das
heißt, XLE betrachtet die DTDSA quasi als Dokument, das nur aus einem erweiterten Prolog
besteht.
In diesem Fall erfolgt kein Zugriff auf eine Datenbank, folglich kann man XLE sofort ausführen:
$ cd code/xle; alias run=`make run`
$ run XLE hello.dtdsa
<root>
Hello World
</root>
Eine value specification folgt einem Doppelpunkt und muß den Wert für eine Auswahl mit |
sowie für PCDATA und CDATA liefern. Dazu kann man folgendes verwenden:
"Hello World"
String
Parameter in0
Feld
Funktion
Wert einer Zeichenkette.
Argument von der Kommandozeile, das
dem Namen der DTDSA folgt.
row.column
Wert aus der Datenbank: row ist eine
Variable, die durch eine binding
specification gesetzt wurde, column ist
ein Attributname.
field(table, column, row) Wert aus der Datenbank: table wählt die
Tabelle, column ist der Attributname, row
ist eine Variable, die durch eine binding
specification gesetzt wurde.
74
Eine binding specification folgt zwei Doppelpunkten und verbindet beliebig viele
Variablennamen mit Listen von Zeilen aus der Datenbank, die dann für die Wiederholungen
mit ?, + und * abgearbeitet werden. Für die Namen gilt dynamische Blockstruktur, das heißt,
sie stehen innerhalb der aktiv aufgerufenen DTD-Elemente so lange zur Verfügung, bis sie
durch erneute Zuweisung verdeckt werden.
Die Listen kann man mit Funktionen oder mit SQL-Anweisungen beschreiben:
row(table)
SQL("SELECT * FROM table")
row(table, <column, ...>,
SQL("SELECT * FROM table
<value, ...>)
WHERE column=value, ...")
unique_row(...)
SQL("SELECT DISTINCT ...")
pjrow(table, <col, ...>)
SQL("SELECT col, ... FROM table")
pjrow(table, <col, ...>,
SQL("SELECT col, ... FROM table
<column, ...>, <value, ...>)
WHERE column=value, ...")
unique_pjrow(...)
SQL("SELECT DISTINCT ...")
In dieser Syntax steht value für die Möglichkeiten der value specification, wobei in einer SQLAnweisung $ vor einem Variablennamen und ein String nur in einfachen Anführungszeichen
angegeben werden muß. Die SQL-Anweisungen können mächtiger sein als die Funktionen.
Die Tabellen können auch aus mehreren Datenbanken stammen und man kann static
definierte Funktionen in einer eigenen Klasse aufrufen und damit weitere value specifications
konstruieren.
dtdsaCreator ist ein syntax-orientierter, grafischer Editor für DTDSA-Dateien, der allerdings
unter JDK 1.3.0 nicht zu funktionieren scheint (er funktioniert unter 1.2 und 1.3.1):
Man wählt eine DTD oder DTDSA und holt ihren Inhalt in das linke Fenster. Knöpfe erscheinen
dort, wo bindings möglich sind; nötige bindings sind eingerahmt. Je nach Art des bindings
erscheint eines der rechten Fenster, in dem die Syntax der Eingabe sofort kontrolliert wird. In
einem weiteren Fenster kann man sich die DTDSA anzeigen lassen und sie speichern.
75
Beispiele
Als relativ sinnvolles Beispiel kann man einen Auszug aus der Tabelle im Abschnitt
Ausführung, Seite 72, produzieren:
$ run XLE company.dtdsa MI
<list>
<company coid='40'>
<name>
GM
</name>
<addr>
MI
</addr>
</company>
</list>
Dieser Auszug entstand durch folgende DTDSA:
xle/company.dtdsa
<!DOCTYPE list [
<!ELEMENT list (company* :: company := row(company, <addr>, <in0>))>
<!ELEMENT company (name, addr)>
<!ATTLIST company
coid CDATA #REQUIRED : company.coid
>
<!ELEMENT name (#PCDATA : company.name)>
<!ELEMENT addr (#PCDATA : company.addr)>
]>
in0 greift auf die Kommandozeile zu; auf diese Weise kann man jeweils ein XML-basiertes
Dokument für alle Einträge mit gleichem addr-Attribut erzeugen. Mit SQL kann man die
Abfrage so formulieren — allerdings darf man addr in SQL nicht als Attributname angeben:
xle/company.sql.dtdsa
<!DOCTYPE list [
<!ELEMENT list (company* :: c := SQL("select * from company where coid = $in0"))>
<!ELEMENT company (name, addr)>
<!ATTLIST company
coid CDATA #REQUIRED : c.coid
>
<!ELEMENT name (#PCDATA : c.name)>
<!ELEMENT addr (#PCDATA : c.addr)>
]>
Der Zugriff auf die Datenbank erfordert natürlich JDBC sowie die folgende Konfigurationsdatei:
xle/access.cfg
org.gjt.mm.mysql.Driver
jdbc:mysql://venus:3306/test?user=guest&password=guest
dontcare
dontcare
dontcare
76
XLE ist eine Konzeptstudie. Man kann damit Information aus vielen Tabellen mischen — wie
IBM’s eigene Beispiele zeigen — aber man kann fast nur Information in ein Dokument
einfügen, die aus einer Datenbank stammt. Einen Report mit festen Textteilen kann man kaum
direkt produzieren:
xle/html.dtdsa
<!DOCTYPE html [
<!ELEMENT html (head, body)>
<!ELEMENT head (title)>
<!ELEMENT title (#PCDATA :in0)>
<!ELEMENT body (p, ul)>
<!ELEMENT p (font, b)>
<!ELEMENT font (#PCDATA :"The following is/are located in")>
<!ELEMENT b (#PCDATA :in0)>
<!ELEMENT ul (li+ :: company := row(company, <addr>, <in0>))>
<!ELEMENT li (#PCDATA :company.name)>
]>
$ run XLE html.dtdsa NY
liefert in einem Web-Browser den Text
The following is/are located in NY
* IBM
* CITIBANK
* American Express
Es ist recht mühsam (und nicht zu verallgemeinern), wie die Titelzeile konstruiert wird.
77
7
Simple API for XML
DasSimple API for XML (SAX), jetzt in der Version 2 — mit Namespace — dient zur Erkennung
eines XML-basierten Dokuments in einem Java-Programm.
SAX definiert eine XMLReaderFactory, von der man sich einen XMLReader beschaffen kann.
Bei diesem kann man verschiedene Handler anmelden und dann mit parse() eine Eingabe
bearbeiten, wobei dann die Handler an verschiedenen Stellen zurückgerufen werden. Die
Architektur entspricht dem flachen Handler, Seite 40.
Der Parser (XMLReader) kann möglicherweise durch Setzen verschiedener boolean Features
konfiguriert werden. Außerdem kann man Object Properties setzen oder betrachten.
7.1 Hauptprogramm
Hat man einen XML-Parser wie Crimson oder Xerces-J, kann man mit folgendem Programm
eine Datei bearbeiten:
sax/Main.java
package sax;
import java.io.IOException;
import java.util.StringTokenizer;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.DeclHandler;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.XMLReaderFactory;
/** processes an XML file with a ContentHandler.
Patterned after McLaughlin's SAXParserDemo.
*/
public class Main {
/** commandline.
*/
public static void main (String args []) {
if (args == null || args.length > 1) {
System.err.println("usage: java -Dorg.xml.sax.driver=classname "+
"sax.Main [file.xml]");
System.err.println("
-Dcontent=classname");
System.err.println("
-Ddtd=classname");
System.err.println("
-Derror=classname");
System.err.println("
-Dfalse='feature ...'");
System.err.println("
-Dlex=classname");
System.err.println("
-Dtrue='feature ...'");
System.err.println("
-Dverbose=true");
System.err.println("
-Dxml=classname");
System.exit(1);
}
InputSource is = args.length == 0
? new InputSource(System.in)
: new InputSource(args[0]);
if (!parse(is)) System.exit(1);
}
78
Das Hauptprogramm übergibt eine URL, also einen lokalen Dateinamen oder eine
Netzadresse, als Kommandozeilenargument an parse().
Das Programm wird über Properties gesteuert. Für SAX muß mindestens der Name der
Parser-Klasse definiert werden. Zusätzlich kann man verschiedene SAX-Handler über
Klassennamen einführen und verschiedene SAX-Features als wahr oder falsch definieren.
parse() erzeugt die Handler, holt sich einen XMLReader (Parser) von der Fabrik und
konfiguriert ihn, und wendet schließlich den Parser auf die URL an:
sax/Main.java
/** create parser and parse one url.
@return true if parse is successful, else print message.
*/
public static boolean parse (InputSource is) {
ContentHandler contentHandler = (ContentHandler)handler("content");
ErrorHandler errorHandler = (ErrorHandler)handler("error");
}
try {
XMLReader parser = XMLReaderFactory.createXMLReader();
if (xml != null)
try {
Object h = Class.forName(xml)
.getConstructor(new Class[]{ XMLReader.class })
.newInstance(new Object[]{ parser });
parser.setContentHandler((ContentHandler)h);
parser.setDTDHandler((DTDHandler)h);
parser.setProperty("http://xml.org/sax/properties/declaration-handler",
h);
parser.setProperty("http://xml.org/sax/properties/lexical-handler", h);
} catch (Exception e) { System.err.println(xml+": "+e); }
if (contentHandler != null)
parser.setContentHandler(contentHandler);
if (declHandler != null)
parser.setProperty("http://xml.org/sax/properties/declaration-handler",
declHandler);
if (dtdHandler != null)
parser.setDTDHandler(dtdHandler);
if (errorHandler != null)
parser.setErrorHandler(errorHandler);
if (lexicalHandler != null)
parser.setProperty("http://xml.org/sax/properties/lexical-handler",
lexicalHandler);
setFeature(parser, "false", false);
setFeature(parser, "true", true);
parser.parse(is);
return true;
} catch (IOException e) {
System.err.println(is.getSystemId()+": "+e);
} catch (SAXParseException e) {
Errors.report("fatal syntax error", is.getSystemId(), e);
} catch (SAXException e) {
System.err.println(is.getSystemId()+": "+e);
}
return false;
79
Um einen Handler aus einer Klasse zu erzeugen, deren Namen als Property angegeben ist,
kann man folgende Hilfsfunktion definieren:
sax/Main.java
/** convenience method to create a handler from a property.
@return null if property was not specified.
*/
public static Object handler (String propertyName) {
String className = System.getProperty(propertyName);
if (className == null || className.length() == 0) return null;
}
Exception e;
try {
return Class.forName(className).newInstance();
} catch (ClassNotFoundException _e) { e = _e; }
catch (InstantiationException _e) { e = _e; }
catch (IllegalAccessException _e) { e = _e; }
System.err.println(propertyName+"="+className+": "+e); System.exit(1);
return null; // not reached
Ein SAX-Feature ist ein boolean-Wert mit einer URL wie http://xml.org/sax/features/
validation als Name, der beim XMLReader mit setFeature() beeinflußt werden kann. Um
diese Features per Java-Property beeinflussen zu können, definiert man folgende
Hilfsfunktion:
sax/Main.java
/** convenience method to set features from a property.
*/
public static void setFeature (XMLReader parser, String propertyName,
boolean how) {
String features = System.getProperty(propertyName);
if (features != null) {
StringTokenizer st = new StringTokenizer(features);
while (st.hasMoreTokens()) {
String feature = st.nextToken();
try {
parser.setFeature(feature, how);
} catch (SAXException e) {
System.err.println(feature+": "+e);
}
}
}
}
}
Main funktioniert auch schon ohne Handler:
$ java -classpath ..:../etc/xml.apache.org/xerces-1_3_0/xerces.jar \
-Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser sax.Main
<?xml version="1.0"?>
<root/>
Allerdings erkennt man selbst bei Validierung kaum Fehler, da nach Voreinstellung nur fatal
errors — im Wesentlichen lexikalische Probleme — berichtet werden:
$ alias run='`make run`'
$ run -Dtrue=http://xml.org/sax/features/validation sax.Main
<!DOCTYPE root [
<!ELEMENT root EMPTY>
]>
<super/>
80
7.2 ErrorHandler
SAX unterscheidet drei Arten von Fehlern: warning() — erfolgt praktisch nie, error() — bei
den meisten Validierungsproblemen, und fatalError() — im Wesentlichen lexikalische
Fehler und Syntaxfehler in der DTD:
sax/Errors.java
package sax;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXParseException;
/** gripes about any SAX problem.
Patterned after McLaughlin's SAXParserDemo.
*/
public class Errors implements ErrorHandler {
/** convenience method to report a SaxParseException.
*/
public static void report (String prefix, String url, SAXParseException e) {
String s;
if ((s = e.getPublicId()) == null
&& (s = e.getSystemId()) == null)
s = url;
System.err.println(prefix+": "+
s+"("+e.getLineNumber()+
":"+e.getColumnNumber()+
") "+e.getMessage());
}
/** reports a warning but does not throw an exception.
*/
public void warning (SAXParseException e) {
report("warning", "", e);
}
/** reports an error but parsing can continue.
*/
public void error (SAXParseException e) {
report("syntax error", "", e);
}
/** terminates with a fatal syntax error.
*/
public void fatalError (SAXParseException e) throws SAXParseException {
throw e;
}
}
Einen error() sollte man definitiv berichten — dies geschieht leider nicht nach Voreinstellung
— und nur nach einem fatalError() sollte man den Parser nicht weiter betreiben.
Hinterlegt man einen geeigneten ErrorHandler und schaltet man Validierung ein, kann man
jetzt ein XML-Dokument prüfen:
$ alias run='`make run`'
$ run -Derror=sax.Errors -Dtrue=http://xml.org/sax/features/validation sax.Main
<!DOCTYPE root [
<!ELEMENT root EMPTY>
]>
<root bad="value"/>
syntax error: (4:20) Attribute "bad" must be declared for element type "root".
81
7.3 ContentHandler
SAX definiert verschiedene Methoden, die aufgerufen werden, wenn bestimmte Teile des
XML-basierten Dokuments beim Parser auftauchen. Definiert man alle möglichen Methoden,
kann man ein Dokument einigermaßen duplizieren:
sax/Dup.java
package sax;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
/** shows all tags.
-Dignore=true to skip ignorable whitespace.
-Dverbose=true for tracing.
<?sax.Dup locator?> to dump locator, if any.
Patterned after McLaughlin's SAXParserDemo.
*/
public class Dup implements ContentHandler {
/** document position of callback -- only valid within this scope.
*/
protected Locator locator;
public void setDocumentLocator (Locator locator) {
if (Boolean.getBoolean("verbose"))
System.err.println(toString(locator));
this.locator = locator;
}
/** convenience Method to dump a locator.
*/
public static String toString (Locator locator) {
if (locator == null) return "null";
else {
StringBuffer result = new StringBuffer();
String s = locator.getPublicId();
if (s == null) s = locator.getSystemId();
if (s != null) result.append(s);
result.append('(').append(locator.getLineNumber());
result.append(':').append(locator.getColumnNumber());
result.append(')');
return result.toString();
}
}
locator wird vom Parser bei einer neuen Quelle übergeben und dann dynamisch modifiziert.
Bei Ausführung entdeckt man allerdings gewisse Ungereimtheiten, beispielsweise enthält der
locator bei verschiedenen Parsern verschiedene Werte.
$ java -classpath ..:../etc/xml.apache.org/xerces-1_3_0/xerces.jar \
> -Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser \
> -Dcontent=sax.Dup -Dverbose=true sax.Main
<?xml version="1.0"?>
<root/>
(1:1)
(1:1) start document
<root></root>Null Entity(-1:-1) end document
$ java -classpath ..:../etc/xml.apache.org/crimson-1.1/crimson.jar \
> -Dorg.xml.sax.driver=org.apache.crimson.parser.XMLReaderImpl \
> -Dcontent=sax.Dup -Dverbose=true sax.Main
<?xml version="1.0"?>
<root/>
(1:-1)
(1:-1) start document
<root></root>(3:-1) end document
82
Für die verschiedenen Arten von Markup gibt es dann verschiedene Methoden(paare):
sax/Dup.java
public void startDocument () throws SAXException {
if (Boolean.getBoolean("verbose"))
System.err.println(toString(locator)+" start document");
}
public void endDocument () throws SAXException {
if (Boolean.getBoolean("verbose"))
System.err.println(toString(locator)+" end document");
}
public void processingInstruction (String target, String data)
throws SAXException {
if ("sax.Dup".equals(target) && "locator".equals(data))
System.err.println(toString(locator));
else
System.out.print("<?"+target+" "+data+"?>");
}
public void startPrefixMapping (String prefix, String uri) throws SAXException {
if (Boolean.getBoolean("verbose"))
System.err.println(toString(locator)+" start xmlns:"+prefix+"=\""+uri+"\"");
}
public void endPrefixMapping (String prefix) {
if (Boolean.getBoolean("verbose"))
System.err.println(toString(locator)+" end xmlns:"+prefix);
}
public void startElement (String namespaceURI, String localName,
String rawName, Attributes atts) throws SAXException {
StringBuffer s = new StringBuffer("<");
s.append(rawName);
for (int a = 0; a < atts.getLength(); ++ a) {
s.append(' ').append(atts.getQName(a)).append('=');
String v = atts.getValue(a);
if (v.indexOf('"') >= 0) s.append('\'').append(v).append('\'');
else s.append('"').append(v).append('"');
}
s.append('>');
System.out.print(s);
}
public void endElement (String namespaceURI, String localName, String rawName)
throws SAXException {
System.out.print("</"+rawName+">");
}
public void characters (char[] ch, int start, int end) throws SAXException {
System.out.print(new String(ch, start, end));
}
}
public void ignorableWhitespace (char[] ch, int start, int end)
throws SAXException {
if (!Boolean.getBoolean("ignore"))
System.out.print(new String(ch, start, end));
}
/** should not happen (external entities)...
*/
public void skippedEntity (String name) throws SAXException {
System.err.println(toString(locator)+": "+name+" skipped");
}
SAX2 unterstützt auch Namespaces.
83
Das mixed content model resultiert in viele Aufrufe von characters() zwischen Elementen:
$ java -classpath ..:../etc/xml.apache.org/xerces-1_3_0/xerces.jar \
> -Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser \
> -Dcontent=sax.Dup sax.Main
<root>
pre
<sep/> <sep/>post</root>
<root>
pre
<sep></sep> <sep></sxep>post</root>
Die Definition von whitespace hängt davon ab, ob ein validierender Parser verwendet wird —
nicht etwa davon, ob validiert wird. Ein validierender Parser sollte whitespace mit
ignorableWhitespace() berichten, der hier dann mit -Dignore=true entfernt werden kann,
ein anderer Parser sollte characters() verwenden. Leider liefern verschiedene Parser
verschiedene Resultate:
$ java -classpath ..:../etc/xml.apache.org/crimson-1.1/crimson.jar \
> -Dorg.xml.sax.driver=org.apache.crimson.parser.XMLReaderImpl \
> -Dcontent=sax.Dup -Dignore=true sax.Main
<!DOCTYPE root [
<!ELEMENT root EMPTY>
<!ATTLIST root ok CDATA #IMPLIED>
]>
<root ok="value">
</root>
<root ok="value"></root>
$ java -classpath ..:../etc/xml.apache.org/xerces-1_3_0/xerces.jar \
> -Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser -Dcontent=sax.Dup \
> -Dignore=true -Dtrue=http://xml.org/sax/features/validation sax.Main
<!DOCTYPE root [
<!ELEMENT root EMPTY>
<!ATTLIST root ok CDATA #IMPLIED>
]>
<root ok="value">
</root>
<root ok="value">
</root>
Xerces macht hier leider einen Fehler. Allgemein ist der Rat der Weisen, man solle sich in
einem XML-basierten Dokument keinesfalls auf whitespace verlassen.
Eine PI kann der Handler mit processingInstruction() verarbeiten.
Theoretisch könnte ein nicht validierender Parser eine externe Entity mit skippedEntity()
übergehen. Praktisch liefert Xerces einen Fehler und Crimson verbietet eine unparsed Entity
und fügt eine parsed Entity ein — whitespace ist wieder mit Fehlern behaftet:
$ java -classpath ..:../etc/xml.apache.org/crimson-1.1/crimson.jar \
> -Dorg.xml.sax.driver=org.apache.crimson.parser.XMLReaderImpl \
> -Dcontent=sax.Dup sax.Main
<!DOCTYPE root [
<!ELEMENT root (#PCDATA)>
<!ENTITY entity SYSTEM "file:/etc/motd">
]>
<?target data?>
<root>&entity;</root>
<?target data?><root>Welcome to Darwin!
</root>
84
LexicalHandler
Möglicherweise kann man einen LexicalHandler beim Parser als Property anmelden. Mit
diesem Handler kann man unter anderem Kommentare, CDATA, manche Entities und die
Position einer DTD bearbeiten:
sax/LDup.java
$ java -classpath ..:../etc/xml.apache.org/crimson-1.1/crimson.jar \
> -Dorg.xml.sax.driver=org.apache.crimson.parser.XMLReaderImpl \
> -Dlex=sax.LDup -Dcontent=sax.Dup -Dverbose=true sax.Main
<!DOCTYPE root [
<!ELEMENT root (#PCDATA)>
<!ENTITY entity SYSTEM "file:/etc/motd">
]>
<?target data?>
<root>&entity;</root>
(1:-1)
(1:-1) start document
<!DOCTYPE root [
]>
<?target data?><root>start entity entity
Welcome to Darwin!
end entity entity
</root>(8:-1) end document
7.4 DTDHandler und DeclHandler
Mit einem DTDHandler sieht man unparsed entity declarations in der DTD. Möglicherweise
kann man auch einen DeclHandler beim Parser als Property anmelden. Mit diesem Handler
sieht man den restlichen Inhalt einer DTD:
sax/DDup.java
$ java -classpath ..:../etc/xml.apache.org/crimson-1.1/crimson.jar \
> -Dorg.xml.sax.driver=org.apache.crimson.parser.XMLReaderImpl \
> -Dlex=sax.LDup -Ddtd=sax.DDup sax.Main
<!DOCTYPE root [
<!ELEMENT root (#PCDATA)>
<!ENTITY entity SYSTEM "file:/etc/motd">
]>
<?target data?>
<root>&entity;</root>
<!DOCTYPE root [
<!ELEMENT root (#PCDATA)>
<!ENTITY entity SYSTEM "file:/etc/motd">
]>
Insgesamt ist festzuhalten, daß man ein XML-basiertes Dokument mit SAX2 nicht exakt
reproduzieren kann. Prinzipiell gibt es eine Property xml-string, über die die Eingabe bei
jedem Event zugänglich sein soll (siehe XDup.java), aber xml-string wird von Xerces und
Crimson nicht unterstützt.
85
7.5 Arithmetische Ausdrücke
Man kann arithmetische Ausdrücke in XML darstellen, siehe XML für arithmetische
Ausdrücke, Seite 45. Der folgende ContentHandler schickt Literale und Operatorenan ein
Cpu-Objekt und gibt das Resultat aus:
sax/Eval.java
/** connects expression based on expr.dtd with a cpu, prints result.
Throws all errors as exceptions.
*/
public class Eval extends DefaultHandler {
/** evaluates an expression.
Add methods like void add () throws SAXException; for operator tags.
*/
public interface Cpu {
void reset();
Object push (Object value);
Object pop () throws EmptyStackException;
}
public void startDocument () throws SAXException {
cpu.reset();
}
public void endDocument () throws SAXException {
System.out.println(cpu.pop());
}
public void startElement (String namespaceURI, String localName,
String rawName, Attributes atts) throws SAXException {
if ("literal".equals(localName)) {
String type = atts.getValue("type");
if (type.indexOf('.') <= 0) type = "java.lang."+type;
String value = atts.getValue("value");
try {
cpu.push(Class.forName(type)
.getConstructor(new Class[] { String.class })
.newInstance(new Object[] { value }));
} catch (Exception e) {
throw new SAXException(localName+" type="+type+" value="+value, e);
}
}
}
public void endElement (String namespaceURI, String localName, String rawName)
throws SAXException {
try {
cpu.getClass().getMethod(localName, new Class[0])
.invoke(cpu, new Object[0]);
} catch (Exception e) {
if (e instanceof SAXException) throw (SAXException)e;
}
}
}
public void warning (SAXParseException e) throws SAXParseException { throw e; }
public void error (SAXParseException e) throws SAXParseException { throw e; }
86
Eine triviale Cpu merkt sich den jeweils letzten Literal. Eine Processing Instruction legt die
Klasse des Cpu-Objekts fest, wobei jeweils der aktuelle Wert-Stack übernommen wird:
sax/Eval.java
/** by default: trivial cpu remembers last value.
*/
protected Cpu cpu = new Cpu() {
public void reset() { }
public Object push (Object value) {
this.value = new Object[] { value }; return value;
}
private Object[] value;
public Object pop () {
if (value == null) throw new EmptyStackException();
Object result = value[0]; value = null; return result;
}
};
/** <?sax.Eval classname?> replaces cpu.
*/
public void processingInstruction (String target, String data)
throws SAXException {
if ("sax.Eval".equals(target)) {
try {
cpu = (Cpu)Class.forName(data)
.getConstructor(new Class[] { Cpu.class })
.newInstance(new Object[] { cpu });
} catch (Exception e) {
throw new SAXException ("error in creating cpu", e);
}
}
}
Die folgende Klasse realisiert Integer-Arithmetik:
sax/IntCpu.java
public class IntCpu extends Stack implements Cpu {
public IntCpu () { }
public void reset () { removeAllElements(); }
public void add () {
Number right = (Number)pop();
push(new Integer(((Number)pop()).intValue() + right.intValue()));
}
// ...
Für Float-Arithmetik ersetzt man die Operatoren:
sax/FloatCpu.java
public class FloatCpu extends IntCpu {
public FloatCpu (Cpu cpu) { super(cpu); }
public FloatCpu () { }
public void add () {
Number right = (Number)pop();
push(new Float(((Number)pop()).floatValue() + right.floatValue()));
}
87
Der Konstruktor übernimmt jeweils den vorhergehenden Stack:
sax/IntCpu.java
/** retrieve stack from incoming cpu.
*/
public IntCpu (Cpu cpu) {
if (cpu != null) {
Stack kcats = new Stack();
try {
for (;;) kcats.addElement(cpu.pop());
} catch (EmptyStackException e) { }
try {
for (;;) addElement(kcats.pop());
} catch (EmptyStackException e) { }
}
}
Damit kann man die Art der Arithmetik durch Processing Instructions wechseln:
sax/expr.xml
<?xml version="1.0"?>
<!DOCTYPE expr SYSTEM "expr.dtd">
<?sax.Eval sax.IntCpu?>
<!-- 2 * ( 3 + 4 * 5 - 6 / - 7 * 8 ) -->
<?sax.Dup locator?>
<expr>
<mul>
<literal type="Byte" value="2"/>
<sub>
<add>
<literal type="Byte" value="3"/>
<mul>
<literal type="Byte" value="4"/>
<literal type="Byte" value="5"/>
</mul>
</add>
<mul>
<?sax.Eval sax.FloatCpu?>
<!-- ?eval.xsl int? -->
<div>
<!-- ?eval.xsl int? -->
<literal type="Byte" value="6"/>
<minus>
<literal type="Byte" value="7"/>
</minus>
</div>
<literal type="Byte" value="8"/>
</mul>
<?sax.Eval sax.IntCpu?>
</sub>
</mul>
</expr>
88
7.6 FrameMaker
FrameMaker kann XML erzeugen. Der vorliegende Text enthält Verweise auf Quellcode und
Ausschnitte aus den Quellen. sax.Code kann als ContentHandler beides extrahieren —
allerdings nur mit Xerces, Crimson produziert Unsinn:
$ java -classpath ..:../etc/xml.apache.org/xerces-1_3_0/xerces.jar \
>
-Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser \
>
-Dcontent=sax.Code sax.Main sax.fm.xml
file: "sax/Main.java"
body: "package sax;
import java.io.IOException;
import java.util.StringTokenizer;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
Analog könnte man beispielsweise ein SAX-basiertes Programm schreiben, um Links in einem
FrameMaker-Dokument zu prüfen. Man muß aber einen beachtlichen Aufwand treiben, um
whitespace korrekt zu behandeln.
7.7 JAXP
89
8
Document Object Model
Je nach Speicherplatz kann man kleinere XML-basierte Dokumente auch komplett im
Speicher als Baum darstellen. Man benötigt dazu eine Klassenhierarchie für die Baumknoten.
8.1 XOML
Als primitiver Baumknoten genügt eine ArrayList, in der die verschachtelten Texte als
String und die verschachtelten Baumknoten der Reihe nach gespeichert werden. Außerdem
enthält jedes derartige Element eine HashMap, die Attributnamen und -werte als String
einander zuordnet.
Mit einem ContentHandler wie Tree kann ein SAX-Parser ein Dokument als Baum darstellen.
Es ist zweckmäßig, wenn man den Baum auch wieder in XML darstellen kann. Problematisch
ist allenfalls der Umgang mit whitespace.
$ java -classpath ..:xerces.jar:oops.jar \
> -Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser \
> -Dcontent=xoml.Tree -Derror=sax.Errors -Dverbose=true sax.Main
<root/>
<root/>
Der Gedanke liegt nahe, eine einfache Sprache xoml per Interpreter zu implementieren, mit
der man derartige Bäume manipulieren kann. Xoml ist ein Goalie der zu xoml.ebnf ein
Programm in Befehle für einen Interpreter VM übersetzt.
Auf der Basis von Schleifen kann ein Programm wie chef.xoml zum Beispiel in einem
Dokument personen.xml Mitarbeiter ihren Chefs zuordnen. Mit Hash-Tabellen, die syntaktisch
als Arrays angesprochen werden, kann man zum Beispiel zählen, wie häufig bestimmte Werte
eines Attributs auftreten.
Ein Programm mit rekursiven Funktionen wie traverse.xoml kann ein unbekanntes Dokument
traversieren und darstellen.
Läßt man Zuweisungen an Baumknoten und Attribute zu, kann ein Programm wie html.xoml
auch ein neues Dokument erzeugen.
Die Implementierung ist naiv, aber durch gezielten Einsatz der Java Foundation Classes
bereits sehr mächtig. Man könnte xoml beispielsweise mit regulären Ausdrücken, weiteren
String-Funktionen, Syntax zur Validierung von Dokumenten und direktem Zugriff auf neue
Java-Klassen erweitern.
90
8.2 jaxb
Sun arbeitet an einem Java API for XML Binding (JAXB). Es geht darum, aus einem
Dokument-Schema (derzeit nur einer DTD) Dokument-spezifische Klassen zu erzeugen, die
ein Dokument einlesen, validieren und ausgeben können.
$ xjc expr.dtd -roots expr
./Add.java
./Div.java
./Expr.java
./Literal.java
./Minus.java
./Mul.java
./Sub.java
Aus der früher betrachteten DTD für arithmetische Ausdrücke entstehen zum Beispiel Klassen,
die arithmetische Ausdrücke repräsentieren können. Da überall mehrere komplexe Elemente
verschachtelt werden können, verwenden alle Klassen jeweils List als Knoteninhalt.
Mit einem einfachen Hauptprogramm kann man einen Ausdruck als XML-Dokument einlesen
— das allerdings keine PIs enthalten darf — oder durch Konstruktion von Objekten erzeugen.
Ein Dokument kann durch einen einfachen Methodenaufruf ganz oder teilweise validiert und
ausgegeben werden:
$ java -classpath .:jaxb-rt-1.0-ea.jar Main
<?xml version="1.0" encoding="UTF-8"?>
<expr>
<add>
<literal type="Integer" value="1"/>
<literal type="Integer" value="2"/></add></expr>
xjc berücksichtigt ein in XML formuliertes Binding Schema, das Einfluß auf die generierten
Klassen nimmt. Damit kann man zum Beispiel von den meisten Klassen verlangen, daß sie
die binär verschachtelten Knoten als explizite, einzelne Properties left und right und nicht
als List speichern und folglich korrekt validieren. Die Wurzel Expr verwendet aber trotz aller
Versuche — für die Validierung inkorrekt — eine List. Das Binding Schema ist auch nicht in
der Lage, bei einem Literal zwei Attribute so zu verknüpfen, daß zie zusammen (oder
einzeln) die generierte Klasse beeinflussen. Man kann aber für ein Element die
Repräsentierung der Properties durch spezielle Java-Klassen definieren.
Schließlich kann man die generierten Klassen auch ableiten und um eigene Methoden wie
zum Beispiel eval() erweitern. Hinterlegt man im sogenannten Dispatcher eine Abbildung
der ursprünglichen zu den abgeleiteten Klassen, wird ein eingelesenes Dokument in den
neuen Klassen repräsentiert.
JAXB ist ein interessanter Ansatz. Ein einfaches Dokument wie eine Personalliste kann mit
einem einfachen Binding Schema so abgebildet werden, daß ein Java-Programm sehr
problemspezifisch codiert werden kann.
Leider ist das System keineswegs ausgereift; wenn XML Schema berücksichtigt wird, sollten
sich außerdem die Abbildungen zu Java-Typen sehr vereinfachen. Derzeit definiert ein
Binding Schema vieles doppelt, was eigentlich schon in der DTD steht, und reproduziert in
XML einiges an Syntax, was man viel eleganter in Java ausdrücken kann. Ohne JavaProgrammierung ist das System ohnedies nicht benutzbar.
91
8.3 DOM
Das Document Object Model des W3C (DOM) spezifiziert eine Klassenhierarchie, mit der
XML-basierte Dokumente repräsentiert werden sollen.
Die Spezifikation ist sprachunabhängig, weil sie auf der Basis der Interface Definition
Language der Common Request Broker Architecture erfolgte. CORBA definiert dann
Language Mappings von der IDL auf sehr viele verschiedene Programmiersprachen, und man
kann einen DOM-Baum prinzipiell über CORBA verteilen. In der Realität gibt es da gewisse
Haken — siehe Stefan Rauch’s Diplomarbeit.
Die Java-Spezifikation entspricht nicht dem Mapping, das ein IDL-Compiler aus der
Spezifikation erzeugen würde: Ein Interface wie Document enthält dort auch CORBAFähigkeiten; nur ein Interface wie DocumentOperations bezieht sich nur auf die in der IDLSpezifikation angegebenen Attribute.
Ein XML-Parser wird normalerweise mit einer DOM-Implementierung geliefert. JAXP
implementiert auch ein Factory-Muster für Parser, die DOM-Bäume abliefern. Die DOMImplementierungen für zwei verschiedene Parser müssen aber nur die gleiche, deklarative
IDL-Spezifikation erfüllen; es handelt sich nicht um die gleichen Klassen. Man kann aber
manchmal interoperieren. Stefan Rauch hat demonstriert, daß sich die Implementierungen
deutlich in Funktionalität und Effizienz unterscheiden.
DOM hat eine etwas merkwürdige Hierarchie, bei der insbesondere die Anbindung mancher
Methoden überrascht:
DOMImplementation
NamedNodeMap
Node
Attr
CharacterData
Comment
Text
CDATASection
Document
DocumentFragment
DocumentType
Element
Entity
EntityReference
Notation
ProcessingInstruction
NodeList
Zugriff auf spezielle Methoden (anstelle von Konstruktoren)
änderbar, Resultat von getAttributes() in Node
Basis-Interface, definiert Obermenge von Methoden
beschreibt Attribut, gilt nicht als Knoten
Methoden zum Zugriff auf Text in einer Node
beschreibt <!-- ... -->
beschreibt <![CDATA[ ... ]]>
Wurzel eines Dokuments, oberhalb des äußeren Elements
Container für unstrukturierte Knoten
beschreibt (rudimentär) DTD
beschreibt <tag ...> ... </tag>
kann <!ENTITY beschreiben (neben Dokument)
kann auf Entity verweisen
beschreibt <!NOTATION (neben Dokument)
beschreibt <?target data?>
änderbar, Resultat von getChildNodes() in Node
Dokumente kann man editieren, wobei sie allerdings nicht unbedingt valid bleiben (kein
Transaktionskonzept). Holt man Knoten in eine NodeList und verschiebt man sie von dort —
zum Beispiel über ein DocumentFragment — in ein neues Dokument, dann werden sie aus
dem ursprünglichen Dokument entfernt. Es gibt aber cloneNode(boolean deep).
Eine DOM-Implementierung muß selbst weder Serializable noch als XML auszugeben sein.
Man kann aber mit einem org.apache.xml.serialize.XMLSerializer auch ein fremd
implementiertes DOM als XML ausgeben, siehe dom.Main.
DOM Level 2 definiert insbesondere Range, um einen Bereich in einem Dokument zu
manipulieren, und DocumentTraversal, um Teilbäume iterativ oder als Baum zu traversieren,
siehe dom.Chef.
92
8.4 JDOM
McLaughlin’s JDOM ist eine speziell auf Java zugeschnittene Klassenbibliothek zur
Bearbeitung von XML-basierten Dokumenten. JDOM stützt sich auf die Java Collection
Classes (ab JDK 1.2) und vermeidet den zusätzlichen Aufwand, den sich DOM durch seine
allgemeine Spezifikation einhandelt.
Die Klassen erlauben elegantere Codierungen als DOM, siehe jdom.Chef, aber um weitere
Entwicklungen wie XPath oder XSLT zu verwenden, muß man einen JDOM-Baum zunächst als
DOM-Baum exportieren.
93
9
XSL Transformationen
Die Extensible Stylesheet Language (XSL) besteht aus zwei Teilen, die völlig verschiedene
Ziele verfolgen: Ein XSL-Transformator wie Saxon oder Xalan transformiert einen Baum —
typischerweise ein XML-basiertes Dokument — unter Kontrolle eines Stylesheets —
normalerweise ein weiteres XML-basiertes Dokument — in einen anderen Baum und gibt ihn
in Form von XML, HTML oder Text aus. Ein XSL-Formatierer wie FOP akzeptiert einen Baum
aus Formatting Objects — typischerweise ein mit XSLT transformiertes, XML-basiertes
Dokument — und produziert daraus druckbare Texte im Portable Document Format (PDF).
XSLT, die Sprache, in der ein Stylesheet formuliert wird, ist eine XML-Applikation, die
Elemente konventioneller Programmiersprachen enthält und sie mit Mustererkennung und anwendung auf Bäumen kombiniert, wobei XPath-Ausdrücke mit zusätzlichen Funktionen die
Rolle von Ausdrücken bei konventionellen Sprachen übernehmen. Eine definitive
Beschreibung mit sehr vielen Beispielen und Tips ist das Buch von Michael Kay.
9.1 XPath
Mit XPath werden Teile eines Baums ausgewählt — folglich definiert XPath, was XSLT als
Baum betrachtet; dies entspricht mehr oder weniger dem DOM. Xalan enthält
org.apache.xml.xpath und andere Pakete, mit denen man ein Programm zum Test von
XPath-Ausdrücken implementieren kann, siehe xpath.Main:
$ java -classpath ..:xerces.jar:xalan.jar \
>
-Djavax.xml.parsers.DocumentBuilderFactory=\
>org.apache.xerces.jaxp.DocumentBuilderFactoryImpl xpath.Main pets.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- from DNJ 7/8 2001 -->
<pets>
<dogs>
<dog
breed="Labrador">
<color>Black</color>
</dog>
<dog
breed="Labrador">
<color>Golden</color>
</dog>
</dogs>
...
</pets>
//*[@breed='Labrador']/color
<!-- result node -->
<color>Black</color>
<!-- result node -->
<color>Golden</color>
94
Sprachelemente
XPath bearbeitet Gleitkommawerte, Strings, logische Werte und Baumknoten, wobei bei
Bedarf sehr tolerant umgewandelt wird:
12e3
Inf -Inf NaN
+ - * div mod
ceiling(...)
floor(...)
number()
round()
count(...)
last()
position()
string-length(...)
sum(...)
true()
false()
and or
not(...)
= != < <= > >=
boolean(...)
contains(s, s)
’...’ "..."
concat(..., ...)
starts-with(...)
substring(...)
substring-after(...)
substring-before(...)
translate(...)
normalize-space(...)
local-name()
name()
namespace-uri()
string(...)
location-path
|
id(...)
Gleitkommawerte
arithmetische Operationen mit üblichem Vorrang
negatives Vorzeichen
arithmetische und Umwandlungsfunktionen
Anzahl Knoten in einer Knotenmenge
Anzahl Knoten im Kontext
im Kontext, ab 1 bis last(), je nach Kontext auch von hinten gezählt
Stringlänge
Knotenmenge wird in number() umgewandelt und aufaddiert
logische Werte
logische Operationen, mit üblichem Vorrang, bewertet mit Abbruch
von links nach rechts
Vergleiche mit logischem Resultat, mit üblichem C-Vorrang; in der
Regel mit Character Entities formuliert; überraschend bei Knoten
Null, NaN und leere Mengen und Strings sind false(), Rest true()
true() wenn zweiter String im ersten enthalten ist
Unicode Strings, in der Regel mit Character Entities
String- und Umwandlungsfunktionen
entfernt umgebenden und vielfachen Leerraum sowie Zeilentrenner
vom Kontext oder vom ersten Element im Argument
Umwandlung, bei Nodeset Textwert des ersten Knotens
wählt Knotenmenge
vereinigt zwei Knotenmengen
liefert Knotenmenge, die ID(s) enthält; Probleme falls nicht valid
Gegenüber diesen Operationen haben location-paths Vorrang; sie dienen zur Auswahl von
Knotenmengen:
location-path: "/"? location-step ( "/" location-step )*;
location-step: axis "::" node-test ( "[" predicate "]" )*;
Ein absoluter location-path beginnt mit / und geht dann von dem Document-Knoten aus.
Ein relativer location-path beginnt mit einem Knoten, der sich aus dem Kontext ergibt —
bei xpath.Main ist das jeweils der letzte ausgegebene Knoten.
Jeder location-step führt entlang einer axis einen node-test aus, durch den Knoten
ausgewählt werden. Ein predicate ist ein Test, durch den dann die Knotenmenge auf die
Knoten reduziert wird, für die dieser boolesche Ausdruck oder die numerische Position zutrifft.
Das Resultat dient dann als Kontext für den nächsten location-step. Leider gibt es eine
Vielzahl von Abkürzungen...
95
axis
child
default direkt verschachtelte Knoten des Kontext-Elements; keine
descendant
descendant-or-self //
parent
..
ancestor
ancestor-or-self
following-sibling
preceding-sibling
following
preceding
attribute
namespace
self
@
.
Attribute oder Namespaces
alle im Kontext-Element enthaltenen Knoten; keine
Attribute oder Namespaces
Kontext und descendant
Knoten, in den der Kontext direkt verschachtelt ist
Document-Knoten und alle, in die der Kontext verschachtelt
ist
ancestor und Kontext
alle Knoten nach dem Kontext, die in den gleichen Knoten
verschachtelt sind; Attribute und Namespaces haben keine
Geschwister
alle Knoten vor dem Kontext, die in den gleichen Knoten
verschachtelt sind
alle Knoten nach dem Kontext, aber keine Attribute und
Namespaces
alle Knoten vor dem Kontext
alle Attribute des Kontext-Elements
alle Namespaces, die für das Kontext-Element gelten
Kontext
node-test
alle Elemente oder alle Attribute mit dem Namen
alle Elemente oder alle Attribute im gleichen Namespace
mit dem Namen
*
alle Elemente oder alle Attribute oder alle Namespaces
prefix:*
im gleichen Namespace
comment()
alle Kommentarknoten
text()
alle Textknoten
node()
alle Knoten, unabhängig vom ihrer Art
processing-instruction() alle PI’s, auch für ein spezielles Target
name
prefix:name
Ein predicate ist entweder eine Zahl, nämlich die Position ab 1 eines Knotens in einer
Menge — eventuell von hinten gezählt — oder ein logischer Wert; im obigen Beispiel:
//dog[2]
<!-- result node -->
<dog breed="Labrador">
<color>Golden</color>
</dog>
//dog[@breed='Labrador'][color='Black']
<!-- result node -->
<dog breed="Labrador">
<color>Black</color>
</dog>
//dog[starts-with(.//text()[2], 'B')]
<!-- result node -->
<dog breed="Labrador">
<color>Black</color>
</dog>
Der innere Index ist nötig, weil es dort zwei Textknoten mit whitespace gibt. Anders als ein
location-step ändert das predicate die Knoten nicht.
96
9.2 Ablauf einer Transformation
Ein Stylesheet enthält sogenannte Templates, die auf einen Baum angewendet werden. Ein
minimales Stylesheet ist folgendes XML-basierte Dokument:
xslt/trivial.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"/>
Wendet man es auf das obige Beispiel an, erhält man nur die Texte:
$ java -classpath saxon.jar com.icl.saxon.StyleSheet pets.xml trivial.xsl
<?xml version="1.0" encoding="utf-8"?>
Black
...
Da hier überhaupt keine Templates definiert sind, werden nur die vordefinierten Templates
angewendet:
Document
Element
Attribut
text()
Kommentar
PI
Namespace
<xsl:apply-templates mode=[same]/>
<xsl:apply-templates mode=[same]/>
<xsl:value-of select="."/>
<xsl:value-of select="."/>
bearbeitet alle Elemente
bearbeitet alle verschachtelten Elemente
wird als Text kopiert
wird kopiert
wird nicht bearbeitet
wird nicht bearbeitet
wird nicht bearbeitet
Attribute werden allerdings nur explizit betreten:
xslt/text.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*">
<xsl:apply-templates select="node() | @*"/>
</xsl:template>
</xsl:stylesheet>
Dieses Stylesheet gibt Attribute und Texte aus — node() wählt zwar alle Knotenarten, aber
die Attribute liegen auf einer eigenen Achse.
Allgemein beginnt eine Transformation damit, daß ein Template für das Document gesucht
wird. Dieses Template kann die Suche mit apply-templates für andere Knoten fortsetzen
lassen und/oder andere Aktionen ausführen.
Templates werden nach Import-Reihenfolge und dann nach Priorität ausgewählt. Die Priorität
hängt davon ab, wie spezifisch match ist. Die vordefinierten Templates werden immer (zuletzt)
gefunden — das kann man nur verhindern, wenn man sie explizit überschreibt.
mode partitioniert die Erkennung eines Templates — damit kann man einen Baum für
verschiedene Zwecke verschieden traversieren.
97
9.3 Steuerung der Traverse
Wie ein Baumknoten bearbeitet wird, beeinflußt man, indem man ein Template definiert, das
zu einem Knoten paßt, siehe pets.xsl:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="pets">
<html>
<body>
<table>
<xsl:apply-templates/></table></body></html></xsl:template>
<xsl:template match="dogs">
<tr>
<td colspan="2">Dogs</td></tr>
<xsl:apply-templates/></xsl:template>
<xsl:template match="dog">
<tr>
<td>
<xsl:value-of select="@breed"/></td>
<td>
<xsl:value-of select="color"/></td></tr></xsl:template>
</xsl:stylesheet>
Tiefer verschachtelte Knoten werden dann nur noch bearbeitet, wenn apply-templates
aufgerufen wird.
Ob ein Baumknoten bearbeitet wird, beeinflußt man, indem man apply-templates auf
ausgewählte Teilbäume einschränkt:
<xsl:apply-templates select="dogs"/></table></body></html></xsl:template>
So werden die Katzen auch nicht mit den vordefinierten Templates bearbeitet.
In welcher Reihenfolge Baumknoten bearbeitet werden, entscheidet zunächst die
Reihenfolge, in der verschiedene apply-templates angegeben werden:
<xsl:apply-templates select="*[.//color = 'Golden']"/>
<xsl:apply-templates select="*[.//color = 'Black']"/></xsl:template>
So erscheinen die Hunde nicht in Dokument-Reihenfolge.
Bezieht sich apply-templates auf mehrere Teilbäume, kann man die Reihenfolge noch mit
sort steuern — mehrere Aufrufe etablieren sekundäre Schlüssel usw.:
<xsl:apply-templates>
<xsl:sort select=".//color" order="descending"/>
</xsl:apply-templates></xsl:template>
So erscheinen die Hunde nach Farben sortiert.
Weitere Beispiele: Darstellung von Chefs zu Personen. Darstellung einer Liste von Dozenten.
98
Pattern
apply-templates@select benützt einen XPath-Ausdruck, um eine Knotenmenge
auszuwählen. Diese Knotenmenge wird dann zum Ziel aller template@match Pattern.
Aus Effizienzgründen ist ein Pattern nur ein eingeschränkter XPath-Ausdruck — mit
zunehmendem Vorrang:
verknüpft Alternativen
erkennt Document
verknüpft location-steps
//
am Anfang nicht nützlich
verknüpft location-steps, erkennt beliebige Verschachtelung
id(’x’)
erkennt ID attribut
key(’x’, ...)
oder Markierung
attribute::
@
bezieht sich auf Attribute
child::
default bezieht sich auf direkt verschachtelte Elemente
node-test
wie bei XPath
predicate
wie bei XPath, ohne Variablen und current()
|
/
Ob ein Pattern auf einen Knoten zutrifft, hängt davon ab, ob der Knoten selbst oder irgendein
umgebender Knoten als Kontext mit dem Pattern als XPath-Ausdruck eine Knotenmenge
liefert, die den Knoten enthält.
Normalerweise betrachtet man deshalb ein Pattern von rechts nach links. select liefert
Knoten an, die selbst den letzten node-test bestehen müssen
match="foo//bar[position()=last()-3]"
Hier passen nur bar-Knoten, die irgendwo in einen foo-Knoten geschachtelt sind. Ein
predicate bezieht sich allerdings nicht auf die von select gelieferte Knotenmenge sondern
auf die Position des Knotens im vorhergehenden Schritt im Pattern.
99
9.4 Programmierelemente
XSLT enthält einfache Kontrollstrukturen. Als Beispiel für mehr oder weniger konventionelle
Programmierung kann man Euklid’s Algorithmus betrachten:
xslt/euclid.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="pairs">
<pair>
<x>36</x> <y>54</y></pair>
<pair>
<x>54</x> <y>36</y></pair></xsl:variable>
definiert globale und lokale Variablen, auf die dann mit $name in
XPath-Ausdrücken verwiesen wird.
select=’wert’ legt den Wert fest — Zuweisung ist später nicht möglich.
Alternativ kann der Inhalt des Elements den Wert festlegen.
variable name=’name’
<xsl:template match="/">
<xsl:message>pairs: <xsl:copy-of select="$pairs"/></xsl:message>
kommuniziert mit dem XSLT-Prozessor.
Typisch ist, daß der Inhalt als Diagnose ausgegeben wird.
terminate=’yes’ sollte die Ausführung dann abbrechen.
copy
shallow copy; Inhalt wird bei Document oder Element eingefügt.
copy-of select=’rtf’
deep copy eines result tree fragments.
message
<xsl:for-each select="$pairs">
<xsl:variable name="result">
<xsl:call-template name="gcd">
<xsl:with-param name="x" select="pair/x"/>
<xsl:with-param name="y" select="pair/y"/></xsl:call-template>
</xsl:variable>
<a value="{$result}"/></xsl:for-each>
<xsl:for-each select="$pairs/pair">
<xsl:variable name="result">
<xsl:call-template name="gcd">
<xsl:with-param name="x" select="x"/>
<xsl:with-param name="y" select="y"/></xsl:call-template></xsl:variable>
<b value="{$result}"/></xsl:for-each></xsl:template>
for-each
select=’nodeset’
sort
call-template name=’name’
with-param
name=’name’
select=’wert’
literal
name=’{wert}’
element
name=’name’
namespace=’uri’
attribute
name=’name’
namespace=’uri’
wendet Inhalt auf nodeset an.
regelt Reihenfolge; viele Attribute.
ruft Template per name auf; auch rekursiv.
weist Wert an Template-Parameter zu.
legt den Wert fest; alternativ auch als Inhalt des Elements.
definiert einen Resultatknoten.
{ ... } wird in Attributen durch Wert wie value-of ersetzt.
definiert einen Resultatknoten. Es gibt auch comment und
processing-instruction.
definiert Attribut im aktuellen Resultatknoten;
auch in -set. Wert als Inhalt des Elements.
100
<xsl:template name="gcd">
<xsl:param name="x"/>
<xsl:param name="y"/>
<xsl:choose>
<xsl:when test="$x = $y">
<xsl:value-of select="$x"/></xsl:when>
<xsl:when test="$x < $y">
<xsl:call-template name="gcd">
<xsl:with-param name="x" select="$x"/>
<xsl:with-param name="y" select="$y - $x"/></xsl:call-template></xsl:when>
<xsl:otherwise>
<xsl:call-template name="gcd">
<xsl:with-param name="x" select="$x - $y"/>
<xsl:with-param name="y" select="$y"/></xsl:call-template>
</xsl:otherwise></xsl:choose></xsl:template>
</xsl:stylesheet>
template
param
value-of
match=’pattern’
mode=’name’
name=’name’
priority=’number’
name=’name’
select=’wert’
select=’wert’
disable-outputescaping=’yes’
text
disable-outputescaping=’yes’
choose
when
test=’boolean’
otherwise
if
test=’boolean’
definiert Template; mit pattern für apply-templates.
schränkt ein; gleicher name bei apply-templates.
definiert Template; mit name für call-template.
entscheidet bei mehrdeutiger Erkennung.
deklariert Parameter; auch global.
legt Voreinstellung fest; alternativ auch als Inhalt.
definiert Resultat-String: als Text des (ersten) Knotens oder
durch Umwandlung von numerischen Werten.
verhindert Ersatzdarstellung von < und &.
definiert Resultat-String durch seinen Text-Inhalt; auch
Zwischenraum.
verhindert Ersatzdarstellung von < und &.
Fallverteiler.
enthält Fall in choose. Inhalt wird je nach test ausgeführt.
letzte Alternative in choose.
Entscheidung; kein else. Inhalt wird je nach test ausgeführt.
Wesentliches Handicap der Sprache sind die fehlende Zuweisung und daher auch das Fehlen
konventioneller Schleifen.
Schleifen muß man durch Rekursion ersetzen — siehe gcd.
Funktionen erhält man, wenn man call-template in eine variable schachtelt, die damit
das Resultat der Template-Anwendung speichert.
Datenstrukturen kann man als Inhalt von variable aufbauen und mit for-each oder durch
XPath-Ausdrücke bearbeiten.
Es ist sehr schwierig — vielleicht auch unmöglich — ganz präzise zu kontrollieren, wieviel
Zwischenraum durch eine Transformation erzeugt wird. [Kay] gibt einige Tips zur
Fehlerbehebung, aber ich glaube nicht, daß man derzeit Text mit XSLT erzeugen kann, bei
dem es auf einzelne Leerzeichen und Zeilentrenner ankommt.
Vorsicht Falle: Das Beispiel gibt Folgendes aus:
<?xml version="1.0" encoding="utf-8"?><a value="36"/><b value="18"/><b value="18"/>
Nur b ist korrekt...
101
9.5 Weitere Beispiele
Suche
Farbtabelle
RGB-Tabelle
Homepage
Code
FTP-Bereich
Artikel
pets.xml
key.xsl
colors.xsl
doColors.xsl
makefile
rgb.txt
rgb2xml
rgb.xsl
index.xml
index.xsl
Schlüssel erzeugen mit <key> und suchen
mit key().
colors.23.html import, Rekursion für Schleifen, Ausgabe
colors.127.html nicht well-formed..
rgb.html
index.html
verschiedene Ausgabedateien; Berechnung
home.html
von Kalendertagen.
title-frame.html
code/index.xml code/index.html Aufsammeln von Pfaden.
code/index.xsl
ftp/index.xml
ftp/index.html
verschiedene Ausgabedateien; Aufsammeln
ftp/index.xsl
von Tabelleneinträgen für rowspan.
rec/index.xml
rec/index.html
rec/index.xsl
In Kay’s Buch wird die Pferdchenreise in XSLT programmiert... (das soll allerdings auch mit
einer Turing-Maschine möglich sein!)
102
103
A
Begriffe
ASCII
CSS
DSSSL
DOM
DTD
American Standard Code for Information Interchange: definiert
Steuerzeichen, Buchstaben, Ziffern, Sonderzeichen und Interpunktion mit
Werten zwischen 0 und 127; enthält praktisch keine nationalen
Schriftzeichen wie Umlaute etc.
Cascading Style Sheets: zur detaillierten Kontrolle der Formatierung von
XML- und HTML-basierten Dokumenten.
Document Style Semantics and Specification Language: 1996 ISOnormierte, von Clark et al. definierte Sprache zur Transformation und
Darstellung von SGML- und damit auch von XML-basierten Dokumenten.
Document Object Model: Spezifikation einer Klassenhierarchie zur
Repräsentierung von XML-basierten Dokumenten.
Document Type Definition: Grammatik für ein validierbares, XMLbasiertes Dokument; mit anderer Meta-Syntax auch für SGML. Es gibt
veröffentlichte DTDs:
<!DOCTYPE
<!DOCTYPE
<!DOCTYPE
<!DOCTYPE
DTDSA
Element
FO
GML
Grammatik
HTML
HTTP
JAXB
JAXP
HTML
HTML
HTML
HTML
PUBLIC
PUBLIC
PUBLIC
PUBLIC
"-//W3C//DTD
"-//W3C//DTD
"-//W3C//DTD
"-//W3C//DTD
HTML
HTML
HTML
HTML
3.2//EN">
4.0//EN">
4.01//EN">
4.01 Frameset//EN">
Document Type Declaration: doctypedecl im Prolog eines XML-basierten
Dokuments, enthält oder verweist auf markupdecl, die dann eine
Grammatik enthalten, welche letztlich die Document Type Definition ist.
DTD with Source Annotation: aus dieser kann XLE mit JDBCDatenbankzugriffen XML-basierte Dokumente generieren.
Knoten im Dokumentbaum eines XML-basierten Dokuments; begrenzt
durch Tags.
Flow Objekte: darauf werden SGML- und XML-basierte Dokumente
transformiert und dann in Sprachen wie PDF und RTF dargestellt.
Generalized Markup Language: 1969 von Goldfarb, Mosher und Lorie zur
Dokument-Produktion bei IBM.
Regeln zur Beschreibung der Syntax einer Sprache; daraus wird häufig
ein Parser maschinell generiert.
Hypertext Markup Language: 1989 von Berners-Lee zur elektronischen
Verbreitung von Dokumenten in der Physik bei CERN.
Hypertext Transfer Protokoll: bevorzugtes Transport-Protokoll für HTML.
Java API for XML Binding: Klassen und ein Compiler von Sun, mit denen
man aus einer DTD spezifische Dokument-Klassen erzeugt, die
passende Dokumente einlesen, validieren und ausgeben können (sehr
early access).
Java API for XML Processing: Factory-Klassen von Sun, die für
standardisierten Zugriff auf XML-Parser und XSLT-Prozessoren sorgen
sollen.
104
JDBC
Jade
Markup
Namespace
Java Database Connectivity: Java Package zum SQL-basierten Zugriff
auf Datenbanken; abstrahiert Zugriff von der eigentlichen Kommunikation,
für die ein Datenbank-spezifischer JDBC-Adapter zuständig ist.
James’ DSSSL Engine: Clark’s C++-basierte Implementierung des
Darstellungsteils von DSSSL.
Praktisch alles außer reinem Text in einem XML-basierten Dokument,
siehe Markup, Seite 52.
Verknüpfung von Namen mit URI in einem XML-basierten Dokument, um
einfache Namen zu differenzieren. Wichtig sind vielleicht:
xmlns:Cocoon="http://xml.apache.org/cocoon/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xsi="http://www.w3.org/1999/XMLSchema/instance"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
Parser
Programm(teil) zur Erkennung einer Sprache.
PDF
Portable Document Format, zur Drucker- und Bildschirm-Ausgabe
verwendete Sprache von Adobe.
PI
Processing Instruction: <?target data?> an Stelle eines Elements in
einem XML-basierten Dokument; enthält Daten, die ein spezieller
Prozessor (target) interpretieren soll.
Repräsentierung Regeln, die die Darstellung von Symbolen einer Sprache in einem
Eingabe-Zeichensatz festlegen; daraus wird häufig ein Scanner
maschinell generiert.
RTF
Rich Text, in Textsystemen und für Dokumentaustausch verwendete
Sprache von Microsoft.
SAX
Simple API for XML: 2000 von David Megginson definierte Schnittstelle
zwischen einem XML-Parser und seinen semantischen Aktionen, an die
sich die meisten Parser halten.
Scanner
Programm(teil) zur Erkennung der Symbole einer Sprache.
SGML
Standard Generalized Markup Language: 1986 ISO-normierte Version
von XML.
SQL
Standard Query Language: zum Zugriff auf Datenbanken.
Syntax
Regeln, die das Aufeinanderfolgen von Symbolen einer Sprache
festlegen.
Tag
Markup am Anfang und Ende eines Elements; der Start-Tag <a> kann
Attribut-Wert-Paare enthalten, der End-Tag hat die Form </a>. In XML
kann ein Element auch aus einem einzigen Tag der Form <b/> bestehen,
der ebenfalls Attribut-Wert-Paare enthalten kann.
Unicode
Zeichensatz, der zunächst die gleichen Zeichen mit den Werten von
ASCII definiert, danach aber auch Symbole und Zeichen aus sehr vielen
Sprachen.
URI
Universal Resource Identifier: Oberbegriff für URL und URN; derzeit
nahezu synonym mit URL.
URL
Universal Resource Locator: Adresse im Internet, mit der man auf eine
Ressource zugreifen kann; in etwa protocol://host:port/path.
105
URN
UTF-8
valid
Validierung
well-formed
XHTML
XLE
XML
XML Schema
XPath
XSD
XSL
XSL-FO
XSLT
Universal Resource Name: soll eine Ressource im Internet eindeutig
bezeichnen; bisher undefiniert.
Eine Repräsentierung von Unicode, die für ASCII-Zeichen identisch zu
ASCII ist und für andere Zeichen bis zu 3 Bytes verwendet.
Ein XML-basiertes Dokument ist valid, wenn es seiner DTD genügt.
Untersuchung durch XML-Parser, ob ein XML-basiertes Dokument valid
ist; well-formed muß es immer sein.
Ein XML-basiertes Dokument ist well-formed, wenn seine
Repräsentierung korrekt ist — das sollte immer der Fall sein.
HTML, neu definiert mit XML: alle Elemente müssen abgeschlossen
werden und Klein/Großschreibung ist signifikant.
XML Lightweight Extractor von den IBM alphaWorks: erzeugt mit Hilfe
einer DTDSA und mit JDBC-Datenbankzugriffen XML-basierte
Dokumente.
Extensible Markup Language: 1997 von Bosak, Clark et al. vereinfachte
Version von SGML.
Ersatz für DTD: definiert Grammatik und vor allem Datentypen für XMLbasierte Dokumente.
Auf ‘‘Pfaden’’ beruhende Syntax zur Adressierung in einem XMLbasierten Dokument.
XML Schema.
Extensible Stylesheet Language: noch nicht verabschiedete, von Clark et
al. definierte Sprache zur Transformation und Darstellung von XMLbasierten Dokumenten.
Formatierungs-Teil von XSL.
Transformations-Teil von XSL: vom W3C verabschiedet.
106
Herunterladen