web-dev-qa-db-fra.com

Chaîne de concaténation XSLT, supprimer la dernière virgule

J'ai besoin de construire une chaîne en utilisant XSLT et de séparer chaque chaîne avec une virgule, mais de ne pas inclure de virgule après la dernière chaîne. Dans mon exemple ci-dessous, j'aurai une virgule de fin si j'ai un nœud Distribution et non un nœud Note par exemple. Je ne sais pas de toute façon construire une chaîne en tant que variable, puis tronquer le dernier caractère en XSLT. Cela utilise également le moteur Microsoft XSLT.

Ma chaîne =

<xsl:if test="Locality != ''">
  <xsl:value-of select="Locality"/>,
</xsl:if>
<xsl:if test="CollectorAndNumber != ''">
  <xsl:value-of select="CollectorAndNumber"/>,
</xsl:if>
<xsl:if test="Institution != ''">
  <xsl:value-of select="Institution"/>,
</xsl:if>
<xsl:if test="Distribution != ''">
  <xsl:value-of select="Distribution"/>,
</xsl:if>
<xsl:if test="Note != ''">
  <xsl:value-of select="Note"/>
</xsl:if>

[Homme, il doit y avoir un meilleur moyen d'entrer dans cette zone de texte de question :(]

29
Craig

Ceci est très facile à réaliser avec XSLT ( Pas besoin de capturer les résultats dans une variable, ou d'utiliser des modèles nommés spéciaux ):

I. XSLT 1.:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

    <xsl:template match="/*/*">
      <xsl:for-each select=
      "Locality/text() | CollectorAndNumber/text()
     | Institution/text() | Distribution/text()
     | Note/text()
      "
      >
        <xsl:value-of select="."/>
        <xsl:if test="not(position() = last())">,</xsl:if>
      </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

lorsque cette transformation est appliquée sur le document XML suivant:

<root>
    <record>
        <Locality>Locality</Locality>
        <CollectorAndNumber>CollectorAndNumber</CollectorAndNumber>
        <Institution>Institution</Institution>
        <Distribution>Distribution</Distribution>
        <Note></Note>
        <OtherStuff>Unimportant</OtherStuff>
    </record>
</root>

le résultat souhaité est produit:

Locality,CollectorAndNumber,Institution,Distribution

Si les éléments souhaités doivent être produits non dans l'ordre du document (quelque chose de non requis dans la question, mais soulevé par Tomalak), il est encore assez facile et élégant de le faire:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>

    <xsl:param name="porderedNames"
     select="' CollectorAndNumber Locality Distribution Institution Note '"/>

    <xsl:template match="/*/*">
        <xsl:for-each select=
         "*[contains($porderedNames, concat(' ',name(), ' '))]">

         <xsl:sort data-type="number"
          select="string-length(
                     substring-before($porderedNames,
                                      concat(' ',name(), ' ')
                                      )
                                )"/>

            <xsl:value-of select="."/>
            <xsl:if test="not(position() = last())">,</xsl:if>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

Ici, les noms des éléments recherchés et leur ordre recherché sont fournis dans le paramètre de chaîne $porderedNames, qui contient une liste séparée par des espaces de tous les noms recherchés .

Lorsque la transformation ci-dessus est appliquée sur le même document XML, le résultat souhaité est produit:

CollectorAndNumber,Locality,Distribution,Institution

II. XSLT 2.:

En XSLT, cette tâche est encore plus simple ( encore une fois, aucune fonction spéciale n'est nécessaire ):

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

    <xsl:template match="/*/*">
    <xsl:value-of separator="," select=
    "(Locality, CollectorAndNumber,
     Institution, Distribution,
     Note)[text()]" />
    </xsl:template>
</xsl:stylesheet>

Lorsque cette transformation est appliquée sur le même document XML, le même résultat correct est produit:

Locality,CollectorAndNumber,Institution,Distribution

Notez bien que les éléments souhaités seront produits dans n'importe quel ordre souhaité, car nous utilisons le type de séquence XPath 2.0 (vs l'union dans la solution XSLT 1.0), qui par définition contient des éléments dans n'importe quel ordre souhaité ( spécifié).

50
Dimitre Novatchev

Je préférerais un modèle d'appel court pour joindre les valeurs de noeud ensemble. Cela fonctionne également si un nœud au milieu de votre liste concaténée, par exemple Institution, est manquant:

<xsl:template name="join">
    <xsl:param name="list" />
    <xsl:param name="separator"/>

    <xsl:for-each select="$list">
        <xsl:value-of select="." />
        <xsl:if test="position() != last()">
            <xsl:value-of select="$separator" />
        </xsl:if>
    </xsl:for-each>
</xsl:template>

Voici un petit exemple d'utilisation:

Exemple de document d'entrée:

