4.1.6  Identifikation mit generate-id()

Die generate-id() Funktion gibt es seit XSLT 1.0. Mit ihr kann eine Prüfsumme eines Knotens im Baum generiert werden.
Das funktioniert natürlich nur, wenn man bei der Auswertung dieses Wertes nicht den Kontext wechselt. D.h. z.B. dass ein Knoten in einem Baum, der in einer Variablen gespeichert ist, eine andere Prüfsumme bekommt, als derselbe Knoten im Kontext-Baum.

4.1.6.1  Beispiel Stückliste

Ein Anwendungszenario wäre bspw. die Generierung einer Target-ID für ein Bauteil in einer Stückliste. Das Bauteil ist nur einmal im System erfasst, hat also eine eindeutige ID, soll aber an mehreren Stellen in die Ausgabe (Eine Dokumentation für eine Maschine) generiert werden.
Die Id an einem Element <part id=“1234”> würde somit mehrfach in die XML Eingabe für einen XSL-FO Prozessor erscheinen und ist für Referenzen unbrauchbar geworden. Deshalb ist es ratsam beim Rendern der Bauteile eine neue Id zu vergeben, das kann z.B. mit den folgenden Templates (vereinfacht) passieren:
<xsl:key name="parts" match="part" use="@id"/>
  
<xsl:template match=“part” mode=“content">
  <!-- Ausgabe des Bauteils im Content Bereich -->
  <fo:block id="{generate-id()}">
    <fo:external-graphic xsl:use-attribute-sets="part.img"/>  
  </fo:block>
</xsl:template>
    
<xsl:template match=“part” mode=“part-list">
  <!-- Ausgabe einer Liste mit allen Verweisen an unterschiedicher Stelle -->
  <fo:block>
    <xsl:for-each select="key('parts',@id)">
      <fo:page-number-citation ref-id="{generate-id()}"/>
    </xsl:for-each>
  </fo:block>
</xsl:template>

4.1.6.2  Beispiel Mantel Dokument

Im Bereich EDI Datenaustausch werden große XML Dateien versendet, die man auf einzelne Transmissions aufsplitten will, um sie in einer XML Datenbank abspeichern zu können. Die Struktur einer Datenübertragung könnte folgendermassen aussehen:
WRAPPER1
  SEQUENZ1
  SEQUENZ2
  SEQUENZ3
  WRAPPER2
    SEQUENZ1
    SEQUENZ2
    SEQUENZ3
    SEQUENZ4
    WRAPPER3
      SEQUENZ1
      SEQUENZ2
      CONTENT
        DATA1
        DATA2
        DATA3
        DATA4
        DATA5
      CONTENT
        DATA1
        DATA2
        DATA3
        DATA4
        DATA5
      WRAPPER4
        SEQUENZ1
      CONTENT
        DATA1
        DATA2
        DATA3
        DATA4
        DATA5
      [...]
Jedes einzelne CONTENT Element soll nun einen Mantel erhalten und separat in einer Datei abgelegt werden. Der "Umschlag" soll dabei alle Elemente des Rahmens der Transmission erhalten. Das ist alles auf der Descendant-Achse bis zum Element WRAPPER3 , ausserdem noch die Elemente SEQUENZ1 und SEQUENZ2 , sowie das Element WRAPPER4 mit Kind SEQUENZ1 . Ohne groß auf die Performanz zu achten, könnte das recht einfach so realisiert werden:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    
  <xsl:output method="xml" indent="yes"/>
  <xsl:strip-space elements="*"/>
  
  <xsl:template match="/">
    <xsl:apply-templates select="/WRAPPER1/WRAPPER2/WRAPPER3/CONTENT" mode="umschlag"/>
  </xsl:template>
    
  <xsl:template match="CONTENT" mode="umschlag">
    <xsl:result-document href="{concat(@id,'.xml')}">
      <umschlag>
        <metadaten><!-- einige Metadaten --></env:metadata>
        <nutzdaten>
            <xsl:apply-templates select="ancestor::WRAPPER1">
              <xsl:with-param name="this-id" select="generate-id()" tunnel="yes"/>
            </xsl:apply-templates>
        </nutzdaten>
      </umschlag>
    </xsl:result-document>
  </xsl:template>

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>
    
  <xsl:template match="CONTENT">
    <xsl:param name="this-element" tunnel="yes"/>
    <xsl:if test="$this-id = generate-id()">
      <xsl:copy>
        <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
    </xsl:if>
  </xsl:template>
    
</xsl:stylesheet>
Im rekursiven Abstieg wird im Modus "umschlag" jedes CONTENT Element selektiert und in einen Umschlag verpackt. Der eigentlich Inhalt des Umschlags wird generiert, indem der gesamte XML Baum über die Standard-Kopierregel in das Element <nutzdaten> gesetzt wird. Dabei wird aber nur derjenige CONTENT Abschnitt evaluiert, der - zu der als Parameter übergebenen - generierten Id passt.

