XSLT if attribute exists / else

I'm new to XSLT, so I want to know what best checks for the existence 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 iterate over the elements with -loop, checking if it has the attribute "lang". If so, I want to get the entire string using the identifier (for example, EN → 'English'). If the attribute is not set, I want it to write "No language set" or something similar.

Now I use the following code, but I ask myself if this can 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?

+6
source share
2 answers

Perhaps a more efficient use of the key. You define a key using a top-level element outside of your templates

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

Then in a loop you can just say

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

But generally speaking, XSLT's more natural approach to all of this will be to use pattern 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> 

Using these templates, you can make <xsl:apply-templates select="/root/items/item" /> and automatically select the appropriate template for each element. The rule is that it will use the most specific template, therefore item[@lang] for those elements that have the lang attribute, and the usual item for those who do not.

The third option is a little trick I learned on SO to put a full if / else check in one 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) if it is treated as a number, 1 if the lang attribute exists and 0 if it is not. If there is lang="EN" , let's say we build the string "No language setEnglish" , and then take the substring starting with the 16th character, which is equal to "English" . If the lang attribute is missing, we build the string "No language set" (because the string value of the empty node set is an empty string) and take the substring starting with the first character (i.e., the whole string).

You can use the same trick with other attributes, for example. Suppose we have an optional color attribute and it wants to say "No color specified" , if it is missing, you can do this with

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

Another alternative, if you can use XSLT 3.0, is map (another useful link: map ).

XML input (fixed to be well formed)

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

Output

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

All Articles