Instructions Reference
Node Construction
xsl:element, xsl:attribute, xsl:namespace, xsl:document — building nodes with dynamic names
#Node Construction
Most stylesheets write literal result elements directly, such as <div>, <span>, or <product>. When the element or attribute name is not known until runtime, you need dynamic node construction. XSLT provides four instructions for this, plus a shorthand called attribute value templates (AVTs) that you will use often.
For C# developers: several node-construction features correspond to familiar .NET APIs. An AVT corresponds to string interpolation, such as
$"/products/{product.Id}".expand-text="yes"extends that interpolation to text content, so the whole stylesheet reads like Razor or C# string interpolation, such as$"Product: {name}, Price: ${price:N2}".xsl:elementcorresponds tonew XElement(fieldName, value), where theXElementconstructor takes the element name as a string parameter.xsl:attributecorresponds tonew XAttribute(attrName, value), or to setting a property dynamically through reflection. Theseparatorattribute corresponds tostring.Join(" ", classList.Where(c => c != null))when building a CSS class list from conditional parts.xsl:namespacecorresponds tonew XAttribute(XNamespace.Xmlns + "xsi", uri), manually adding a namespace declaration to anXElement.xsl:documentcorresponds tonew XDocument(new XElement("products", ...)), constructing an in-memory XML document for further processing.
#Contents
#Attribute Value Templates (AVTs)
Before covering the dynamic construction instructions, you need to understand AVTs. AVTs are the most common way to put computed values into output attributes.
An AVT is any attribute on a literal result element (or on certain XSLT instruction attributes) that contains {expression} placeholders. The processor evaluates the XPath expression and substitutes the string result.
<a href="/products/{@id}">
<xsl:value-of select="name"/>
</a>
Given <product id="WP-001"><name>Widget Pro</name></product>, this produces:
<a href="/products/WP-001">Widget Pro</a>
#Multiple Expressions in One Attribute
You can mix literal text and multiple expressions:
<img src="/images/{@category}/{@id}.png"
alt="{name} - {price}"
class="product {if (@on-sale = 'true') then 'sale' else 'regular'}"/>
#Escaping Curly Braces
To produce a literal { or } in an AVT, double it:
<!-- Producing CSS or JavaScript that contains braces -->
<style>
.product-{@id} {{ color: red; }}
</style>
Output:
<style>
.product-WP-001 { color: red; }
</style>
#Where AVTs Work
AVTs are available on:
-
Literal result element attributes —
<div class="{$class}">(the most common use). -
Certain XSLT instruction attributes — marked in the spec as "attribute value template." For example,
xsl:element/@name,xsl:attribute/@name,xsl:result-document/@href,xsl:sort/@order. -
Not on
select,test, ormatchattributes — those are already XPath expressions.
#Common AVT Patterns
<!-- Conditional CSS class -->
<tr class="{if (position() mod 2 = 0) then 'even' else 'odd'}">
<td><xsl:value-of select="name"/></td>
</tr>
<!-- Data attributes from XML values -->
<div data-id="{@id}" data-category="{@category}">
<xsl:apply-templates/>
</div>
<!-- URL construction -->
<link rel="stylesheet" href="{$base-url}/css/{$theme}.css"/>
<!-- Computed id for anchor links -->
<section id="{translate(lower-case(name), ' ', '-')}">
<h2><xsl:value-of select="name"/></h2>
</section>
#Text Value Templates
XSLT 3.0 extends the AVT concept to text content with expand-text="yes". When enabled, {expression} works inside text nodes, not only in attributes.
<xsl:template match="product" expand-text="yes">
<div class="product">
<h3>{name}</h3>
<p class="price">${format-number(price, '#,##0.00')}</p>
<p class="sku">SKU: {@id}</p>
</div>
</xsl:template>
This is equivalent to writing <xsl:value-of select="name"/> everywhere, but it is more readable. You can enable it on any element, such as xsl:stylesheet, xsl:template, or a literal result element. It then applies to all descendant text nodes.
#Enabling Globally
Most stylesheets enable it on the root element:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
expand-text="yes">
<!-- All templates can now use {expression} in text -->
</xsl:stylesheet>
#Escaping in Text Value Templates
Just like AVTs, double the braces to produce literal { and }:
<xsl:template match="product" expand-text="yes">
<script>
const product = {{ "id": "{@id}", "name": "{name}" }};
</script>
</xsl:template>
Output:
<script>
const product = { "id": "WP-001", "name": "Widget Pro" };
</script>
#xsl:element
xsl:element creates an element whose name is computed at runtime. Use it when you cannot write the element name as a literal in the stylesheet.
<xsl:element name="{$element-name}">
<!-- content -->
</xsl:element>
#Basic Usage
<!-- Convert a "field" element into an element whose name comes from the @name attribute -->
<xsl:template match="field">
<xsl:element name="{@name}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
Given:
<record>
<field name="firstName">Alice</field>
<field name="lastName">Smith</field>
<field name="email">[email protected]</field>
</record>
Output:
<firstName>Alice</firstName>
<lastName>Smith</lastName>
<email>[email protected]</email>
#Namespace Handling
The namespace attribute specifies the namespace URI for the constructed element:
<!-- Create element in a specific namespace -->
<xsl:element name="atom:feed" namespace="http://www.w3.org/2005/Atom">
<xsl:element name="atom:title" namespace="http://www.w3.org/2005/Atom">
<xsl:value-of select="$site-title"/>
</xsl:element>
</xsl:element>
Output:
<atom:feed xmlns:atom="http://www.w3.org/2005/Atom">
<atom:title>My Product Catalog</atom:title>
</atom:feed>
If you omit namespace, the element inherits the namespace from the prefix used in the name attribute (resolved against in-scope namespace declarations). If there is no prefix, the element is in the default namespace of the stylesheet at that point.
#The use-attribute-sets Attribute
xsl:element can apply named attribute sets — predefined groups of attributes:
<xsl:attribute-set name="table-defaults">
<xsl:attribute name="class">data-table</xsl:attribute>
<xsl:attribute name="cellpadding">4</xsl:attribute>
<xsl:attribute name="border">1</xsl:attribute>
</xsl:attribute-set>
<xsl:template match="data">
<xsl:element name="table" use-attribute-sets="table-defaults">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
Output:
<table class="data-table" cellpadding="4" border="1">
<!-- content -->
</table>
Attribute sets work on literal result elements too: <table xsl:use-attribute-sets="table-defaults">.
#Practical Example: Generic XML-to-HTML Table
<!-- Turn any XML structure into an HTML table -->
<xsl:template match="*[*]" mode="auto-table">
<table>
<thead>
<tr>
<xsl:for-each select="*[1]/*">
<th><xsl:value-of select="local-name()"/></th>
</xsl:for-each>
</tr>
</thead>
<tbody>
<xsl:for-each select="*">
<tr>
<xsl:for-each select="*">
<xsl:element name="{if (position() = 1) then 'th' else 'td'}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</tr>
</xsl:for-each>
</tbody>
</table>
</xsl:template>
Here xsl:element chooses between <th> and <td> based on position — something you cannot do with a literal element.
#xsl:attribute
xsl:attribute creates an attribute on the parent element. Its name can be computed at runtime.
<xsl:attribute name="class">product-card</xsl:attribute>
#Why Not Just Use AVTs?
For static attribute names, AVTs are always cleaner:
<!-- Preferred: AVT -->
<div class="product {$extra-class}">...</div>
<!-- Equivalent but verbose: xsl:attribute -->
<div>
<xsl:attribute name="class">product <xsl:value-of select="$extra-class"/></xsl:attribute>
...
</div>
Use xsl:attribute when:
-
The attribute name is dynamic:
<xsl:attribute name="{$attr-name}">...</xsl:attribute> -
The attribute value requires complex construction: multiple instructions build the value
-
You are conditionally adding an attribute:
<input type="checkbox">
<xsl:if test="@selected = 'true'">
<xsl:attribute name="checked">checked</xsl:attribute>
</xsl:if>
</input>
#Dynamic Attribute Names
<!-- Convert XML attributes to data-* attributes -->
<xsl:template match="product">
<div>
<xsl:for-each select="@*">
<xsl:attribute name="data-{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:for-each>
<xsl:apply-templates/>
</div>
</xsl:template>
Given <product id="WP-001" category="electronics" status="active">, output:
<div data-id="WP-001" data-category="electronics" data-status="active">
<!-- content -->
</div>
#The separator Attribute
When the content of xsl:attribute produces a sequence, the separator attribute controls how items are joined. This is useful for multi-value attributes like CSS classes:
<div>
<xsl:attribute name="class" separator=" ">
<xsl:text>product-card</xsl:text>
<xsl:if test="@on-sale = 'true'">
<xsl:text>on-sale</xsl:text>
</xsl:if>
<xsl:if test="@featured = 'true'">
<xsl:text>featured</xsl:text>
</xsl:if>
<xsl:if test="stock = 0">
<xsl:text>out-of-stock</xsl:text>
</xsl:if>
</xsl:attribute>
<xsl:apply-templates/>
</div>
A product that is on sale and featured produces:
<div class="product-card on-sale featured">...</div>
Without separator, the default behavior concatenates everything with no delimiter. The separator attribute handles absent conditional items automatically, without extra spaces or dangling separators.
#Placement Rules
xsl:attribute must appear before any child nodes in the parent element. This fails:
<!-- ERROR: attribute after child content -->
<div>
<p>Some content</p>
<xsl:attribute name="class">container</xsl:attribute> <!-- too late! -->
</div>
The attribute instruction must come first:
<div>
<xsl:attribute name="class">container</xsl:attribute>
<p>Some content</p>
</div>
#xsl:namespace
xsl:namespace creates a namespace declaration on the parent element. This instruction is rarely needed, because the serializer usually adds xmlns declarations automatically when you create an element in a namespace. Occasionally, however, you need explicit control.
#When You Need xsl:namespace
The primary use case is when the output document must contain namespace declarations that are not used by any element or attribute in that document. This happens with:
-
Schema-instance declarations (
xsi:schemaLocation) -
Namespace-aware content embedded in attribute values (like XPath expressions in Schematron)
-
XSLT stylesheets that generate other XSLT stylesheets
<xsl:template match="/">
<root>
<xsl:namespace name="xsi" select="'http://www.w3.org/2001/XMLSchema-instance'"/>
<xsl:attribute name="xsi:schemaLocation"
namespace="http://www.w3.org/2001/XMLSchema-instance">
http://example.com/schema catalog.xsd
</xsl:attribute>
<xsl:apply-templates/>
</root>
</xsl:template>
Output:
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://example.com/schema catalog.xsd">
<!-- content -->
</root>
#Dynamic Namespace Prefix
The name attribute specifies the prefix. The select attribute, or the content, specifies the URI:
<!-- Add a namespace with a dynamic prefix -->
<xsl:namespace name="{$prefix}" select="$namespace-uri"/>
Use an empty name to set the default namespace:
<!-- Set the default namespace -->
<xsl:namespace name="" select="'http://www.w3.org/1999/xhtml'"/>
#xsl:document
xsl:document creates a new document node, the root of a tree. Use it to construct a temporary tree that you want to process further. For example, you can build an intermediate XML structure and then transform it.
<xsl:variable name="temp-doc" as="document-node()">
<xsl:document>
<products>
<xsl:for-each select="//product[@status = 'active']">
<xsl:sort select="name"/>
<product>
<name><xsl:value-of select="name"/></name>
<price><xsl:value-of select="price"/></price>
</product>
</xsl:for-each>
</products>
</xsl:document>
</xsl:variable>
<!-- Now process the temporary document -->
<xsl:apply-templates select="$temp-doc/products/product"/>
#When to Use xsl:document
In practice, xsl:document is uncommon because a variable with content already creates a document node:
<!-- These are nearly equivalent -->
<xsl:variable name="temp">
<products>...</products>
</xsl:variable>
<xsl:variable name="temp" as="document-node()">
<xsl:document>
<products>...</products>
</xsl:document>
</xsl:variable>
The explicit xsl:document form is useful when:
-
You need a document node inside a function that returns
document-node():
<xsl:function name="my:build-lookup" as="document-node()">
<xsl:param name="items" as="element()*"/>
<xsl:document>
<lookup>
<xsl:for-each select="$items">
<entry key="{@id}" value="{name}"/>
</xsl:for-each>
</lookup>
</xsl:document>
</xsl:function>
-
You need validation —
xsl:documentsupports thevalidationandtypeattributes for schema validation of the constructed tree. -
You are building a multi-pass transformation where the first pass constructs an intermediate document and the second pass transforms it to final output.
#Multi-Pass Transformation Example
<xsl:template match="/">
<!-- Pass 1: Build normalized intermediate document -->
<xsl:variable name="normalized" as="document-node()">
<xsl:document>
<catalog>
<xsl:for-each select="//product">
<xsl:sort select="@category"/>
<xsl:sort select="name"/>
<item category="{@category}"
name="{name}"
price="{price}"
in-stock="{stock > 0}"/>
</xsl:for-each>
</catalog>
</xsl:document>
</xsl:variable>
<!-- Pass 2: Render the normalized document as HTML -->
<html>
<body>
<xsl:for-each-group select="$normalized/catalog/item" group-by="@category">
<section>
<h2><xsl:value-of select="current-grouping-key()"/></h2>
<ul>
<xsl:for-each select="current-group()">
<li class="{if (@in-stock = 'true') then 'available' else 'sold-out'}">
<xsl:value-of select="@name"/> — $<xsl:value-of select="@price"/>
</li>
</xsl:for-each>
</ul>
</section>
</xsl:for-each-group>
</body>
</html>
</xsl:template>
#When to Use Dynamic Construction vs. Literal Elements
Use literal result elements (the default) whenever the element name is known at stylesheet-authoring time:
<!-- Preferred: literal result element -->
<div class="product">
<h3><xsl:value-of select="name"/></h3>
</div>
Use xsl:element / xsl:attribute when:
|
Scenario |
Example |
|---|---|
|
Element name is data-driven |
|
|
Attribute name is data-driven |
|
|
Element name depends on a condition |
|
|
You need programmatic namespace control |
|
|
Generating XSLT from XSLT |
Element names collide with the XSLT namespace |
|
Building a generic/reflective transform |
Processing arbitrary XML structures |
#Decision Flowchart
-
Is the element or attribute name fixed? Use a literal result element with AVTs for attribute values.
-
Is the name computed from data? Use
xsl:elementorxsl:attribute. -
Is the name one of a small, known set? Consider
xsl:choosewith literal elements. It is more readable thanxsl:elementwhen there are only two or three alternatives.
<!-- For a small set of known names, choose + literal is clearer -->
<xsl:choose>
<xsl:when test="@level = '1'"><h1><xsl:apply-templates/></h1></xsl:when>
<xsl:when test="@level = '2'"><h2><xsl:apply-templates/></h2></xsl:when>
<xsl:when test="@level = '3'"><h3><xsl:apply-templates/></h3></xsl:when>
<xsl:otherwise><p><xsl:apply-templates/></p></xsl:otherwise>
</xsl:choose>
<!-- For an open-ended set, xsl:element is necessary -->
<xsl:element name="h{@level}">
<xsl:apply-templates/>
</xsl:element>
#C# Parallel Summary
|
XSLT |
C# (LINQ to XML) |
C# (String Interpolation) |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
N/A |
|