9. Fortgeschrittene Techniken Inhalt 1. 2. 3. 4. 5. 6. 7. 8. 9. Einführung Vorlagen XPath Kontrollstrukturen Sortierung, Gruppierung und Nummerierung Parameter und Variablen Ein- und Ausgabeformate Auslagerung und Wiederverwendung Fortgeschrittene Techniken Eigene XSLT-Funktionen Benannte Vorlagen Stylesheet-Funktionen Einführung Trotz des sehr stark erweiterten Funktionsumfangs von XPath gibt es natürlich immer Bedarf an wieder verwendbaren Komponenten, die man entweder in einer einzigen Transformation mehrfach verwenden möchte oder die für eine ganze Anwendung in verschiedenen Transformationen zum Einsatz kommen können. Mit Hilfe der allgemeinen Vorlagen gibt es bereits ein Konzept, wie man wieder verwendbare Komponenten in XSLT erstellen kann. Diese hängen allerdings doch sehr von den Vorlagen-Regeln und damit der Vorlagenautomatik ab. Weitere Lösungen sind benannte Vorlagen und StylesheetFunktionen (neu in XSLT 2.0). Benannte Vorlagen Die einfachste Möglichkeit, auch in XSLT 1.0 schon solche benannten wieder verwendbaren Vorlagen zu erstellen, stellen benannte Vorlagen in Form von Funktionen oder Prozeduren, wie sie auch zur Erledigung von wiederholt auftretenden Anweisungen in anderen Programmiersprachen zum Einsatz kommen, dar. Man wird diesen benannten Vorlagen nicht notwendigerweise ansehen, dass sie spezielle Vorlagen für unsere Anwendung darstellen. Vielleicht liegen sie in einer entsprechend benannten Datei, die von mehreren anderen XSLT-Dateien eingebunden wird, sie weisen einen besonderen Namensraum auf oder befolgen spezielle Namenskonventionen. Benannte Vorlagen Sie bestehen aus folgenden Bestandteilen: Ein möglichst allgemeiner Name, der auf ihre Wiederverwendbarkeit deutlich hinweist. Dieser Name könnte genauso gut in einer anderen Programmiersprache für eine ähnlich aufgebaute oder mit ähnlichen Algorithmen ausgestattete Methode/Funktion/Routine stehen. Eine Parameterliste, die einzelne Werte oder ganze Knoten bzw. Knotensätze erwartet. Diese Parameter tragen auch Namen, die nicht mit Namen aus dem XMLDatenstrom Ähnlichkeiten aufweisen, sondern eher allgemeiner Natur sind. Dies soll verdeutlichen, dass eine universelle Einsetzbarkeit der Vorlage durch diese Schnittstellen gegeben ist. Der Aufruf erfolgt dann gerade nicht über das Ping-Pong-Spiel, sondern mit Hilfe des Vorlagennamens unter Verwendung der benötigten Parameter. Der Rückgabewert einer solchen Vorlage erfolgt dann über die Ausgabe im XMLAusgabestrom mit Hilfe von xsl:text, xsl:value-of oder einer Textknoten-Ausgabe in einem Element. Benannte Vorlagen Benannte Vorlagen Benannte Vorlagen Eigene XSLT-Funktionen Benannte Vorlagen Stylesheet-Funktionen Stylesheet-Funktionen Die allgemeine Syntax des neuen xsl:function-Elements lautet: <xsl:function name = qname as = sequence-type override = "yes" | "no"> <!-- Content: (xsl:param*, sequence-constructor) --> </xsl:function> Die Deklaration stellt ein Element der obersten Ebene dar, damit es von XPathAusdrücken im ganzen Transformationsdokument aufgerufen werden kann. Folgende Attribute sind vorhanden: name enthält den Namen der Funktion in Form eines QName, der auch ein Namensraumpräfix enthalten muss. as gibt optional den Rückgabedatentyp an, in den der erstellte Wert umgewandelt wird, ansonsten wird einfach der Wert so zurückgeliefert, wie er ermittelt wurde. override legt optional mit dem Wert yes fest, dass die so auszeichnete Funktion eine andere Funktion gleichen Namens und die Anzahl der Stellen überschreibt und diese Funktion stattdessen verwendet wird. Fragen... SQL-ähnliche Abfragen Bedingungen Verknüpfungen Mengen Abfragen Die einfachsten Abfragen von XML mit Hilfe von XPath, wie sie auch schon in der Version 1.0 möglich waren, setzen die verschiedenen Operatoren ein. Hier ist eine große Ähnlichkeit zwischen XPath und SQL, allerdings auch mit vielen anderen Syntaxstrukturen, die es ermöglichen, Bedingungen zu formulieren. Letztendlich entsprechen die Operatoren den einfachen Syntaxmöglichkeiten, die in der WHERE-Klausel von SQL auftauchen können. Vergleich Bedeutung Kalkül Bedeutung Logik Bedeutung = Gleichheit + Addition and Und != Ungleichheit - Subtraktion or Oder < bzw. &lt; kleiner * Multiplikation not Nicht > bzw. &gt; größer div Division <= bzw. &lt; kleiner gleich mod Modulo >= bzw. &gt größer gleich Abfragen <Umsatzliste> <Rechnungsliste> <Rechnung R_Nr="7" R_Datum="31.03.03" R_Summe="8.61"/> <Rechnung R_Nr="115" R_Datum="31.05.03" R_Summe="19.16"/> ... </Rechnungsliste> <Postenliste> <Posten P_Nr="33" R_Nr="7" P_Summe="2.28" T_Nr="2"/> <Posten P_Nr="34" R_Nr="7" P_Summe="0.17" T_Nr="3"/> ... <Posten P_Nr="559" R_Nr="115" P_Summe="4.49" T_Nr="8"/> <Posten P_Nr="560" R_Nr="115" P_Summe="2.93" T_Nr="10"/> </Postenliste> <Tarifliste> <Tarif T_Nr="8" T_Name="Schicht2" T_GueBis="31.12.03"/> <Tarif T_Nr="9" T_Name="Nachtschicht1" T_GueBis="31.12.03"/> <Tarif T_Nr="10" T_Name="Nachtschicht2" T_GueBis="31.12.03"/> </Tarifliste> </Umsatzliste> Abfragen Alle Rechnungen mit mehr als fünf Euro Summe //Rechnung[@R_Summe > 5] Alle Rechnungen mit einer Summe zwischen 2 und 5 //Rechnung[@R_Summe > 2 and @R_Summe < 5] Jede zweite Rechnung //Rechnung[position() mod 2 = 0] Alle Rechnungen nach dem 01.04.03 //Rechnung[xs:date(string-join((substring(@R_Datum, 7,2), substring(@R_Datum, 4,2), substring(@R_Datum, 1,2)),'-')) > xs:date('03-04-01')] Alle Posten, deren Summe größer als der Durchschnitt ist //Posten[@P_Summe > avg(//Posten/@P_Summe)] SQL-ähnliche Abfragen Bedingungen Verknüpfungen Mengen Verknüpfungen Normalerweise dürften die XML-Dokumente hierarchisch aufgebaut sein, sodass keine besonderen Verknüpfungen wie bei relationalen Daten notwendig sind. Dies erleichtert bspw. die reihenweise Verarbeitung der Rechnungen und ihrer Posten, wobei die Posten alleine für eine Ausgabe wenig Sinn machen. Nichtsdestoweniger gibt es immer wieder Situationen, in denen man gerade eine solche aufgetrennte Datenhaltung verwenden möchte. Dies eignet sich immer dann, wenn allgemeine Elemente zentral gesammelt werden, auf die sich nachher in Form von Ressourcen bezogen werden soll. Hier wäre eine doppelte Datenhaltung, die bei lokaler und hierarchisierter Speicherung notwendig würde, unnütz. Stattdessen verweise man mit Hilfe von Referenzen auf die global verfügbaren Ressourcen. Verknüpfungen <Umsatzliste> <Rechnung R_Nr="7" R_Datum="31.03.03" R_Summe="8.61"> <Posten P_Nr="33" R_Nr="7" P_Summe="2.28" T_Nr="2" /> <Posten P_Nr="34" R_Nr="7" P_Summe="0.17" T_Nr="3" /> ... </Rechnung> <Rechnung R_Nr="8" R_Datum="31.03.03" R_Summe="5.85"> <Posten P_Nr="41" R_Nr="8" P_Summe="1.18" T_Nr="2" /> <Posten P_Nr="42" R_Nr="8" P_Summe="0.02" T_Nr="3" /> ... </Rechnung> ... Verknüpfungen <Umsatzliste> <Tarif T_Nr="1" T_Name="Frühstück" T_GueBis="30.06.03"> <Rechnungsliste/> </Tarif> <Tarif T_Nr="2" T_Name="Mittagspause" T_GueBis="30.06.03"> <Rechnungsliste> <Rechnung R_Nr="7" R_Datum="31.03.03" R_Summe="8.61"/> <Rechnung R_Nr="8" R_Datum="31.03.03" R_Summe="5.85"/> ... </Rechnungsliste> </Tarif> SQL-ähnliche Abfragen Bedingungen Verknüpfungen Mengen Mengen Schnittmenge (Menge der Elemente, die in beiden Mengen vorhanden ist): op:intersect($parameter1 as node()*, $parameter2 as node()*) as node()* Vereinigungsmenge (Menge aller Elemente beider Mengen): op:union($parameter1 as node()*, $parameter2 as node()*) as node()* Differenzmenge (Menge der Elemente, die in der einen, nicht aber in der anderen Menge enthalten sind): op:except($parameter1 as node()*, $parameter2 as node()*) as node()* Fragen... Dynamisches XSLT XSLT erzeugen XSLT zusammensetzen XSLT erzeugen <?xml-stylesheet type="text/xsl" href="821_01.xslt"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"> <xs:element name="Erfolguebersicht"> <xs:complexType> <xs:sequence> <xs:element name="Erfolg" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="Gesamt" type="xs:string"/> <xs:element name="Neukunden" type="xs:string"/> </xs:sequence> <xs:attribute name="Stadt" type="xs:string" use="required"/> <xs:attribute name="Monat" type="xs:string" use="required"/> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema> XSLT erzeugen <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/02/xpath-functions" xmlns:xdt="http://www.w3.org/2005/02/xpath-datatypes" exclude-result-prefixes="fn xdt xs"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> <!-- Globale Parameter --> <xsl:param name="Wurzel"/> <!-- Startvorlage --> <xsl:template match="/xs:schema/xs:element[parent::xs:schema]"> <xsl:element name="xsl:stylesheet"> <xsl:attribute name="version">2.0</xsl:attribute> <xsl:namespace name="fn">http://www.w3.org/2005/02/xpathfunctions</xsl:namespace> <xsl:namespace name="xdt">http://www.w3.org/2005/02/xpathdatatypes</xsl:namespace> <xsl:namespace name="xs">http://www.w3.org/2001/XMLSchema</xsl:namespace> <xsl:element name="xsl:output"> <xsl:attribute name="method">html</xsl:attribute> <xsl:attribute name="version">1.0</xsl:attribute> <xsl:attribute name="encoding">UTF-8</xsl:attribute> <xsl:attribute name="indent">yes</xsl:attribute> </xsl:element> Dynamisches XSLT XSLT erzeugen XSLT zusammensetzen XSLT zusammensetzen Kategorie Neuerstellung Zusammensetzen Speicherort Es ist kein Speicherort vorhanden. Die Daten werden auf Basis einer externen Informationsquelle neu erstellt. Die Päckchen (globale komplexe Typen / Vorlagen) liegen in einzelnen Dateien oder in einer Datenbank. Grundprinzip XSLT generiert neues XSLT, das dann für Eine Transformation zur Verfügung steht. XSLT oder eine andere Sprache setzt neues XSLT zusammen, das dann für eine Transformation zur Verfügung steht. Aufbau XML Schema kann Datenstrukturen beschreiben, die dann mit Hilfe von allgemeinen Vorlagen umgewandelt werden. Alternativ kann es auch Zusatzinformationen geben, die die Ausgabe (Tabelle, Liste, Summenbildung etc.) steuern. Für Textdateien stehen die einfachen Techniken der dateibasierten Auslagerung zur Verfügung. Für den Datenbank-Einsatz müssen die Päckchen zunächst gespeichert und dann bei Bedarf durch eine Abfrage zusammengesetzt werden. XSLT zusammensetzen <Kundenliste xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance" xsi:noNamespaceSchemaLocation="221_01.xsd"> <Kunde Nr="235" Anrede="Frau" Beginn="04.10.03"> <Name> <Rufname>Verena</Rufname> <Zuname>Fiegert</Zuname> </Name> <Adresse> <Strasse>Universitätsstr. 40</Strasse> <PLZ>47051</PLZ> <Stadt>Duisburg</Stadt> </Adresse> </Kunde> ... XSLT zusammensetzen Es sind zwei unterschiedliche Möglichkeiten der dateibasierten Auslagerung vorhanden: Bei der Einbindung handelt es sich um einen Vorgang, der die Inhalte einer externen Datei wie bei einem Kopiervorgang in den Quelltext einfügt. Da zudem das xsl:include-Element überall als Element der obersten Ebene erscheinen darf – d.h. auch zwischen xsl:template-Elementen –, kann man über die Position bestimmen, ob bei konkurrierenden Vorlagen die eingebundenen oder die lokal vorhandenen ausgeführt werden. <!-- Kategorie: Deklaration --> <xsl:include href = uri-reference /> Beim Import handelt es sich um eine Einbettung von externen Vorlagen, die stets eine geringere Priorität haben als lokal vorhandene Vorlagen. Das xsl:import-Element darf an keiner anderen Stelle stehen als an erster Stelle aller Elemente der obersten Ebene. <!-- Kategorie: Deklaration --> <xsl:import href = uri-reference /> XSLT zusammensetzen Import und Inklusion <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" version="1.0" encoding="ISO-8859-1" indent="yes"/> <xsl:include href="kundenliste.xslt"/> <xsl:include href="kunde.xslt"/> <xsl:include href="name.xslt"/> <xsl:include href="adresse.xslt"/> </xsl:stylesheet> XSLT zusammensetzen Externe Dokumente zusammensetzen <paeckchenstruktur> <ausgabe typ="uebersichtsliste"/> <paeckchen name="kundenliste.xslt"/> <paeckchen name="kunde.xslt"/> <paeckchen name="name.xslt"/> <paeckchen name="adresse.xslt"/> </paeckchenstruktur> XSLT zusammensetzen Datenbankeinsatz -- Tabelle BERICHT DROP TABLE "SCOTT"."BERICHT"; CREATE TABLE "SCOTT"."BERICHT" ( "B_NR" NUMBER(10) NOT NULL, "B_NAME" VARCHAR2(50) NOT NULL, "B_WURZEL" "SYS"."XMLTYPE" NOT NULL, CONSTRAINT "B_Schluessel" PRIMARY KEY("B_NR")); -- Tabelle XMLPAKET DROP TABLE "SCOTT"."XMLPAKET"; CREATE TABLE "SCOTT"."XMLPAKET" ( "X_NR" NUMBER(10) NOT NULL, "X_XMLSCHEMA" "SYS"."XMLTYPE" NOT NULL, "X_XSLT" "SYS"."XMLTYPE" NOT NULL, CONSTRAINT "X_Schluessel" PRIMARY KEY("X_NR")); -- Tabelle BERICHT_ZU_XMLPAKET DROP TABLE "SCOTT"."BERICHT_ZU_XMLPAKET"; CREATE TABLE "SCOTT"."BERICHT_ZU_XMLPAKET" ( "BZX_NR" NUMBER(10) NOT NULL, "B_NR" NUMBER(10) NOT NULL, "X_NR" NUMBER(10) NOT NULL, CONSTRAINT "BZX_Schluessel" PRIMARY KEY("BZX_NR")); XSLT zusammensetzen Datenbankeinsatz INSERT INTO "SCOTT"."BERICHT" VALUES (1, 'Kundenliste', XMLType('<xs:element name="Kundenliste" type="KundenlisteType" xmlns:xs="http://www.w3.org/2001/XMLSchema"/>')); XSLT zusammensetzen Datenbankeinsatz INSERT INTO "SCOTT"."XMLPAKET" VALUES (1, XMLType('<xs:complexType name="KundenlisteType" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:sequence> <xs:element name="Kunde" type="KundeType" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType>'), XMLType('<xsl:template match="/Kundenliste" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <html> <head> <title>Kundenliste</title> </head> <body> <xsl:apply-templates select="Kunde"/> </body> </html> </xsl:template>')); XSLT zusammensetzen Datenbankeinsatz CREATE OR REPLACE FUNCTION "SCOTT".getXSLT ( berichtsnr IN "SCOTT"."BERICHT"."B_NR"%TYPE) RETURN "SYS"."XMLTYPE" IS -- Zwischenspeicher Rückgabewert v_XSLT VARCHAR2(32767); -- Cursor für Rückgabedaten CURSOR c_XSLT IS SELECT X_XSLT FROM "SCOTT"."XMLPAKET" xp INNER JOIN "SCOTT"."BERICHT_ZU_XMLPAKET" bzx ON xp."X_NR" = bzx."X_NR" INNER JOIN "SCOTT"."BERICHT" b ON b."B_NR" = bzx."B_NR" WHERE b."B_NR" = berichtsnr; BEGIN -- Dokumentbeginn v_XSLT := '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>'; -- Auslesen des Cursors FOR x_daten IN c_XSLT LOOP v_XSLT := v_XSLT || x_daten.x_xslt.getClobVal(); END LOOP; -- Dokumentschluss v_XSLT := v_XSLT || '</xsl:stylesheet>'; -- Rückgabe RETURN XMLType(v_XSLT); END getXSLT; XSLT zusammensetzen Datenbankeinsatz XSLT zusammensetzen Datenbankeinsatz -- Tabelle XMLDATEN DROP TABLE "SCOTT"."XMLDATEN"; CREATE TABLE "SCOTT"."XMLDATEN" ( "XD_NR" NUMBER(10) NOT NULL, "XD_TEXT" "SYS"."XMLTYPE" NOT NULL, CONSTRAINT "XD_Schluessel" PRIMARY KEY("XD_NR")); -- Tabelle XMLDATEN DROP TABLE "SCOTT"."XMLDATEN_ZU_BERICHT"; CREATE TABLE "SCOTT"."XMLDATEN_ZU_BERICHT" ( "XZB_NR" NUMBER(10) NOT NULL, "XD_NR" NUMBER(10) NOT NULL, "B_NR" NUMBER(10) NOT NULL, CONSTRAINT "XZB_Schluessel" PRIMARY KEY("XZB_NR")); XSLT zusammensetzen Datenbankeinsatz INSERT INTO "SCOTT"."XMLDATEN" VALUES (1, XMLType('<Kundenliste> <Kunde Nr="235" Anrede="Frau" Beginn="04.10.03"> <Name> <Rufname>Verena</Rufname> <Zuname>Fiegert</Zuname> </Name> ... </Kundenliste>')); XSLT zusammensetzen Datenbankeinsatz Fragen...