#Node Construction
Most of the time you write literal result elements directly in your stylesheet — <div>, <span>, <product>. But when the element or attribute name is not known until runtime, you need dynamic node construction. XSLT provides four instructions for this, plus a powerful shorthand called attribute value templates (AVTs) that you will use constantly.
#Contents
#Attribute Value Templates (AVTs)
Before covering the dynamic construction instructions, you need to understand AVTs — they are the single most-used feature for putting 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>
C# parallel: This is exactly string interpolation — $"/products/{product.Id}".
#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 too — not just 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 far more readable. You can enable it on any element — xsl:stylesheet, xsl:template, or any literal result element — and it 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>
C# parallel: expand-text="yes" turns your entire stylesheet into something that feels like Razor or C# string interpolation — $"Product: {name}, Price: ${price:N2}".
#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
Creates an element whose name is computed at runtime. Use this when you cannot write the element name as a literal in your 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>
C# parallel: new XElement(fieldName, value) — the XElement constructor takes the element name as a string parameter.
#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
Creates an attribute on the parent element, with a name that 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 with multiple instructions
-
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>
C# parallel: new XAttribute(attrName, value) or setting a property dynamically with reflection.
#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 is to concatenate everything with no delimiter. The separator attribute is particularly helpful because it automatically handles the case where some conditional items are absent — no extra spaces or dangling separators.
C# parallel: string.Join(" ", classList.Where(c => c != null)) — building a CSS class list from conditional parts.
#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
Creates a namespace declaration on the parent element. This is rarely needed because namespace declarations are usually handled automatically — when you create an element in a namespace, the serializer adds the necessary xmlns declarations. But occasionally 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 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'"/>
C# parallel: new XAttribute(XNamespace.Xmlns + "xsi", "http://www.w3.org/2001/XMLSchema-instance") — manually adding namespace declarations to an XElement.
#xsl:document
Creates a new document node (the root of a tree). This is primarily useful for constructing temporary trees that you want to process further — for example, building an intermediate XML structure and then transforming 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.
C# parallel: new XDocument(new XElement("products", ...)) — constructing an in-memory XML document for further processing.
#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/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 |
|