XSLT if attribute exists / else

19,385

It might be more efficient to use a key. You define a key using a top-level element outside your templates

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

Then in the loop you can simply say

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

But generally speaking a more natural XSLT approach to the whole thing would be to use template matching instead of for-each and 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>

With these templates in place you can then do <xsl:apply-templates select="/root/items/item" /> and it will pick the appropriate template for each item automatically. The rule is that it will use the most specific template, so the item[@lang] one for those items that have a lang attribute and the plain item one for those that don't.

A third possibility is a little trick I learned on SO to put the whole if/else check into a single XPath expression

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

The trick here is that boolean(@lang) when treated as a number is 1 if the lang attribute exists and 0 if it doesn't. If there is a lang="EN", say, then we construct a string "No language setEnglish" and then take the substring starting at the 16th character, which is "English". If there is no lang attribute we construct the string "No language set" (because the string value of an empty node set is the empty string) and take the substring starting at the first character (i.e. the whole string).

You could use the same trick with other attributes, e.g. suppose we had an optional color attribute and wanted to say "No color specified" if it is absent, you can do that with

<xsl:value-of select="substring(
   concat('No color specified', @color),
   1 + (18 * boolean(@color))
 )" />
Share:
19,385
carlo
Author by

carlo

Front-end web developer

Updated on June 19, 2022

Comments

  • carlo
    carlo almost 2 years

    I'm quite new to XSLT and therefore I want to know what is the best practice for checking the existance of an attribute. My XML looks something like this:

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

    Note that the 'lang'-attribute for the 'item'-element is optional.

    Now I want to loop through the items using a -loop, while checking if it has a "lang"-attribute. If it does, I want to fetch the entire string using the ID (eg. EN -> 'English'). If the attribute isn't set I want it to write "No language set" or something alike.

    Now I use the following code but I'm questioning myself if it can't be done in a more efficient way.

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

    Any suggestions / tips?

  • carlo
    carlo over 11 years
    Thanks, I try to use <xsl:key> but when I do this the page doesn't load. What could I be doing wrong?
  • carlo
    carlo over 11 years
    Solved the problem by moving the <xsl:key> out of the <xsl:template>
  • carlo
    carlo over 11 years
    Now what could I do if there would be an extra optional attribute for <item>, let's say 'color'?
  • Ian Roberts
    Ian Roberts over 11 years
    @carlo I've added some notes on another trick I picked up (from Dimitre Novatchev here on SO) to compress the if/else into one XPath expression, which means you don't need an explicit if or separate templates for the lang/no-lang cases.