Conditionals, loops and variables

XSLT has no assignment. Every value is bound once, and that single constraint explains most of how stylesheets are structured.

if, choose and for-each

<xsl:choose>
  <xsl:when test="@status = 'published' and published-date &lt;= current-date()">
    <span class="live">Live</span>
  </xsl:when>
  <xsl:when test="@status = 'draft'">
    <span class="draft">Draft</span>
  </xsl:when>
  <xsl:otherwise>
    <span class="unknown">Unknown</span>
  </xsl:otherwise>
</xsl:choose>

<xsl:for-each select="item">
  <li>
    <xsl:value-of select="position()"/>.
    <xsl:value-of select="title"/>
  </li>
</xsl:for-each>
  • xsl:if has no else branch; use xsl:choose when you need one.
  • current-date() is XSLT 2.0. In 1.0 you pass the date in as a parameter from the calling program.
  • Inside for-each the context changes, so apply-templates and value-of are relative to the item.
  • Prefer apply-templates over for-each when the nodes have their own template — the loop version duplicates logic that belongs in a template.

Variables are bindings, not slots

<xsl:variable name="items" select="//item"/>
<xsl:variable name="total" select="sum($items/price)"/>
<xsl:variable name="label">
  <xsl:choose>
    <xsl:when test="count($items) = 0">No items</xsl:when>
    <xsl:otherwise><xsl:value-of select="count($items)"/> items</xsl:otherwise>
  </xsl:choose>
</xsl:variable>

<!-- Result tree fragment: use it as a string, not as a node-set -->
<xsl:value-of select="$label"/>

<!-- This is an error in XSLT 1.0: cannot assign twice -->
<!-- <xsl:variable name="total" select="0"/> -->
ConstructScopeSet by
Top-level xsl:variableWhole stylesheetThe stylesheet, evaluated at the root
Template-level xsl:variableThat templateThe stylesheet, evaluated once
xsl:param at top levelWhole stylesheetExternal caller or a default value
xsl:param in a templateThat templatewith-param or a default
with-paramThe called template onlyThe calling template
# Passing parameters in from the environment
xsltproc --stringparam locale en-GB --param limit 25 transform.xsl input.xml
java -cp saxon-he.jar net.sf.saxon.Transform -s:input.xml -xsl:t.xsl \
     -o:out.html locale=en-GB limit=25
⚠️
A variable whose content is created with xsl:variable and child instructions instead of select is a result tree fragment in XSLT 1.0. You can string it, but you cannot query it with // or a predicate. Convert it with an extension function or restructure the stylesheet around templates.

FAQ

How do I build up a string in a loop?
You cannot append to a variable. Recursion with a named template that carries an accumulator parameter is the 1.0 pattern, and string-join over a sequence is the 2.0 answer.
Are global variables recomputed for every node?
No. A global variable is evaluated once, in the context of the document root, and reused. That is why using position() at global scope is meaningless.

Match patterns versus select expressions Debugging and testing stylesheets

Last refreshed 2026-09-18.