JiMRI: DecoderPro User Guide - Use XSLT Transformation for complex decoders File

# JiMRI : Guide d'Utilisation de DecoderPro

## Utiliser la transformation XSLT pour les décodeurs complexes

Certains décodeurs contiennent des blocs répétés de CV, par exemple pour définir le comportement de plusieurs accessoires, chacun contrôlé par plusieurs CVs. Un décodeur d'aiguillages avancé peut par exemple définir plusieurs chemins, contenant chacun plusieurs aiguillages et leur position souhaitée pour former le chemin de déplacement sur le réseau.

Bien que le fichier du décodeur doive définir des dizaines, voire des centaines, de CV et leur apparence sur les volets au total, seule une fraction des CV ou des affichages sont réellement uniques : le reste peut être généré à partir d'un modèle. Bien que la création d'un modèle, et la recette de transformation soit beaucoup plus complexe que de copier-coller des définitions de CV, l'avantage est une maintenance beaucoup plus facile. une fois que le plus dur est fait : chaque changement se propage de manière cohérente à toutes les parties générées.

Pour donner un exemple de simplification possible - prenons le fichier du décodeur. `Public_Domain_dccdoma_ARD_SCOM_MX.xm`. Il configure un décodeur, capable d'afficher les aspects des signaux sur plusieurs mâts de signalisation. La configuration contient plus de 500 CV - pourtant l'idée de base de la configuration est très simple :

- un aspect par défaut pour chaque mât de signaux
- pour chaque mât de signaux ET pour chacun des 32 aspects possibles, le nombre de signaux à afficher, interprété par le décodeur lui-même

Quelques statistiques :

- Définition originale du décodeur : 870 kByte 20608 lignes.
- Fichier de feuilles de style : 12 kByte, 257 lignes.
- Modèle de fichier décodeur : 18 kByte, 390 lignes.

Pour JiMRI lui-même ou la vitesse de fonctionnement du DecoderPro, ces deux approches sont les mêmes : le modèle de fichier est transformé (expansé) en interne en définition de décodage en XML et traité comme s'il était écrit entièrement à la main. Pour la maintenance, il est bien plus facile de maintenir ~600 lignes de XML que 20600.

JiMRI fournit une option pour appliquer une feuille de style XSLT à un fichier décodeur, avant que le fichier soit chargé dans DecoderPro et avant qu'il soit interprété comme variables et panneaux de CV. Cela permet d'écrire à la main des définitions de CV uniques et leurs panneaux, et d'ajouter du contenu généré le cas échéant.

## Exemples de fichiers

Pour illustrer les techniques décrites ici, quelques fichiers d'exemple sont fournis ; tous les fichiers sont sous licence GNU GPL.

- decoder-template.xml - l'exemple de définition du décodeur.
- scom.xsl - la feuille de style

Le modèle de décodeur doit être placé dans le dossier xml/decoders de l'installation de JiMRI. Il est basé sur le modèle de Petr Sidlo. dccdoma.cz - ARD-SCOM-MX decoder - génère les mêmes panneaux de décodeur que l'original (à partir de 12/2019). La feuille de style (scom.xsl) doit être placée également dans le dossier `xml/decoder` de l'installation JiMRI.

Le modèle peut être traité à partir de la ligne de commande pour générer le XML du décodeur, afin que vous puissiez inspecter les effets de la modification de la feuille de style et/ou des données intégrées dans le modèle de décodeur. La ligne de commande pour Linux :

    xsltproc scom.xsl decoder-template.xml > decoder-gen.xml

N'oubliez pas de remplacer les fichiers par leurs noms ou emplacements réels ; pour expérimenter à partir de la ligne de commande, le mieux est de placer le modèle de fichier du décodeur ET sa feuille de style dans un certain répertoire et d'y travailler. Plus tard, déplacez la feuille de style et le modèle dans les dossiers comme décrit ci-dessus.

## Appliquer la feuille de style au fichier décodeur.

Une instruction pour traiter le fichier comme un modèle doit être présente dans le fichier, afin d'agir comme un modèle. Sinon, JiMRI le prendrait comme une définition de décodeur "ordinaire" - tous les éléments d'affichage (voir ci-dessous) "détournés" pour contenir les données pour le traitement du modèle. apparaîtraient dans l'interface utilisateur !

L'instruction de traitement doit apparaître au début de la définition du décodeur :

    <?transform-xslt href="http://jmri.org/xml/decoders/scom.xsl"?>

