2013-01-15 2 views
6

Je ne suis pas familier avec XSLT et je veux donc savoir quelle est la meilleure pratique pour vérifier l'existence d'un attribut. Mon XML ressemble à ceci:XSLT si l'attribut existe/else

<root> 
    <languages> 
     <lang id="EN">English<lang> 
     <lang id="FR">French<lang> 
     <lang id="DE">German</lang> 
    </languages> 
    <items> 
     <item lang="EN">test 1</item> 
     <item>test 2</item> 
     <item lang="FR">item 3</item> 
    </items> 
</root> 

Notez que « lang'-attribut pour le » élément item' est facultatif.

Maintenant je veux boucler les éléments en utilisant un -loop, tout en vérifiant s'il a un attribut "lang". Si c'est le cas, je veux récupérer la chaîne entière en utilisant l'ID (par exemple, EN -> 'English'). Si l'attribut n'est pas défini, je veux écrire "No language set" ou quelque chose de semblable.

Maintenant j'utilise le code suivant mais je me pose la question si cela ne peut pas être fait de manière plus efficace.

<xsl:for-each select="//root/items/item"> 
    <xsl:variable name="cur_lang" select="@lang" /> <!-- first I store the attr lang in a variable --> 
    <xsl:choose> 
     <xsl:when test="@lang"> <!-- then i test if the attr exists --> 
      <xsl:value-of select="//root/languages/lang[@id=$cur_lang]" /> <!-- if so, parse the element value --> 
     </xsl:when> 
     <xsl:otherwise> 
      No language set <!-- else --> 
     </xsl:otherwise> 
    </xsl:choose> 
</xsl:for-each> 

Des suggestions/conseils?

+2

Pouvez-vous ajouter un exemple de Quelle devrait être la sortie exacte de l'entrée que vous avez fournie? –

+0

Carlo, Vous avez oublié de fournir la sortie désirée - Je pensais que vous vouliez que la chaîne de langue étendue remplace la valeur actuelle de l'attribut 'lang'. Veuillez * modifier * la question et fournir le résultat souhaité lorsque la transformation est appliquée sur le document XML fourni. –

Répondre

7

Il peut être plus efficace d'utiliser une clé. Vous définissez une clé à l'aide d'un élément de niveau supérieur en dehors de vos modèles

<xsl:key name="langByCode" match="lang" use="@id" /> 

Puis dans la boucle vous pouvez simplement dire

<xsl:when test="@lang"> <!-- then i test if the attr exists --> 
    <xsl:value-of select="key('langByCode', @lang)" /> 
</xsl:when> 

Mais, en général une approche XSLT plus naturel de la chose serait utiliser à la place correspondant de modèle de for-each et if:

<xsl:template match="item[@lang]"> 
    <xsl:value-of select="key('langByCode', @lang)" /> 
</xsl:template> 

<xsl:template match="item"> 
    <xsl:text>No language set</xsl:text> 
</xsl:template> 

Avec ces modèles en place, vous pouvez alors faire <xsl:apply-templates select="/root/items/item" /> et il choisira le modèle approprié pour chaque article automatiquement. La règle est qu'il utilisera le modèle le plus spécifique, de sorte que le item[@lang] un pour les éléments qui ont un attribut lang et le item un pour ceux qui ne le font pas.

Une troisième possibilité est un petit truc que j'appris sur le SO de mettre l'ensemble if/else vérifier en une seule expression XPath

<xsl:value-of select=" 
    substring(
    concat('No language set', key('langByCode', @lang)), 
    1 + (15 * boolean(@lang)) 
)" /> 

L'astuce ici est que boolean(@lang) quand ils sont traités comme un numéro est 1 si la L'attribut lang existe et 0 s'il ne l'est pas. S'il y a un lang="EN", disons, alors nous construisons une chaîne "No language setEnglish" et prenons ensuite la sous-chaîne commençant au 16ème caractère, qui est "English". Si aucun attribut lang nous construisons la chaîne "No language set" (parce que la valeur de chaîne d'un ensemble de nœuds vides est la chaîne vide) et prenons la sous-chaîne commençant au premier caractère (c'est-à-dire la chaîne entière).

Vous pouvez utiliser la même astuce avec d'autres attributs, par ex. Supposons que nous ayons un attribut de couleur en option et je voulais dire "No color specified" si elle est absente, vous pouvez le faire avec

<xsl:value-of select="substring(
    concat('No color specified', @color), 
    1 + (18 * boolean(@color)) 
)" /> 
+0

Merci, j'essaie d'utiliser mais quand je fais cela, la page ne se charge pas. Que pourrais-je faire de mal? – carlo

+0

Résolu le problème en déplaçant le sur le carlo

+0

Maintenant, que pouvais-je faire s'il y aurait un attribut optionnel supplémentaire pour , disons 'color'? – carlo

0

Une autre alternative, si vous êtes en mesure d'utiliser XSLT 3.0, est un map (un autre lien utile: map).

XML d'entrée (fixée à être bien formé)

<root> 
    <languages> 
     <lang id="EN">English</lang> 
     <lang id="FR">French</lang> 
     <lang id="DE">German</lang> 
    </languages> 
    <items> 
     <item lang="EN">test 1</item> 
     <item>test 2</item> 
     <item lang="FR">item 3</item> 
    </items> 
</root> 

XSLT 3,0

<xsl:stylesheet version="3.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:map="http://www.w3.org/2005/xpath-functions/map" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" extension-element-prefixes="xs map"> 
    <xsl:output indent="yes"/> 
    <xsl:strip-space elements="*"/> 

    <xsl:variable name="lang-map" as="map(xs:string, xs:string)" 
     select="map:new(
     for $lang in /*/languages/lang 
     return 
      map{$lang/@id := $lang/string()} 
     )"/> 

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

    <xsl:template match="languages"/> 

    <xsl:template match="item[@lang and map:contains($lang-map,@lang)]"> 
     <item><xsl:value-of select="$lang-map(current()/@lang)"/></item> 
    </xsl:template> 

    <xsl:template match="item"> 
     <item>No language found.</item> 
    </xsl:template> 

</xsl:stylesheet> 

Sortie

<root> 
    <items> 
     <item>English</item> 
     <item>No language found.</item> 
     <item>French</item> 
    </items> 
</root>