<?xml version="1.0" encoding="utf-8"?>
<items>
  <item>
    <Locality>locality1</Locality>
    <CollectorAndNumber>collectorAndNumber1</CollectorAndNumber>
    <Distribution>distribution1</Distribution>
    <Note>note1</Note>
  </item>
  <item>
    <Locality>locality2</Locality>
    <CollectorAndNumber>collectorAndNumber2</CollectorAndNumber>
    <Institution>institution2</Institution>
    <Distribution>distribution2</Distribution>
    <Note>note2</Note>
  </item>
  <item>
    <Locality>locality3</Locality>
    <CollectorAndNumber>collectorAndNumber3</CollectorAndNumber>
    <Institution>institution3</Institution>
    <Distribution>distribution3</Distribution>
  </item>
</items>

Transformation XSL:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="/">
    <summary>
      <xsl:apply-templates />
    </summary>
  </xsl:template>

  <xsl:template match="item">
    <item>
      <xsl:call-template name="join">
        <xsl:with-param name="list" select="Locality | CollectorAndNumber | Institution | Distribution | Note" />
        <xsl:with-param name="separator" select="','" />
      </xsl:call-template>
    </item>
  </xsl:template>

  <xsl:template name="join">
    <xsl:param name="list" />
    <xsl:param name="separator"/>

    <xsl:for-each select="$list">
      <xsl:value-of select="." />
      <xsl:if test="position() != last()">
        <xsl:value-of select="$separator" />
      </xsl:if>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Document de sortie généré:

<?xml version="1.0" encoding="utf-8"?>
<summary>
  <item>locality1,collectorAndNumber1,distribution1,note1</item>
  <item>locality2,collectorAndNumber2,institution2,distribution2,note2</item>
  <item>locality3,collectorAndNumber3,institution3,distribution3</item>
</summary>

NB: Si vous utilisiez XSLT/XPath 2.0 alors il y aurait fn: string-join

fn:string-join**($operand1 as string*, $operand2 as string*) as string

qui pourrait être utilisé comme suit:

fn:string-join({Locality, CollectorAndNumber, Distribution, Note}, ",") 
7
Dirk Vollmar

Supposons que vous ayez quelque chose comme le XML d'entrée suivant:

<root>
  <record>
    <Locality>Locality</Locality>
    <CollectorAndNumber>CollectorAndNumber</CollectorAndNumber>
    <Institution>Institution</Institution>
    <Distribution>Distribution</Distribution>
    <Note>Note</Note>
    <OtherStuff>Unimportant</OtherStuff>
  </record>
</root>

Ensuite, ce modèle le ferait:

<xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>

  <xsl:output method="text" />

  <xsl:template match="record">
    <xsl:variable name="values">
      <xsl:apply-templates mode="concat" select="Locality" />
      <xsl:apply-templates mode="concat" select="CollectorAndNumber" />
      <xsl:apply-templates mode="concat" select="Institution" />
      <xsl:apply-templates mode="concat" select="Distribution" />
      <xsl:apply-templates mode="concat" select="Note" />
    </xsl:variable>

    <xsl:value-of select="substring($values, 1, string-length($values) - 1)" />
    <xsl:value-of select="'&#10;'" /><!-- LF -->
  </xsl:template>

  <xsl:template match="Locality | CollectorAndNumber | Institution | Distribution | Note" mode="concat">
    <xsl:value-of select="." />
    <xsl:text>,</xsl:text>
  </xsl:template>

</xsl:stylesheet>

Sortie sur mon système:

Locality,CollectorAndNumber,Institution,Distribution,Note
3
Tomalak

Je pense qu'il pourrait être utile de mentionner que position () ne fonctionne pas correctement lorsque j'utilise une sélection compliquée qui filtre certains nœuds, dans ce cas, j'ai trouvé cette astuce:

vous pouvez définir une variable de chaîne contenant la valeur des nœuds, séparés par un caractère spécifique, puis en utilisant str: tokenize (), vous pouvez créer une liste complète de nœuds dont la position fonctionne bien avec elle.

quelque chose comme ça:

<!-- Since position() doesn't work as expected(returning node position of current 
node list), I got round it by a string variable and tokenizing it in which 
absolute position is equal to relative(context) position. -->
<xsl:variable name="measObjLdns" >
    <xsl:for-each select="h:measValue[@measObjLdn=$currentMeasObjLdn]/h:measResults" >
        <xsl:value-of select="concat(.,'---')"/> <!-- is an optional separator. -->
    </xsl:for-each>
</xsl:variable>

<xsl:for-each select="str:tokenize($measObjLdns,'---')" ><!-- Since position() doesn't 

work as expected(returning node position of current node list), 
I got round it by a string variable and tokenizing it in which
absolute position is equal to relative(context) position. -->

    <xsl:value-of select="."></xsl:value-of>
    <xsl:if test="position() != last()">
        <xsl:text>,</xsl:text>
    </xsl:if>
</xsl:for-each>
<xsl:if test="position() != last()">
    <xsl:text>,</xsl:text>
</xsl:if>
2
Mostafa

N'avez-vous pas une valeur qui sera toujours là? Si vous le faites, vous pouvez le retourner et mettre des virgules devant tout sauf le premier élément (ce qui serait votre valeur qui est toujours là).

1
Martin Peck