Ainsi, l'en-tête du modèle de décodeur ressemblerait à ceci :

    <?xml version="1.0" encoding="utf-8"?>
    <?transform-xslt href="http://jmri.org/xml/decoders/scom.xsl"?>
    <decoder-config xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="http://jmri.org/xml/schema/decoder.xsd" showEmptyPanes="no" >

        <decoder>
    ...

## Fournir des métadonnées à la feuille de style

L'un des points critiques est de savoir comment générer les numéros de CV ou d'autres variables : Le langage XSLT fournit des calculs numériques simples, mais les fonctions plus sophistiquées fonctions ne sont généralement pas accessibles (par défaut). Certains contenus générés sont est composé d'une liste de chaînes de caractères (c'est-à-dire que les noms des aspects du signal sont répétés pour chaque mât de signal). chaque mât de signal), et nous devons fournir une telle entrée à la feuille de style. Le fichier décodeur est la seule entrée fournie pour la feuille de style par le cadre JiMRI.

Le fichier de modèle de décodeur est encore interprété comme une définition de décodeur et doit adhérer strictement au schéma XML `decoder.xsd`. Pour les parties que nous voulons générer à partir du modèle, les éléments prescrits doivent être consciencieusement détournés pour fournir des informations sur le modèle.

- des points d'ancrage, où le contenu généré sera inséré
- des données d'entrée pour la feuille de style

Il y a plusieurs façons d'aborder le problème, je vais présenter une façon que je considère comme plus ou moins propre (bien qu'elle abuse d'éléments pour fournir des données différentes de celles qu'ils devraient formellement !). Le guide doit être vu comme une recommandation pour garder les décodeurs générés quelque peu cohérents. S'il vous plaît n'hésitez pas à contribuer et à fournir des approches plus simples.

### Ajout de variables

Le simple ajout de variables est simple, et ne nécessite aucun placeholder supplémentaire dans le fichier du décodeur. Cependant, l'élément `<variables>` doit être présent, afin que la technique décrite ci-dessous pour générer des variables fonctionne. L'élément pourrait ressembler à cet exemple :

            <variables>
              <variable CV="1" item="Short Address" default="100" >
                <splitVal highCV="9" upperMask="XXXXXVVV" factor="1" offset="0" />
                <label>Decoder Address:</label>
                <tooltip>Accessory decoder address. CV1 - LSB. CV9 - MSB.</tooltip>
              </variable>
            </variable>

Le contenu supplémentaire généré sera appliqué à l'intérieur de cet élément.

### Volet du détenteur de données

Alors que la définition de l'élément `variable` est plutôt stricte, les définitions de l'interface utilisateur semblent les plus détendues, donc nous en en abusons. La section suivante décrit quelques types de données typiques, comment elles peuvent être représentées dans le décodeur afin que le texte soit conforme aux règles obligatoires de `decoder.xsd`. Et enfin comment ils peuvent être accessibles à partir de la feuille de style.