4.1.6.3  Verlinkung auf nächstes Verweisziel

Beispiel Publishing: In einer Publikation sind Grafiken vorhanden, auf die verlinkt werden soll. Das hört sich einfach an, jedoch gibt es auch Fälle, in denen ein solches Verweisziel mehrfach im Buch vorhanden ist.
Man sollte dann z.B. nicht auf dieselbe Grafik in Kapitel 1 verweisen, sondern besser auf die Grafik, die am nächsten liegt. Das kann in Blätterrichtung, aber auch entgegen der Blätterrichtung sein.
Abgesehen davon, dass es natürlich mehr Sinn macht, auf die Vorgänger-Grafik zu verweisen, weil der Leser ja beim Blättern diese schon begutachtet hat und nur zurückblättern muss (anstatt schnell vorzublättern und Inhalte zu überspringen), ist dieses Problem nicht ganz trivial:
1.

Das nächst gelegene Verweisziel findet man über die preceding und following XPath-Achse:

<xsl:variable name="nearest-preceding" 
              select="preceding::*[@id = current()][1]"/>
<xsl:variable name="nearest-following" 
              select="following::*[@id = current()][1]"/>
2.

Welches Verweisziel ist aber nun näher? Um diese Frage zu beantworten, müsste man irgendwie die Distanz zwischen den Knoten messen können.

3.

Wenn man öfters mit der preceding und following Achse zu tun hat, merkt man schnell, dass die häufige Selektion in diesen beide Achsen auf die Performanz geht. Wie kann man die Performanz hier optimieren?

Neben der generate-id() Funktion werden zwei weitere - sehr clevere - XSLT Konstrukte zur Beantwortung dieser Fragen verwendet:
  • xsl:key um die gesuchten Elemente zum schnellen Auffinden in einen Index aufzunehmen.
  • Den relativ neuen << -Operator, um die Postion des aktuellen Elements im DOM Baum zu bestimmen.
Mit diesen beiden Konstrukten, kann man beispielsweise die Anzahl aller Paras bis zu einer gewissen Position im DOM berechnen. Das geht so:
<xsl:key name="paras" match="p" use="true()"/>
[...]
<xsl:variable name="current-para-count" 
              select="count(key('paras',true())[. &lt;&lt; current()])"/>
Zieht man den Para-Count das erste Vorgänger-Verweisziels davon ab, hat man einen Distanzwert bestimmt.
Jetzt kann man dasselbe für das erste Nachfolger-Verweisziel machen und vergleichen.
Mit diesem Vorgehen lässt sich schon Punk 2.) aus obiger Liste beantworten, denn das nächste Verweisziel, egal ob in Blätterrichtung oder entgegen der Blätterrichtung kann bestimmt werden.
Packt man diese Erkenntnisse in ein XSLT Stylesheet, dann sieht das ungefähr so aus:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="2.0">
    
    <xsl:key name="ids" match="@id" use="."/>
    <xsl:key name="paras" match="para" use="true()"/>

   <!-- Vergabe einer neuen, eindeutigen ID an den Verweiszielen, so dass auf das -->
   <!-- nächstgelegene Verweisziel verwiesen werden kann. -->
        
   <xsl:template match="@id[count(key('ids',.)) gt 1]">
        <xsl:attribute name="id" select="concat(.,'_',generate-id(parent::*))"/>
    </xsl:template>
    
    <!-- Vergabe einer neuen Ziel-ID an den Links -->
    
    <xsl:template match="@target-id[count(key('ids',.)) gt 1]">
        <xsl:variable name="nearest-preceding" select="preceding::*[@y.id = current()]"/>
        <xsl:variable name="nearest-following" select="following::*[@y.id = current()]"/>
        <xsl:choose>
            <xsl:when test="$nearest-preceding and $nearest-following">
              <xsl:variable name="current-para-count" 
                            select="count(key('paras',true())[. &lt;&lt; current()])"/>
              <xsl:variable name="nearest-preceding-para-count" 
                            select="count(key('paras',true())
            	                    [. &lt;&lt; $nearest-preceding])"/>
              <xsl:variable name="nearest-following-para-count" 
                            select="count(key('paras',true())
            	                    [. &lt;&lt; $nearest-following])"/>
              <xsl:variable name="distance-to-preceding" 
                            select="$current-para-count - $nearest-preceding-para-count"/>
              <xsl:variable name="distance-to-following" 
                            select="$nearest-following-para-count - $current-para-count"/>	
              <xsl:attribute name="target-id" 
                            select="if ($distance-to-preceding le $distance-to-following) 
                                    then concat($nearest-preceding/@y.id,
                                                '_',generate-id($nearest-preceding)) 
                                    else concat($nearest-following/@y.id,
                		                '_',generate-id($nearest-following))"/>
            </xsl:when>
            [...]
        </xsl:choose>
    </xsl:template>
Previous Page Next Page
Version: 93
Jan 25 2021