Accueil
 chercher             Plan du site             Info (English version) 
L'histoire de XML s'écrit en ce moment même. XMLfr vous aide à la suivre et à en dégager les tendances.Les listes de discussions XMLfr sont à votre disposition pour réagir sur nos articles ou simplement poser une question.Si vous ètes passionnée(e) par XML, pourquoi ne pas en faire votre métier ?XMLfr n'est heureusement pas le seul site où l'on parle de XML. Découvrez les autres grâce à XMLfr et à l'ODP.Les partenaires grâce auxquels XMLfr peut se développer.Pour tout savoir sur XMLfr.XMLfr sans fil, c'est possible !Pour ceux qui veulent vraiment en savoir plus sur XML.L'index du site.
 Si vous vous posez une question, vous n'êtes peut-être pas le premier...Les traductions en français des bibles XML.Ces articles sont des références dans leur domaine.Tout ce qu'il faut savoir pour démarrer sur un sujet XML...


Créer une table des matières en XSLT

Comment puis-je générer une table des matières en XSLT?

Eric van der Vlist, Dyomedea (vdv@dyomedea.com).
jeudi 13 avril 2006

Table des matières

Introduction

Principes généraux

Génération de la table des matières

Structure générale

Génération de liens

Numérotation des sections

Génération d'identifiants lisibles et stables

Références

Introduction

Une difficulté peut en cacher une autre et au problème de création de la table des matières s'ajoute fréquemment celui de la structuration du document source lorsqu'il utilise un format n'imposant pas une structuration forte tel que XHTML ou NITF.

La structuration du document source est couvert par un autre article et nous ne nous intéresserons ici qu'au problème de la création de la table des matières à partir d'un document déjà structuré tel qu'un document DocBook.

Nous utiliserons dans cet article le document DocBook accessible à l'adresse « http://www.docbook.org/specs/cs-docbook-simple-1.1.xml » comme exemple.

Principes généraux

Une transformation XSLT produit un arbre résultat à partir d'un arbre source. Pour cela, elle accède en lecture à l'arbre source et en écriture à l'arbre résultat.

Par analogie aux méthodes d'accès à un fichier, l'accès à l'arbre source peut être qualifié d'accès aléatoire dans la mesure où la transformation peut accéder à tout fragment de l'arbre source au moyen du langage XPath.

Au contraire, l'accès à l'arbre résultat est un accès en mode « append » dans la mesure où la transformation ne peut que rajouter des noeuds à un point d'insertion courant que l'on ne peut pas remonter.

Ces modes d'accès conditionnent la manière dont nous allons devoir constituer notre table des matières : puisque nous ne maîtrisons pas le point d'insertion, nous allons devoir effectuer un parcours du document source spécifique à la génération de la table des matières.

Pour différencier ce parcours nous utiliserons un « mode » XSLT particulier.

La structure de notre transformation ressemblera donc à :

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
version="1.0">
    <xsl:template match="/">
        <html>
            <head>
                <!-- 
                
                    Création de l'entête du document
                
                -->
            </head>
            <body>
                <!-- 
                
                    Génération de contenu avant la table des mati
ères
                
                -->
                <div class="tableDesMatières">
                    <h1>Table des matières</h1>
                    <ul>
                        <xsl:apply-templates select="/*/section"
                                mode="tableDesMatières"/>
                    </ul>
                </div>
                <!-- 
                
                    Génération de contenu après la table des mati
ères
                    
                -->
            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>

Génération de la table des matières

Structure générale

La structure du template qui génère la table des matière est assez simple :

    <xsl:template match="section" mode="tableDesMatières">
        <li>
            <p>
                <xsl:value-of select="title"/>
            </p>
            <xsl:if test="section">
                <ul>
                    <xsl:apply-templates select="section" 
                            mode="tableDesMatières"/>
                </ul>
            </xsl:if>
        </li>
    </xsl:template>

Génération de liens