Toutes les données (pas les définitions des panneaux de l'interface utilisateur) seront placées dans un élément unique <volet>. Le nom du volet peut être arbitraire, mais doit être unique afin qu'un volet défini par le système ou un vrai volet personnalisé ne soit pas remplacé accidentellement. Dans notre exemple, le nom __Aspects est utilisé. Je recommande de préfixer le nom du panneau par deux traits de soulignement. Le nom nom du volet doit être utilisé dans les sélecteurs - donc si vous inventez votre propre nom, remplacez le texte des exemples par le nom de votre choix.

#### Passage de la racine des données

Chaque fois, qu'une valeur doit être lue par la feuille de style, elle doit être sélectionnée par une expression XPath. Par exemple :

    <xsl:template name="generate-masts">
          <xsl:variable name="cvStart" select="string(//pane[name/text() ='__Aspects']/display[@item='mastcount']/@tooltip)"/>
          <xsl:variable name="outputs" select="string(//pane[name/text() ='__Aspects']/display[@item='outputs']/@label)"/>
          <xsl:for-each select="//pane[name/text() ='__Aspects']/display[@item='masts']/label">
              ...
          </xsl:for-each>
    </xsl:template>

Le sélecteur contient toujours la partie préfixe commun, qui trouve le volet "détenteur de données" dans le fichier modèle du décodeur. Nous pouvons économiser la frappe en passant cet élément comme paramètre :

    <xsl:template name="generate-masts">
          <xsl:param name="root"/>
          <xsl:variable name="cvStart" select="string($root/display[@item='mastcount']/@tooltip)"/>
          <xsl:variable name="outputs" select="string($root/display[@item='outputs']/@label)"/>
          <xsl:for-each select="$root/display[@item='masts']/label">
              ...
          </xsl:for-each>
    </xsl:template>

L'invocation d'un tel modèle générateur doit passer le paramètre :

    <xsl:call-template name="generate-masts">
          <xsl:with-param name="root" select="//pane[name/text() ='__Aspects']//display[position() = 1]/.."/>
    </xsl:call-template>

Notez le suffixe étrange. C'est parce que les éléments d'affichage ne peuvent pas être imbriqués directement dans l'élément pane, ils doivent être dans une sorte de colonne, de ligne, de groupe, etc. Le sélecteur étrange à la fin trouvera le premier élément d'affichage imbriqué et prendra son élément parent comme racine de données.

Une variable globale peut être définie de manière similaire - placez directement cet élément comme élément de premier niveau dans la feuille de style :

      <xsl:variable name="root" select="//pane[name/text() = '__Aspects']//display[position() = 1]/.."/>

Les templates peuvent maintenant référencer la racine des données par la seule expression $root.

#### Constantes, valeurs max/min, valeurs uniques.

Une constante peut être utilisée, par exemple, comme un nombre maximum d'éléments, un numéro de CV spécifique, .... Je recommande l'utilisation de l'élément display. pour définir une constante. Cet élément a deux attributs de forme libre : label et tooltip. Nous pouvons donc définir en fait deux constantes dans un seul élément ! Cela peut être utile, s'il y a des valeurs étroitement liées entre elles, par exemple. Les constantes, qui définissent le nombre maximum d'aspects gérés par l'interface utilisateur et le CV de départ peuvent être écrits comme :

      <display item="mastcount" label="15" tooltip="128"/>

Le "mastcount" est un nom arbitraire (mais unique). Nommez-le ainsi après la signification de la valeur pour votre décodeur. Il sera utilisé dans les sélecteurs pour accéder à la valeur comme ceci :

      <xsl:variable name="cvStart" select="string($root/display[@item='mastcount']/@tooltip)"/>

- $root est le paramètre / variable qui contient la racine des données.
- mastcount est le nom de l'élément display - votre valeur.
- @tooltip signifie que le sélecteur lira l'attribut tooltip. Vous pouvez utiliser @label pour accéder à l'autre.

#### Enumérations, séquences, listes

Il faut parfois générer une CV (variable, élément d'affichage) pour, par exemple, chaque sortie identifiée par un nom, ou un numéro. La liste peut être codée comme une série de <label> sous-éléments d'un élément <display> :

      <display item="masts" tooltip="512">
          <label>0</label><label>1</label><label>2</label><label>3</label><label>4</label><label>5</label><label>6</label><label>7</label>.
          <label>8</label><label>9</label><label>10</label><label>11</label><label>12</label><label>13</label><label>14</label><label>15</label>.
      </affichage>

Nous pouvons ensuite soit itérer ces éléments un par un, soit y accéder par indice/position selon les besoins. Les exemples suivants sélectionnent les élément de données mâts sous la racine des données (voir ci-dessus pour la racine des données). Pour chacun des éléments il appelle un autre modèle (non représenté ici), et transmet la valeur de l'élément (encodée dans le contenu de l'élément label) au modèle en tant que paramètre mast :

      <xsl:template name="generate-panes">
          <xsl:param name="racine"/>

          <xsl:for-each select="$root/display[@item='masts']/label">
              <xsl:variable name="mât" select="string(./text())"/>
              <xsl:call-template name="mast-pane">
                  <xsl:with-param name="root" select="$root"/>
                  <xsl:with-param name="mast" select="$mast"/>
              </xsl:call-template>
          </xsl:for-each>
      </xsl:template>

Notez, que l'élément content est utilisé comme valeur ici - cela permet d'utiliser tous les caractères gênants comme les guillemets, les doubles guillemets, ">" et d'autres caractères non autorisés dans les attributs.

Les éléments individuels peuvent être accédés par leur index (qui est passé en paramètre) :

      <xsl:template name="generate-one-panes">
          <xsl:param name="root"/>
          <xsl:param name="index"/>

          <xsl:variable name="mast" select="string($root/display[@item='masts']/label[position() = $index]/text())"/>
          <xsl:call-template name="mast-pane">
              <xsl:with-param name="root" select="$root"/>
              <xsl:with-param name="mast" select="$mast"/>
          </xsl:call-template>
      </xsl:template>

Vous pouvez facilement utiliser la liste d'étiquettes ci-dessus pour faire une boucle de 1 à 15, ce qui n'est directement pas possible en XSLT. Au lieu de contrôler la boucle par une variable d'indice de contrôle, nous contrôlons la boucle par les données qui doivent s'appliquer dans les itérations individuelles du cycle et dériver la variable d'index à partir d'elles. Voici l'exemple modifié :

    <xsl:template name="generate-panes">
        <xsl:param name="root"/>
        <-- The loop count is controlled by the number of label variables -->
        <xsl:for-each select="$root/display[@item='masts']/label">
            <xsl:variable name="mast" select="string(./text())"/>
            <xsl:call-template name="mast-pane">
                <xsl:with-param name="root" select="$root"/>
                <xsl:with-param name="mast" select="$mast"/>
                <-- We use the current label's element position to derive the
                   "loop control variable" value -->
                <xsl:with-param name="index" select="./position()"/>
            </xsl:call-template>
        </xsl:for-each>
    </xsl:template>

### Cycles et boucles

Le langage XSLT est un langage déclaratif, et les variables, une fois assignées, ne peuvent pas être modifiées - il n'a donc pas de construction de boucle comme le font la plupart des langages de programmation. Parfois, un cycle peut être remplacé de manière plus illustrative par une itération sur le contenu. Parfois, ce n'est pas possible : vraiment un certain nombre fixe d'itérations doit être fait, comme générer des CV séquentiels avec la même structure - seuls le numéro de séquence et l'indice de la fonction représentée différeront.

Cela peut être fait par la tail recursion( récursion de queue), qui remplace les boucles en invoquant un modèle à partir de ce modèle lui-même. La seule mise en garde est que le nombre d'itérations est limité à environ 100 ( ?), avant que l'espace de la pile ne soit épuisé. L'exemple se trouve dans `TamsValleyDepot_QuadLn_s_11.xsl`, cherchez le modèle `AllLEDGroups` :

    <xsl:template name="AllLEDGroups">
    <-- Utilisez l'attribut select="" pour choisir une valeur initiale pour le cycle.
        S'applique si le modèle n'obtient pas de paramètre lors de la (première) invocation -->.
    <xsl:param name="CV1" select="633"/>
    <xsl:param name="CV2" select="513"/>
    <xsl:param name="CV3" select="537"/>
    <-- C'est la variable de contrôle de la boucle -->.
    <xsl:param name="index" select="1"/>
    <!-- La ligne suivante contrôle le nombre -->
    <xsl:if test="24 >= $index">
      <xsl:call-template name="OneLEDGroup">
        <xsl:with-param name="CV1" select="$CV1"/>
        <xsl:with-param name="CV2" select="$CV2"/>
        <xsl:with-param name="CV3" select="$CV3"/>
        <xsl:with-param name="index" select="$index"/>
      </xsl:call-template>
      <-- itérer jusqu'à ce que ce soit fait -->
      <-- Le if quelques lignes plus haut fait en sorte que, ce modèle d'appel
          ne sera pas exécuté pour i > 24 -->.
      <xsl:call-template name="AllLEDGroups">
        <xsl:with-param name="CV1" select="$CV1 +1"/>
        <xsl:with-param name="CV2" select="$CV2 +1"/>
        <xsl:with-param name="CV3" select="$CV3 +2"/>
        <-- S'appelle lui-même, mais avec la variable de contrôle un plus haut, donc en comptant
            le nombre de cycles-->
        <xsl:with-param name="index" select="$index+1"/>
      </xsl:call-template>
    </xsl:if>
    </xsl:template>

## Création de la feuille de style

### Un "boilerplate"

Le modèle est susceptible de contenir des instructions de type " boilerplate ". Les déclarations suivantes devraient se trouver en haut, définissant comment la sortie sera générée :

    <?xml version="1.0" encoding="utf-8"?>
    <xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:db="http://docbook.org/ns/docbook"
      xmlns:xi="http://www.w3.org/2001/XInclude"

      exclude-result-prefixes="db">

      <xsl:output method="xml" encoding="utf-8" indent="yes"/>
      <xsl:strip-space elements=""/>
      <xsl:preserve-space elements="text"/>
    </xsl:stylesheet>

Ce qui suit va copier les éléments, et leurs attributs vers la sortie :

    <xsl:template match="@*|node()">
      <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
    </xsl:template>

### Générer du contenu aux points d'insertion

Les définitions de variables sont généralement générées par la feuille de style. Les variables de base et fixes doivent être fournies, comme d'habitude, dans l'élément `<variables>`. La feuille de style peut ensuite appliquer les variables générées à la fin :

    <xsl:template match="variables">
      <variables>
        <xsl:copy-of select="node()"/>
        <-- instructions d'appel de modèle, qui génèrent le contenu ; l'exemple suit -->
        <xsl:call-template name="generate-masts">
              <xsl:with-param name="root" select="//pane[name/text() = '__Aspects']//display[position() = 1]/.."/>
        </xsl:call-template>

        <xsl:call-template name="generate-aspects">
              <xsl:with-param name="root" select="//pane[name/text() = '__Aspects']//display[position() = 1]/.."/>
        </xsl:call-template>
      </variables>
    </xsl:template>

Notez que, dans cet exemple, l'élément `pane` avec un nom spécial (`__Aspects`) est utilisé comme un support pour les données d'entrée pour la génération. Alors que `//pane[name/text() == '__Aspects']` sélectionne le support de données, l'élément `//display[` est utilisé comme support de données pour la génération. `//display[position() = 1]/..` sélectionne un élément intérieur à l'élément XML du volet porteur. Faites attention aux erreurs de frappe dans les chaînes de caractères, sinon les clauses de sélection sélectionnent des données vides, et rien - ou un contenu invalide - ne sera généré.

Pour les Panneaux d'interface, je recommande de remplacer le détenteur des données par la séquence des panneaux générés. Dans mon exemple , les données sont fournies par le panneau nommé __Aspects, que nous ne voulons absolument pas afficher dans DecoderPro car ce ... n'est pas un panneau de l'interface utilisateur, après tout. Ce qui suit va remplacer le support de données (un VPanneau de niveau supérieur) avec des panneaux générés par la feuille de style :

      <xsl:template match="pane[name='__Aspects']" priority="100">
          <-- instructions d'appel de modèle pour les groupes individuels de panneaux à générer ; l'exemple suit -->.
          <xsl:call-template name="generate-panes">
                  <xsl:with-param name="root" select="//pane[name/text() = '__Aspects']//display[position() = 1]/.."/>
          </xsl:call-template>
      </xsl:template>

La clause `match` va réagir sur l'élément `__Aspect` du volet du détenteur de données, mais contrairement aux variables point d'insertion, aucune instruction de copie n'est présente. L'ancien contenu sera donc jeté (l'élément `<pane>` entier !), remplacé par les éléments que les instructions `call-template` génèrent.

## Utilisation des fragments XML

Si une partie du contenu généré ne change pas d'un endroit à l'autre, il est possible de la préparer comme un fragments XML à être inclus : il ne fera pas partie de la feuille de style XSL avec toutes ces étranges instructions xsl:xxx, mais sera stocké comme un morceau séparé, petit et propre morceau de XML. Cela peut être utile pour les valeurs de choix, ou même les panneaux d'interface utilisateur répétés sans contenu variable. Un exemple d'utilisation est à nouveau dans `TamsValleyDepot_QuadLn_s_11.xsl`. (LedPaneHeader)

Les variables individuelles sont générées à l'aide de xsl:template, mais la partie valeur, la plupart du temps un choix est incluse depuis un fichier séparé. Notez que le xi:include sera généré dans le XML résultant, donc c'est le lecteur du panneau DecoderPro, qui fait l'inclusion du contenu, et non le générateur. Le modèle substitue les parties variables de la définition :

      <variable item="Aspect{$index} LED1 Out" CV="{$CV2}" mask="XXXVVVVV" default="0">
          <xi:include href="http://jmri.org/xml/decoders/tvd/LedOutput.xml"/>
      </variable>

Il y a deux choses importantes. Lorsque vous utilisez le préfixe xi:, ce préfixe doit être déclaré en haut du document (peut être dans n'importe quel élément parent, mais par convention, les préfixes sont rassemblés au début). Utilisez exactement la même URL (valeur de l'attribut), sinon l'instruction ne sera pas reconnue.

      <xsl:stylesheet version="1.0"
          xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xmlns:db="http://docbook.org/ns/docbook"
          xmlns:xi="http://www.w3.org/2001/XInclude" -- c'est la déclaration du préfixe.
          >

Le deuxième problème est que le xi:include doit utiliser des URLs que JiMRI est capable de résoudre localement. Dans le cas contraire, le DecoderPro tenterait de télécharger des parties de la définition depuis Internet, ce qui nécessite une connexion en ligne - et est lent. Le préfixe http://jmri.org/xml est garanti pour résoudre dans le répertoire xml de votre installation locale de JiMRI. Pour plus de cartographie, veuillez consulter les autres documents JiMRI.

Traduction 2021-03-25