Stringverarbeitung

(Auszug aus "XSLT 2.0 & XPath 2.0" von Frank Bongers, Kapitel 3.)

Stringwerte von Knoten sind schon in einigen Zusammenhängen vorgekom­men, z. B. um sie ins Ergebnisdokument auszugeben, als Gegenstand von xsl:if-Tests oder Ähnliches. Sie können die Behandlung von Strings in vielen Fällen dem Prozessor überlassen, ohne sich Gedanken darüber zu machen.

Oft ist es aber interessant, die Länge eines Strings festzustellen, zu prüfen, ob er eine bestimmte Zeichenfolge enthält oder mit ihr beginnt. Man will aus Strings bestimmte Zeichen vor einer Weiterverarbeitung entfernen oder Teilstrings extrahieren.

Dies alles (und noch viel mehr) ist mit den XSLT-Funktionen zur String­­manipulation möglich. Sie bilden neben Variablen und benannten Templates ein weiteres Werkzeug zur rekursiven Verarbeitung von Informationen. XPath 2.0 stellt eine beinahe unübersichtlich zu nennende Menge von Stringfunktio­nen zur Verfügung, von denen einige mit regulären Ausdrücken umgehen kön­nen. Die Funktionen fn:contains(), fn:ends-with() und fn:starts-with() sowie die RegEx-Stringfunktion fn:matches() wurden bereits im letzten Abschnitt vor­gestellt.

Die XPath-Spezifikation stellt noch eine weitere Funktion zur Verfügung, die beliebige Eingabewerte in Strings umwan­delt: fn:string(). Diese Funktion wird jedoch nicht wirklich zu den String­funktionen gezählt, da sie nicht mit Strings arbeitet, sondern Stringwerte erzeugt.

Folgende weitere XPath-Funktionen zur Stringmanipulation existieren:

Stringfunktionen in XPath 2.0:
fn:compare(), fn:concat(), fn:escape-uri(), fn:lower-case(), fn:normalize-space(), fn:normalize-unicode(), fn:string-length(), fn:string-join(), fn:substring(), fn:substring-after(), fn:substring-before(), fn:translate(), fn:upper-case()

Dazu kommen noch zwei weitere RegEx-Stringfunktionen:
fn:replace(), fn:tokenize()

Eine genauere Diskussion dieser Funktionen findet im Referenzteil zu den Funktionen von XPath und XSLT statt (Kapitel 5, Referenz XSLT- und XPath-Funktionen), hier soll nur auf einige Funk­tionen exemplarisch eingegangen werden.

Die Funktion fn:string-length() zählt die Anzahl der Zeichen eines ihr überge­benen Strings. Falls kein Argument übergeben wird, gibt sie die Zeichenzahl des Stringwerts des gerade gültigen Kontextitems zurück.

Bemerkenswert in diesem Zusammenhang ist allerdings, dass auch Zeichen, die im Dokumentquelltext durch Character Entities – E – oder XML-Entity-Referenzen – & – vertreten sind, als einzelnes Zeichen gezählt werden, also in ihrer »expandierten« Form. Der XSLT-Prozessor hat nämlich auf den Quelltext gar keinen Zugriff; zum Zeitpunkt des Aufrufs der Stringfunktion ist das Doku­ment bereits durch den XML-Parser gegangen, und statt der Entity-Referenz steht tatsächlich nur ein einzelnes Zeichen da. Dies gilt analog für geparste Tex­tentities – bei diesen werden die Zeichen des Expansionstextes gezählt.

Die Funktion fn:substring() entnimmt aus einem Eingabestring einen Teilstring mit definiertem Anfang und definierbarer Länge. Vernünftigerweise sollte der Anfang des Substrings durch eine ganze, positive Zahl angegeben werden. Das durch den Startindex bezifferte Zeichen ist Teil des Ausgabestrings (also nicht 'ab', sondern 'einschließlich'!).

Die Funktion fn:substring-after() gibt denjenigen Teil eines Eingabestrings zurück, der dem ersten Vorkommen eines Teststrings im Eingabestring folgt. Der Teststring kann aus einem einzelnen Zeichen oder einer Zeichenkette bestehen. Ist der Teststring nicht im Eingabestring enthalten, oder ist der Ein­gabestring leer, so wird ein leerer String zurückgegeben.

Die Funktion fn:substring-before() gibt denjenigen Teil eines Eingabestrings zurück, der vor dem ersten Vorkommen eines Teststrings im Eingabestring auf­tritt. Der Teststring kann aus einem einzelnen Zeichen oder einer Zeichenkette bestehen.

Ist der Teststring nicht im Eingabestring enthalten, oder sind Eingabestring oder Teststring leer, so wird ein leerer String zurückgegeben.

Die Funktion fn:translate() kann beliebige Zeichen in einem übergebenen String durch beliebige andere Zeichen ersetzen. Das Vorgehen ist dabei, dass ein in der Vergleichsliste gefundenes Zeichen aus dem Ursprungsstring durch das – vom Index mit der Position in der Vergleichsliste korrespondierende – Zeichen aus dem String der Ersetzungsliste ersetzt wird.

Rekursiver Templateaufruf vs. regulärer Ausdruck

In XSLT 1.0 mussten für viele kleine Routinevorgänge komplexe, rekursive Templates eingesetzt werden. In XSLT 2.0 ist dies zwar immer noch möglich, jedoch nicht mehr in jedem Fall unumgänglich. Hier sollen zwei Lösungs­ansätze für das Zählen von Worten in einem Fließtext verglichen werden. Der erste Ansatz arbeitet, wie in XSLT 1.0 erforderlich, mit einem benanntem Template, das sich selbst wieder aufruft und dabei durch den Text zählt. Der zweite erledigt elegant das Gleiche »im Handumdrehen« mit einer XPath 2.0-RegEx-Stringfunktion.