Les choses se compliquent si l'on veut générer des liens vers la partie du document généré où la section elle-même sera affichée.

Pour cela, il va falloir générer une ancre lors de l'affichage de la section et un lien vers cette ancre dans la table des matières, le problème étant de calculer la valeur de cette ancre de la même manière lors des deux parcours.

La solution la plus simple est d'utiliser la fonction XPath « generate-id() ». Cette fonction génère un identifiant unique pour chaque noeud d'un document XML et il suffit de l'utiliser de la même manière lors de la définition et lors de la génération du lien :

    <xsl:template match="section" mode="tableDesMatières">
        <li>
            <p>
                <a href="#{generate-id()}" title="{title}">
                    <xsl:value-of select="title"/>
                </a>
            </p>
            <xsl:if test="section">
                <ul>
                    <xsl:apply-templates select="section" mode="t
ableDesMatières"/>
                </ul>
            </xsl:if>
        </li>
    </xsl:template>
    <xsl:template match="section">
        <!-- 
        
            Ce template affiche la section
            
        -->
        <div id="{generate-id()}">
            <xsl:apply-templates/>
        </div>
    </xsl:template>

Cette solution fonctionne mais elle présente deux inconvénients:

  1. Le format de ces identifiants n'est pas lisibles et dépend de l'implémentation utilisée. Dans le cas de Saxon, on obtient des valeurs du type « #d0e107 », Xalan génère des valeurs du type « #N10076 » et d'autres processeurs génèrent des valeurs ayant des formats totalement différents.
  2. La recommandation XSLT 1.0 spécifie explicitement que ces valeurs peuvent changer entre deux exécutions d'une même transformation : « Une implémentation n'est pas obligée de générer les mêmes identificateurs chaque fois qu'un document subi une transformation ».

Les URLs ainsi générés sont donc instables ce qui est une mauvaise pratique et il est préférable de constituer ses propres identifiants de manière à leur assurer une meilleur stabilité.

Il y a beaucoup de méthodes pour cela et nous verrons comment tirer profit de la numérotation des sections que nous allons maintenant mettre en place pour générer nos identifiants.

Numérotation des sections

Pour numéroter les sections, nous allons simplement compter, pour chaque niveau, le nombre de sections « soeurs » précédent la section courante et ajouter un « . » après ce numéro.

Cela se fait très simplement au moyen du template suivant :

    <xsl:template match="section" mode="numérotation">
        <xsl:value-of select="count(preceding-sibling::section) +
 1"/>
        <xsl:text>.</xsl:text>
    </xsl:template>

L'utilisation de ce template se fait au moyen d'une instruction « xsl:apply-templates » en utilisant l'axe « ancestor-or-self » et notre template de génération de table des matière devient :

    <xsl:template match="section" mode="tableDesMatières">
        <xsl:variable name="numéro">
            <xsl:apply-templates select="ancestor-or-self::sectio
n" mode="numérotation"/>
        </xsl:variable>
        <li>
            <p>
                <a href="#{generate-id()}" title="{title}">
                    <xsl:value-of select="$numéro"/>
                    <xsl:text> </xsl:text>
                    <xsl:value-of select="title"/>
                </a>
            </p>
            <xsl:if test="section">
                <ul>
                    <xsl:apply-templates select="section" mode="t
ableDesMatières"/>
                </ul>
            </xsl:if>
        </li>
    </xsl:template>

Génération d'identifiants lisibles et stables

Nous pouvons maintenant utiliser ces numéros comme identifiants à condition de les préfixer (les identifiants ne doivent pas commencer par un chiffre).

Après cette modification, nos templates deviennent :

    <xsl:template match="section" mode="tableDesMatières">
        <xsl:variable name="numéro">
            <xsl:apply-templates select="ancestor-or-self::sectio
