vishia - XslHints

vishia - XslHints

Inhalt


Topic:.XslHints.

pStyle=std tableStyle=stdTable

In folgender Aufstellung gibt es einige Hinweise, die für die XSL-Konvertierung gegebenenfalls wichtig sein könnten und in der Grundlagenliteratur oft nicht genügend beschrieben sind. Diese Aufstellung ist aus Eigeninteresse begonnen worden, weil oft Probleme schonmal erkannt und gelöst worden sind, die nach einem Jahr oder kürzer aber wieder in Vergessenheit geraten und neu entdeckt werden müssen. Anderen Lesern kann diese Aufstellung einige Tips geben. Verwendet wurde in neuerer Zeit ausschließlich SAXON als XSLT-Translator http:\www.saxonica.com.


1 XPATH

Topic:.XslHints..

pStyle=std tableStyle=stdTable

.


1.1 Unterschied zwischen <xsl:apply-templates> und <xsl:apply-templates select="*">

Topic:.XslHints...

pStyle=std tableStyle=stdTable

Der Ausdruck <xsl:apply-templates> entspricht <xsl:apply-templates select="text()|child::*">. Damit wird auch der Text innerhalb des aktuellen Elementes berücksichtigt. Dagegen ist <xsl:apply-templates select="*"> eine Kurzform für <xsl:apply-templates select="child::*"> und selektiert nur Kind-Elemente, nicht aber Textknoten im aktuellen Element.


1.2 Unterschied select="any/xpath/*" und select="any/xpath"

Topic:.XslHints...

pStyle=std tableStyle=stdTable

