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&uuml;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&uuml;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 (&uuml;). 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&uuml;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&uuml;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&uuml;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&uuml;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 &amp; &gt; &lt; &quot; und &apos; 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&uuml;ck!</title> <link href="5.css" rel="stylesheet" type="text/css"> </head> <body> <table> <tr> <td>Hello, Osnabr&uuml;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: &amp; &gt; &lt; &quot; &szlig; &auml; & > < " ß ä 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&uuml;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&uuml;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: &#x22; &#x26; &#x27; &#x3c; &#x3e; Doppelanführungszeichen &quot; &amp; &apos; &lt; &gt; & 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 &#123; 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 &lt; $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