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]"/>
Plain Text

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:

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()])"/>
Plain Text

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>
Plain Text