Das /* am Ende adressiert alle Childelemente unterhalb xpath, nicht aber die unter xpath stehenden Textknoten. Ohne /* wird dagegen der Knoten any/xpath selbst adressiert, identisch mit select="any/xpath/.".

Um sowohl Kindelemente als auch Textknoten zu selektieren, kann man schreiben.

<xsl:for-each select="any/xpath">
  <xsl:apply-template select="text()|*" />
  <xsl:for-each select="text()|*><xsl:call-template name="somewhat" /></xsl:for-each>
</xsl:for-each>

Mit diesem Kontstrukt wird vom Element any/xpath sowohl textuelle Knoten als auch child-Elemente adressiert, nicht aber der Knoten selbst. Schreibt man dagegen

<xsl:apply-template select="any/xpath" />

dann erwischt man den Knoten any/xpath selbst auch mit, wendet also seine Translation an. Das ist dann egal, wenn es für diesen Knoten das folgende Template gibt:

<xsl:template match="xpath"><xsl:apply-templates /></xsl:template>

Nicht egal ist es jedoch, wenn in der Translation etwas zusätzliches erzeugt wird.

Schreibt man

 <xsl:apply-template select="any/xpath/*" />

dann erwischt man Textknoten unterhalb any/xpath nicht mit, sondern nur Kind-Elemente. Damit ist die obige Schreibweise die einzig richtige.


1.3 Erkennung des Vorhandenseins von Elementen, boolean() vs. count()>0

Topic:.XslHints...

pStyle=std tableStyle=stdTable

Es gibt zwei Möglichkeiten, innerhalb von <xsl:if test="..."> oder <xsl:when test="..."> abzufragen, ob ein Element vorhanden ist:

 <xsl:if test="count(xmlPath/toElement)>0">

oder

 <xsl:if test="boolean(xmlPath/toElement)">

Ersteres sieht logischer aus, weil das Vorhandensein (Anzahl, XPATH-Funktion count()) direkt aufgerufen wird. Letzeres sieht wie ein Schnellschuss aus. Die XPATH-Funktion boolean() wandelt ein beliebiges Argument in die Aussage true oder false um, hier also die Tatsache der Selektierbarkeit.

Grundsätzlich sollte es so sein, dass eine nachfolgende Selektion etwa mit

 <xsl:for-each select="xmlPath/toElement">

genau dann keine null-Menge selektiert, wenn die vorige Abfrage nach dem Vorhandensein true liefert.

Festgestellt wurde aber, dass mit einer Java-Version im 1.4-Bereich mit Xerxes mit der count()-Variante das auch funktioniert, mit einer höheren Java-Version wurde aber offensichtlich bei einer Abfrage mit der Positionsangabe following-sibling

 <xsl:when test="count(following-sibling::*[1])>0">

mit true bewertet, sichtbar an der Nichtabarbeitung anderer, insbesondere des <xsl:otherwise>-Blockes. Dennoch war kein nachfolgnedes Element vorhanden, die folgende Selektion

 <xsl:for-each select="following-sibling::*[1]">

brachte keinen Eintritt. Der Fehler war nicht vorhanden, wenn abgefragt wurde:

 <xsl:when test="boolean(following-sibling::*[1])">

Woran das genau liegt ist ungeklärt. Jedenfalls scheint boolean() die bessere wahl zu sein um abzufragen, ob ein Element existiert.


1.4 Arbeit mit following-sibling

Topic:.XslHints...

pStyle=std tableStyle=stdTable

following-sibling ist eine Achsenbezeichnung. Schreibt man

 following-sibling

dann ist das gleichbedeutend mit

 following-sibling::*

und wählt alle folgenden Elemente der gleichen Ebene aus. Die Auswahl kann eingeschränkt werden:

 following-sibling::tag[1]

wählt nur den unmittelbaren Nachfolger aus, der vom <tag> ist.

Konstrukte mit following-sibling sind insbesondere günstig für Rekursionen oder für die Abfrage, ob noch ein weiteres Element folgt, um Trennzeichen oder dergleichen zu erzeugen. Beispiel:

 <xsl:template name="ExampleRecursion">
   <xsl:for each select="following-sibling::tag[1]">
     <xsl:call-template name="ExampleRecursion">
   </xsl:for-each>
 </xsl:template>
,
 <xsl:template match="something">
   <xsl:for each select="tree/tag[1]">
     <xsl:call-template name="ExampleRecursion"><!-- first recursion -->
   </xsl:for-each>
   <!-- ... -->
   <xsl:for-each select="otherthing">
     <xsl:text>write somewhat</xsl:text>
     <xsl:if test="boolean(following-sibling::otherthing[1])">
       <xsl:text>,</xsl:text> <!-- separator between write somewhat, but not on end. -->
     </xsl:if>
   </xsl:for-each>
 </xsl:template>

Es gibt allerdings noch eine elegantere Möglichkeit zur Erkennung, ob ein Element das Letzte einer Kette ist, siehe XslHints_lastForEach.


1.5 Sortierte Abarbeitung, doppelte Elemente erkennen mit following-sibling

Topic:.XslHints...

pStyle=std tableStyle=stdTable

Folgendes Beispiel ist mit SAXON getestet und geht nur mit XSLT 2.0.

Aufgabenstellung: In einem XML-Baum sind verstreut und unsortiert verschiedene Elemente enthalten, dabei auch sich gleichende Elemente an verschiedener Stelle. Von den gleichen Elementen soll jeweils nur ein Exemplar in einen Ausgabebaum kopiert werden. In einem konkreten Fall ging es um Typangaben zu Variablen und Funktionsargumenten in einem Headerfile. Es gibt verschiedene Variable und Funktionsargumente vom gleichen Typ, die Typen sollen aufgelistet werden, aber jeder nur einmal.

Um die Typen ausfindig zu machen, kann man schreiben:

 <xsl:variable name="sortedTypes">
   <xsl:for-each select="//*[boolean(type)]">
     <xsl:sort select="type/@name" />
     <usage test="2" type="{type/@name}" tag="{local-name()}" name="{@name}"/>
   </xsl:for-each>
 </xsl:variable>

Die gefundenen Xml-Nodes werden in eine Xslt-Variable geschrieben. Im Beispiel werden dabei alle Elemente selektiert, die ein Child <type> enthalten. Die Nodes sind in der Variable nach dem Typ-Namen sortiert.

In einem zweiten Durchlauf wird jetzt der Inhalt der Xslt-Variable evaluiert. Da die Elemente in der Variable sortiert sind, können auf einfache Weise gleiche Elemente erkannt werden: Es werden nur die Elemente selektiert, deren direkter Nachfolger nicht den gleichen Namen hat. Dabei wird also jeweils das letzte Element einer Folge von Elementen mit gleichen Namen erwischt. Wichtig ist die Abfrage position()=last(), das wurde zuerst vergessen und das letzte Element fehlte zunächst unbemerkt. Das liegt daran, dass ein Vergleich mit dem nicht mehr vorhandenen following-sibling::usage[1] nach dem lezten Element ebenfalls false liefert, für das lezte Element also die weitere Bedingung notwendig ist.

 <xsl:for-each select="$sortedTypes/usage[@type != following-sibling::usage[1]/@type or position()=last()]">
   <type name="{@type}" />
 </xsl:for-each>

Der Versuch, in der ersten <xsl:for-each...-Reihe beide Aufgaben gleichzeitig zu erledigen, in dem entweder über den Zugriff auf das vorige oder folgende Element der Nodelist die Gleichheit des Namens getestet wird, oder der Name in einer Variable zum Test des folgenden Durchlaufs gespeichert wird, scheiterte: Der Zugriff auf eine andere Node einer Nodelist der <xsl:for-each...-Reihe ist in XSLT nicht vorgesehen. Um den jeweils letzten Typnamen zwecks Vergleich in einer Variable zu speichern, benötigt man die SAXON-Erweiterung <saxon:assign.... Hier hat aber das Detail versagt, es funktionierte nicht. Die Verwendung von <saxon:assign... ist in der aktuellen SAXON-Dokumentation (Version 8.9) als deprecated bezeichnet, soll also auch nicht für Neuentwicklungen verwendet werden.

Der Trick mit dem Zwischenparken der sortierten Gesamtauswahl in einer Xslt-Variablen und Selektion der nicht-Doppelung in der folgenden <xsl:for-each...-Anweisung ist also zielführend, sollte verarbeitungszeitoptimal sein und ist auch zukunftssicher.


1.6 Trennzeichen zwischen Elementen einer <xsl:for-each>-Reihe, aber nicht nach dem letzten

Topic:.XslHints..lastForEach.

pStyle=std tableStyle=stdTable

 <xsl:for-each select="elements">
   <xsl:text>write somewhat</xsl:text>
   <xsl:if test="last() > position()">
     <xsl:text>,</xsl:text> <!-- separator between write somewhat, but not on end. -->
   </xsl:if>
 </xsl:for-each>

Mit der Abfrage <xsl:if test="last()>position()"> bekommt man sehr leicht heraus, ob das aktuell behandelte Element in einer <xsl:for-each>-Liste das letzte ist oder nicht. "position()<last()" kann man leider nicht schreiben, sondern stattdessen "postion()&lt;last()", weil das < Zeichen vom XML-Parser als Fehler erkannt wird. daher ist die obige Schreibweise besser geeignet.


2 Variablen-Nutzung

Topic:.XslHints..

pStyle=std tableStyle=stdTable

.


2.1 Nur konstante Variablen - Rekursionen

Topic:.XslHints...

pStyle=std tableStyle=stdTable

Variablen sind in XSLT gemäß dem Standard nach dem Anlegen nicht mehr veränderbar. Zweifel daran, ob es überhaupt Variablen wären, Laut http://www.de.wikipedia.org/wiki/Variable_(Philosophie) wird aber von einer Variable gesagt, sie ist eine Größe, die verschiedene Werte annehmen kann. Dass diese Werte auch wärend der Abarbeitung geändern werden können, ist nicht notwendige Voraussetzung für diesen Begriff. Die Nichtänderbarkeit von Variablen in XSLT entspricht dem Prinzip der funktionalen Programmierung (==>wikipedia). Dieses Paradigma soll nicht durchbrochen werden, Erweiterungen von XSLT, um Variablen ändern zu können, beispielsweise in SAXON in älteren Versionen als add-on angeboten wurden mittlerweile vom Entwickler Michael Kay als deprecated zurückgenommen. Die Begründung ist unter anderem auch damit gegeben, dass ein XSLT-Translator nach einer Vorübersetzung (Compilierung) besser optimieren kann, wenn das Funktionale Programmierparadigma eingehalten wurde: Bei einer einmal konstant festgelegten Variable ist die Variable entweder vorhanden und dann mit einem festen Wert besetzt oder sie wird angelegt. Bei Variablen, deren Wert sich ändern kann, ist dagegen der Fortschritt der Abarbeitung eine für Optimierungen zusätzlich zu beachtende Größe, die komplexer Natur ist. Daher sollte man sich in XSLT daran gewöhnen.

In der Funktionalen Programmierung wird oft mit Rekursionen gearbeitet, um adäquate Probleme zu lösen wie in der Imperativen Programmierung mit Schleifen.

Beispiel in Java: Um Zahlen zu addieren, wird im Imperativen Ansatz eine Schleife verwendet:

 float calculate()
 { float summe=0;
   for(int idx=0; idx<10; idx++)
   { summe = summe + data[idx];
   }
   return summe;
 }

Um das gleiche nach dem Funktionalen Paradigma zu lösen, kann man rekursiv arbeiten:

 float calculate(float summeBefore, int idx)
 { if(idx >=10) return summeBefore;
   else return calculate(summeBefore + data[idx], idx+1);
 }

Besser als mit dem einfachen Java-Beispiel eines indizierten Zugriffes auf ein array ist die Rekursivität mit Iteratoren les- und schreibbar. Das Adäquatbeispiel wäre:

 float calculate(float summeBefore, Iterator iter)
 { if(iter.hasNext()) return calculate(summeBefore + iter.next().getValue(), iter);
   else return summeBefore;
 }

Der Iterator drückt besser und eindeutiger aus, ob es einen Nachfolger gibt oder nicht.

Mit dem rekursivem Blick sind diese Lösungen einfacher lesbar als die for-Schleife: Wenn es noch etwaszu addieren gibt, wird dies getan und die Funktion rekursiv gerufen, ansonsten wird rückgekehrt. Anzumerken ist auch hier, das laufzeitmäßig ein Compiler optimieren kann, wenn das Funktionale Programmierparadigma verstanden wird.

Entsprechend ist in XSLT zu arbeiten:

 <xsl:template name="calculate">
 <xsl:param name="summeBefore" select="0" />
   <xsl:if test="not(boolean(following-sibling::element[1]))">
     <xsl:value-of select="summeBefore" />
   </xsl:if>
   <xsl:for-each select="following-sibling::element[1]" >
     <xsl:call-template name="calculate">
       <xsl:with-param name="summeBefore" select="number($summeBefore + @value)" />
     </xsl:call-template>
   </xsl:for-each>
 </xsl:template>

Der Aufruf des benannten Templates (der Subroutine in XSLT) erfolgt mit Select des ersten Elementes:

 <output>
   <xsl:for-each select="pathTo/element[1]">
     <xsl:call-template name="calculate" />
   </xsl:for-each>
 </output>

Die berechnete Summe wird hier in das output-Element <output> als Textinhalt geschrieben, weil der letzte Aufruf der Rekursion mit <xsl:value-of select="summeBefore" /> einen output erzeugt.

(TODO: Die Beispiele sind nicht schreibfehlergetestet.)


2.2 Kopieren eines Xml-Segments in eine Variable, Zugriff darauf

Topic:.XslHints...

pStyle=std tableStyle=stdTable

 <xsl:variable name="xmltree"><xsl:copy-of select="a/b" /></xsl:variable>
 <xsl:for-each select="$xmltree"><xsl:value-of select="c/d" /></xsl:for-each>

In der ersten Zeile wird ein Baumsegment in eine Variable kopiert. Vermutlich kopiert der XSL-Translator intern dabei nur eine Referenz auf den vorhandenen Baum, so dass dies keine aufwändige Operation ist.

In der zweiten Zeile beziehen sich aufgrund der <xsl:for-each ... - Auswahl der ... "$xmltree" - Variable alle Selektionen innerhalb des <xsl:for-each>-Blockes auf das Baumsegment in der Variable.

DAS GEHT ABER NICHT????? mit Xalan! Geht dies mit SAXON? TODO Test! SAXON und XSLT 2.0 kann dies prinzipiell.

Eine Auswahl nach dem Stil

 <xsl:value-of select="$xmltree/c/d" />

wäre intuitiv zu erwarten, funktioniert aber nicht unter Xalan und XSLT 1.0, wohl aber unter SAXON-8 mit XSLT 2.0!


3 Schreibweise bei Attributangaben

Topic:.XslHints.attrib.

pStyle=std tableStyle=stdTable

Zu unterscheiden sind grundsätzlich die Attributangaben von <xsl:tag ...> und die Attributeangaben bei erzeugten Output-Xml-Tags.


3.1 Attributangaben bei <xsl:...>

Topic:.XslHints.attrib.xslAttrib.

pStyle=std tableStyle=stdTable

Bei den Attributangaben von <xsl:tag ...> dürfte alles klar sein:

In den meisten Attributen beispielsweise bei <xsl:for-each select="xxx"> steht immer ein XPATH-Ausdruck, der zwecks Abarbeitung aufgelöst wird. Also beispielsweise:

 <xsl:for-each select="inputTag">

dann ist inputTag ein erwartetes XML-Element im aktuellen Knoten.

Will man in einem solchen Attribut eine Textkonstante haben, dann muss man schreiben, gezeigt an einem typischen Beispiel:

 <xsl:with param name="paramName" select="'constantValue'" />

Hier ist auch gleichzeitig die Ausnahme gezeigt: Da name="..." immer eine Konstante sein muss, wird hier kein XPATH-Ausdruck erwartet sondern eben ein einfacher Text als Konstante. Aber bei select"..." wird ein XPATH erwartet. Die Schreibweise 'constantValue' ist ein XPATH-Ausdruck, der als Ergebnis constantValue liefert.

Noch ein kleiner Hinweis: XPATH-Ausdrücke können auch Funktionen sein, die dann immer mit einem der definierten Funktionsnamen anfangen und meist Parameter in (...) haben. Die Parameter werden ebenfalls meist als XPATH-Ausdruck erwartet, alles schön in der XML-Spezifikation und diversen Nachschlagewerken beschrieben. Also beispielsweise

 <xsl:with-param name="px" select="substring-before(inputTag,':')" />

3.2 Attributangabe bei erzeugten Tags für den Output-XML-File

Topic:.XslHints.attrib..

pStyle=std tableStyle=stdTable

Hier wird im einfachen Fall genau der Text erzeugt, der darinnen steht. Also kein XPATH aufgelöst. Ein Beispiel aus der Erzeugung eines ANT.XML-Files:

 <target name="MyTarget" >

Das wird genauso in den Output geschrieben. Um in ein Attribut variable Werte hineinzubekommen, gibt es zwei Möglichkeiten:

 <target>
 <xsl:attribute name="name"><xsl:value-of select="inputTag/@ident"></xsl:attribute>
 ...
 </target>

Die erste Möglichkeit ist die gezeigte <xsl:attribute ... Anweisung. Wichtig ist, dass diese Attributanweisung am Anfang steht, sonst gibts Fehlermeldungen bei Ablauf des XSL-Scripts.

Eine andere Möglichkeit für das selbe Beispiel ist:

 <target name="{inputTag/@ident}">

Hier wird eine Spezialfunktionalität des XSL genutzt: Wenn eine { im sonst erwarteten konstantem Text steht, dann wird die Zeichenkette bis zur } als XPATH aufgelöst. Das geht sogar mehrfach oder mit Mischung mit normalem Text, wie ein weiteres Praxisbeispiel zeigt:

  <arg line ="document={@ident}"/>

Hier wird @ident als XPATH expandiert, also der Wert aus dem Attribut ident der aktuellen XML-Input-Node geholt. Enthält die aktuelle Inputnode beispielsweise

 <xmltag ident="MyDocument">

dann wird also

 <arg line="document=MyDocument">

als XML-Output erzeugt.

Aber was ist, wenn man aber eine { selbst im XML-Output erzeugen will, tritt häufig auf wenn man ANT.XML-Files erzeugt? Schreibt man ein { direkt, dann wird das nicht ausgegeben sondern statt dessen wie oben beschrieben verfahren. Man muss {'{'} schreiben!

Was soll das: Das erste { leitet einen XPATH ein. Der XPATH besteht aus '{', was eine konstante Zeichenkette beschreibt, die letzlich das gewünschte { darstellt. Dann muss man noch mit } abschließen.

Ein Beispiel:

 <arg line ="-o {'${tmp}'}/{@ident}.genctrl.xsl" />

Dadurch dass ${tmp} als Konstante innerhalb des Path-Ausdruckes steht, wird das { nun wirklich im XML-Output erzeugt:

 <arg line ="-o ${tmp}/IDENT.genctrl.xsl" />

wobei IDENT aus dem Wert des Attributes ident der XML-Inputnode gelesen wird. Die Schreibweise ${tmp} bedeutet im ANT.XML: Nimm beim Ausführen von ANT den Wert der <property name="tmp" ...> was im konkreten meist der tmp-Path ist.

Soll eine { und dazwischen ein variabler Wert aus dem XML-Input stehen, dann muss man nun wirklich schreiben:

 <tag attr="${'{'}{XPATH}.ext{'}'}">

Kompliziert, aber eigentlich logisch. .ext ist im Beispiel dann ein konstanter Text. Vielleicht muss man nicht {'}'} schreiben, bei der schließenden Klammer reicht }. Aber es sieht so symmetrischer aus.

Übrigens: Will man ein < erzeugen, so muss man &lt; schreiben, adäquat für & ein &amp;. Für > kann man &gt;. Das gilt allgemein bei XML.


4 Zugriff auf Elemente mit Schlüsselwert, key()

Topic:.XslHints..

pStyle=std tableStyle=stdTable

.


4.1 <xsl:key>: Index erstellen und mit key(name, value) benutzen

Topic:.XslHints...

pStyle=std tableStyle=stdTable

In XSLT existiert die Möglichkeit, für verschiedene Schlüssel einen Index zu erstellen. Index meint hier eine sortierte Listen wie ein Stichwortverzeichnis (Index) in einem Buch.

Folgende Anweisung muss am Anfang eines XSLT-Scripts stehen und führt zum Erstellen des Index (Beispiel);

 <xsl:key name="id" use="@xmi.id" match="UML:Package|UML:Class|UML:Interface"/>

Für alle angegebenen Elemente match=..., hier UML:Package usw. wird ein Schlüsselwert aus dem angegebenen Element use=..., hier das Attribut xmi.id, entnommen und ein Index mit dem angegebenem Namen name=..., hier id gebildet. Auf diesen Schlüssel kann nun zugegriffen werden, einfach mit der XPATH-Funktion

 key('id',@value)

Das erste Argument ist die Bezeichnung des Schlüssels, hier id wie oben bei <xsk:key name=... angegeben. Das zweite Argument ist der Wert des Schlüssels, nach dem gesucht wird. Die XPATH-Funktion key liefert das jenige Element zurück, welches laut dem Index dem Schlüssel entspricht.

TODO: Was ist bei nicht eindeutigen Schlüsseln? Was ist wenn das Element nicht existiert. testen! Passiert nicht bei einem ordentlichen XMI-File.

Ein wichtiges Anwendungsbeispiel ist XMI. Jedes Element hat einen eindeutig generierten Ident-String, jeweils als Attribut xmi.id. Der Verweis auf Elemente, beispielsweise von einer Assoziation auf die zugehörigen End-classes, wird über diesen Ident-String realisiert. Ein XMI-Datensatz ist voll dieser Verweise. Es gibt eher weniger parent-child-Relationen.


4.2 generate-id() - eindeutigen Schlüssel generieren

Topic:.XslHints...

pStyle=std tableStyle=stdTable

Dies ist das Gegenstück zur Nutzung von Schlüsseln <xsl:key...> und key(name, value). Hier wird ein Schlüssel generiert. Die XPATH-Funktion liefert eine Zeichenkette, die innerhalb des XSLT-Laufes eindeutig ist.

Um diesen Identstring auch nutzen zu können, werden Ergebnisse zumeist in <xsl:variable> gepackt um dann entsprechenden output-Elementen als Quell-Kennzeichnung (im XMI als Attribut xmi.id) oder Ziel-Kennzeichnung (um auf ein gekennzeichnetes Element zu verweisen) zugewiesen zu werden.

 <xsl:variable name="attribId" select="generate-id()" />
 <UML:Attribute name="{@name}" xmi.id="{$attribId}" ...
    ...
    <UML:TaggedValue xmi.id="{generate-id()}" modelElement="{$attribId}"/>

5 Konstante Texte, Zeichencodierungen

Topic:.XslHints..

pStyle=std tableStyle=stdTable

Ein paar Anmerkungen:


5.1 Leerzeichen in der Ausgabe produzieren

Topic:.XslHints...

pStyle=std tableStyle=stdTable

Eine kleine Besonderheit:


5.2 Bullets

Topic:.XslHints...

pStyle=std tableStyle=stdTable

<xsl:variable name="ListBullet1"><xsl:text>&#x2022;</xsl:text></xsl:variable>

 <xsl:variable name="ListBullet2"><xsl:text>&#x25e6;</xsl:text></xsl:variable>
 <xsl:variable name="ListBullet3"><xsl:text>&#x25a0;</xsl:text></xsl:variable>
 <xsl:variable name="ListBullet4"><xsl:text>&#x25a1;</xsl:text></xsl:variable>

Das sind die UTF-Codes der 4 üblichen Bullets für Listendarstellung. Verwendet in XmlDocu_xsl/Pre2Word.xsl. (Für HTML-Ausgabe braucht man so etwas meist nicht, da das Bullet für Listenanstriche vom Browser selbst generiert wird.)