Der rekursive Ansatz mit XSLT 1.0 und XPath 1.0

Hier zunächst ein Worte zählende Template in zu XSLT 1.0 kompatibler Formulierung. Das Präfix fn vor den Funktionen wurde hier deshalb weggelassen.

<!-- Zahl der Worte im Stringvalue des current node: -->
<xsl:template name="wortezaehlen">
  <!-- Parameter nimmt Text von außen entgegen: -->
  <xsl:param name="text" select=""/>
  <!-- Zählparameter speichert Zählerstand: -->
  <xsl:param name="zahl" select="1"/>
  <!-- die Eingabe normalisieren: -->
  <xsl:variable name="text_n" select="concat(normalize-space($text),' ')"/>
  <!-- erstes Wort abtrennen, mit dem Rest weiterarbeiten: -->
  <xsl:variable name="rest" select="substring-after($text_n,' ')"/>
  <xsl:choose>
    <!-- wenn es einen Rest gibt: -->
    <xsl:when test="$rest">
      <!-- den Zähler um eins hochzählen: -->
      <xsl:variable name="zaehler" select="$zahl + 1"/>
      <!-- und das Template neu aufrufen: -->
      <xsl:call-template name="wortezaehlen">
         <xsl:with-param name="text" select="$rest"/>
         <xsl:with-param name="zahl" select="$zaehler"/>
      </xsl:call-template>
    </xsl:when>
    <!-- wenn nichts übrig ist: -->
    <xsl:otherwise>
      <!-- letzten Zählerstand ausgeben: -->
      <xsl:value-of select="$zahl"/>
      <!-- ... quasi der Rückgabewert des Templates! -->
    </xsl:otherwise>
  </xsl:choose>
<!-- Ende des Zähltemplates -->
</xsl:template>

Code-Beispiel: kap03/3.04/wortezaehlen.xsl (Auszug).

Dieses Template wird zunächst unter Übergabe des Stringwerts des Kontext­knotens gestartet, wobei auch ein Zähler initialisiert wird. Innerhalb des Tem­plates erfolgt eine recht komplexe Stringverarbeitung, die den Text mit Hilfe von substring-after() Wort für Wort abbaut. Solange noch ein Rest übrig ist, wird dieser dem Template beim rekursiven Aufruf wieder übergeben und der ebenfalls übergebene Zählerstand hochgesetzt. Ist der Rest verschwunden, so ist das letzte Wort gezählt, und der Zählerstand wird ausgegeben.

Als Studienobjekt mag ein Template dieser Art sehr wohl noch dienen; Sie sehen hier sehr schön die Verwendung von Zwischenvariablen und die Para­meterübergabe sowie den bedingten rekursiven Aufruf. Auch unter XSLT 2.0 ist dieses Template lauffähig. Dass es nicht mehr zeitgemäß ist, beweist ein Blick auf die Alternative, die eine in XPath 2.0 zur Funktions­sammlung hinzu­gekommene RegEx-Stringfunktion einsetzt: fn:tokenize().

Der Ansatz mit XSLT 2.0 und XPath 2.0

Der Unterschied in der Quelltextlänge ist geradezu »entlarvend«:

 <!-- Worte im Stringvalue des current node -->
 <xsl:template name="wortezaehlen">
   <xsl:param name="text" select=""/>
   <xsl:value-of select="fn:count(fn:tokenize(fn:normalize-space($text), ' '))"/>
 <!-- Ende des Zähltemplates -->
 </xsl:template>
  

Code-Beispiel: kap03/3.04/wortezaehlen2.xsl (Auszug).

Das Prinzip ist vom Ansatz her ähnlich, von der Durchführung her allerdings völlig anders. Es handelt sich ebenfalls um ein benanntes Template, das den Stringwert des aktuellen Knotens als Parameter übergeben bekommt und die­sen auswertet. Hier hören die Ähnlichkeiten auf. Es handelt sich nicht um ein rekursiv arbeitendes Template, sondern die ganze Arbeit wird in eine RegEx-Stringfunktion verlagert, die den normalisierten Text übergeben bekommt.

Die Funktion tokenize() spaltet den Text an den Wortgrenzen (als zweites Argument wird die Zeichenkette übergeben, nach der gesucht wird – hier das Leerzeichen). Es liegt nun eine Sequenz aus allen erkannten Worten vor, die durch die Funktion count() gezählt und deren Zahl unmittelbar ausgegeben werden kann.

Die Notwendigkeit, die Funktionalität in ein eigenes Template auszulagern, besteht nicht mehr unbedingt, im Fall des sich selbst wiederholt aufrufenden Templates war dies unabdingbar.

Noch interessanter wird der Vergleich jedoch, wenn man die Verarbeitungs­zeit in Erfahrung bringt. Saxon gibt entsprechende Informationen nach Ein­gabe der Option -t heraus, diese liegt für das RegEx-Template bei 16 ms, für das rekursive Template bei 32 ms. Es ist daher erwägenswert, ältere Style­­sheets unter Berücksichtigung der Möglichkeiten von XPath 2.0 zu modernisieren, auch wenn die alte Herangehensweise unter XSLT 2.0 funk­­tionstüchtig bleibt.

   

<< zurück vor >>
Tipp der data2type-Redaktion:
Zum Thema XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © Galileo Press, Bonn 2008
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "XSLT 2.0 & XPath 2.0 ― Das umfassende Handbuch" denselben Bestimmungen wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Galileo Press, Rheinwerkallee 4, 53227 Bonn