n" mode="numérotation"/>
        </xsl:variable>
        <li>
            <p>
                <a href="#section_{$numéro}" title="{title}">
                    <xsl:value-of select="$numéro"/>
                    <xsl:text> </xsl:text>
                    <xsl:value-of select="title"/>
                </a>
            </p>
            <xsl:if test="section">
                <ul>
                    <xsl:apply-templates select="section" mode="t
ableDesMatières"/>
                </ul>
            </xsl:if>
        </li>
    </xsl:template>
    <xsl:template match="section">
        <!-- 
        
            Ce template affiche la section
            
        -->
        <xsl:variable name="numéro">
            <xsl:apply-templates select="ancestor-or-self::sectio
n" mode="numérotation"/>
        </xsl:variable>
        <div id="section_{$numéro}">
            <xsl:apply-templates/>
        </div>
    </xsl:template>

Les identifiants ainsi générés sont du type : « section_3.1. ». Ils sont à la fois lisibles et aussi stables que possible dans la mesure où ils ne changent que lorsque la structure du document source est modifiée.

La structure complète de la transformation (à améliorer et adapter à votre contexte) est la suivante :

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
version="1.0">
    <xsl:template match="/">
        <html>
            <head>
                <!-- 
                
                    Création de l'entête du document
                
                -->
            </head>
            <body>
                <!-- 
                
                    Génération de contenu avant la table des mati
ères
                
                -->
                <div class="tableDesMatières">
                    <h1>Table des matières</h1>
                    <ul>
                        <xsl:apply-templates select="/*/section" 
mode="tableDesMatières"/>
                    </ul>
                </div>
                <!-- 
                
                    Génération de contenu après la table des mati
ères
                    
                -->
                <xsl:apply-templates select="/*/section"/>
            </body>
        </html>
    </xsl:template>
    <xsl:template match="section" mode="numérotation">
        <xsl:value-of select="count(preceding-sibling::section) +
 1"/>
        <xsl:text>.</xsl:text>
    </xsl:template>
    <xsl:template match="section" mode="tableDesMatières">
        <xsl:variable name="numéro">
            <xsl:apply-templates select="ancestor-or-self::sectio
n" mode="numérotation"/>
        </xsl:variable>
        <li>
            <p>
                <a href="#section_{$numéro}" title="{title}">
                    <xsl:value-of select="$numéro"/>
                    <xsl:text> </xsl:text>
                    <xsl:value-of select="title"/>
                </a>
            </p>
            <xsl:if test="section">
                <ul>
                    <xsl:apply-templates select="section" mode="t
ableDesMatières"/>
                </ul>
            </xsl:if>
        </li>
    </xsl:template>
    <xsl:template match="section">
        <!-- 
        
            Ce template affiche la section
            
        -->
        <xsl:variable name="numéro">
            <xsl:apply-templates select="ancestor-or-self::sectio
n" mode="numérotation"/>
        </xsl:variable>
        <div id="section_{$numéro}">
            <xsl:apply-templates/>
        </div>
    </xsl:template>
    <xsl:template match="title">
        <xsl:variable name="numéro">
            <xsl:apply-templates select="ancestor-or-self::sectio
n" mode="numérotation"/>
        </xsl:variable>
        <p>
            <xsl:value-of select="$numéro"/>
            <xsl:text> </xsl:text>
            <xsl:value-of select="."/>
        </p>
    </xsl:template>
    <xsl:template match="*">
        <p>
            <xsl:value-of select="."/>
        </p>
    </xsl:template>
</xsl:stylesheet>

Références

  1. Structurer un document HTML avec XSLT
  2. Recommandation XSLT 1.0
  3. Recommandation XPath 1.0

Copyright 2006, Eric van der Vlist


 

Mots clés.



L'histoire de XML s'écrit en ce moment même. XMLfr vous aide à la suivre et à en dégager les tendances.


Les documents publiés sur ce site le sont sous licence "Open Content"
Conception graphique
  l.henriot  

Conception, réalisation et hébergement
Questions ou commentaires
  redacteurs@xmlfr.org