#Dynamic Evaluation
xsl:evaluate (XSLT 3.0) compiles and executes an XPath expression provided as a string at runtime. This is the XSLT equivalent of runtime expression compilation in dynamic languages — powerful, occasionally necessary, and something you should use judiciously.
#Contents
#When You Need Dynamic Evaluation
Most XSLT stylesheets use static XPath expressions — expressions written directly in the stylesheet and known at compile time. But some situations require expressions that are not known until runtime:
-
Configurable transforms: A configuration file specifies which fields to sort by, which elements to extract, or which conditions to apply.
-
Data-driven templates: An XML schema or metadata file describes the structure, and the stylesheet uses it to generate XPath expressions dynamically.
-
User-defined formulas: A spreadsheet or report definition contains user-written expressions.
-
Generic tools: A stylesheet that processes arbitrary XML based on runtime parameters.
C# parallel: The need for dynamic evaluation arises in C# too:
// Roslyn scripting — compile and run C# at runtime
var result = await CSharpScript.EvaluateAsync<int>("1 + 2 * 3");
// Dynamic LINQ — build queries from strings
var filtered = dataSource.Where("Price > @0", 100);
// Expression trees — runtime query construction
Expression<Func<Product, bool>> predicate = BuildFilter(userInput);
var results = products.Where(predicate.Compile());#xsl:evaluate — Syntax and Attributes
#Basic Usage
<!-- Evaluate a simple expression -->
<xsl:variable name="result" select="xsl:evaluate('1 + 2 * 3')"/>
<!-- result: 7 -->
<!-- Evaluate an expression stored in a variable -->
<xsl:variable name="expr" select="'price * quantity'"/>
<xsl:value-of select="xsl:evaluate($expr)"/>#As an Instruction
xsl:evaluate can also be used as an instruction element rather than a function:
<xsl:evaluate xpath="$sort-expression" context-item="."/>#Attributes
|
Attribute |
Description |
Default |
|---|---|---|
|
|
The XPath expression to evaluate (as a string) |
Required |
|
|
Expected return type (like |
|
|
|
The context node for the expression ( |
Current context |
|
|
Element whose in-scope namespaces are used |
Stylesheet element |
|
|
Whether to use schema type information |
|
|
|
Variable bindings (child elements) |
None |
|
|
Base URI for resolving relative URIs |
Static base URI |
#Simple Examples
<!-- Dynamic field access -->
<xsl:param name="field-name" select="'price'"/>
<xsl:value-of select="xsl:evaluate($field-name)"/>
<!-- If context is a product element, returns the value of its price child -->
<!-- Dynamic predicate -->
<xsl:param name="filter" select="'@status = ''active'''"/>
<xsl:for-each select="//product[xsl:evaluate($filter)]">
<xsl:value-of select="name"/>
</xsl:for-each>
<!-- Dynamic sort key -->
<xsl:param name="sort-by" select="'price'"/>
<xsl:for-each select="//product">
<xsl:sort select="xsl:evaluate($sort-by)" data-type="number"/>
<li><xsl:value-of select="name"/></li>
</xsl:for-each>#Specifying Return Type
Use the as attribute to constrain the result type:
<!-- Expect a number -->
<xsl:evaluate xpath="$expression" as="xs:decimal"/>
<!-- Expect a sequence of nodes -->
<xsl:evaluate xpath="$node-selector" as="node()*"/>
<!-- Expect a boolean -->
<xsl:evaluate xpath="$condition" as="xs:boolean"/>If the evaluated expression returns a value that does not match the as type, a type error is raised.
#Binding Variables with xsl:with-param
Dynamic expressions often need access to values from the surrounding context. Use xsl:with-param to bind variables that the expression can reference:
<xsl:evaluate xpath="$expression">
<xsl:with-param name="threshold" select="100"/>
<xsl:with-param name="category" select="$current-category"/>
</xsl:evaluate>The expression can reference these as $threshold and $category:
<!-- If $expression is "price > $threshold and @category = $category" -->
<!-- The variables $threshold and $category are available inside the expression -->#Complete Example: Configurable Filtering
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<!-- Configuration passed as parameters -->
<xsl:param name="filter-expression" select="'price < $max-price'"/>
<xsl:param name="max-price" select="100"/>
<xsl:param name="sort-field" select="'name'"/>
<xsl:param name="sort-order" select="'ascending'"/>
<xsl:template match="catalog">
<filtered-catalog>
<xsl:variable name="filtered" as="element(product)*">
<xsl:for-each select="product">
<xsl:variable name="passes-filter" as="xs:boolean">
<xsl:evaluate xpath="$filter-expression" context-item=".">
<xsl:with-param name="max-price" select="$max-price"/>
</xsl:evaluate>
</xsl:variable>
<xsl:if test="$passes-filter">
<xsl:sequence select="."/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="$filtered">
<xsl:sort select="xsl:evaluate($sort-field)" order="{$sort-order}"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</filtered-catalog>
</xsl:template>
</xsl:stylesheet>#Namespace Resolution
When the dynamic expression uses namespace prefixes, the processor needs to know which namespaces are in scope. The namespace-context attribute specifies an element whose in-scope namespaces are used for resolving prefixes in the expression.
#Using namespace-context
<!-- The expression uses the "fn" prefix — we need to tell the evaluator
which namespace it maps to -->
<xsl:variable name="ns-context" as="element()">
<dummy xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"/>
</xsl:variable>
<xsl:evaluate xpath="'math:sqrt(fn:sum(//price))'"
namespace-context="$ns-context"/>If no namespace-context is provided, the namespaces from the xsl:evaluate element itself are used.
#Practical Pattern: Expressions from a Configuration File
<!-- config.xml -->
<report-config>
<column name="Total Revenue" xpath="format-number(sum(//order/@total), '#,##0.00')"
xmlns:xs="http://www.w3.org/2001/XMLSchema"/>
<column name="Average Order" xpath="format-number(avg(//order/@total), '#,##0.00')"/>
<column name="Largest Order" xpath="max(//order/@total)"/>
</report-config><xsl:template match="/">
<xsl:variable name="config" select="doc('config.xml')/report-config"/>
<xsl:variable name="data" select="doc('orders.xml')"/>
<table>
<thead>
<tr>
<xsl:for-each select="$config/column">
<th><xsl:value-of select="@name"/></th>
</xsl:for-each>
</tr>
</thead>
<tbody>
<tr>
<xsl:for-each select="$config/column">
<td>
<xsl:evaluate xpath="string(@xpath)"
context-item="$data"
namespace-context="."/>
</td>
</xsl:for-each>
</tr>
</tbody>
</table>
</xsl:template>The namespace-context="." ensures that any namespace declarations on the <column> elements are available to the evaluated expression.
#Security Considerations
xsl:evaluate executes arbitrary XPath expressions at runtime. This raises security concerns when the expression comes from untrusted input.
#What an Attacker Can Do
XPath by itself cannot modify the file system or execute system commands. However, an attacker-controlled expression could:
-
Read sensitive data:
doc('/etc/passwd')ordoc('file:///C:/secrets/config.xml') -
Cause denial of service: Expressions with exponential complexity, like deeply nested
forloops -
Access other documents:
collection(),doc(),unparsed-text()can reach files on the server -
Exfiltrate data: If the output goes to the user, sensitive data from other documents could be exposed
#Mitigation Strategies
-
Never evaluate user-provided expressions directly. Instead, offer a controlled set of options:
<!-- DANGEROUS: evaluating user input directly -->
<xsl:evaluate xpath="$user-input"/>
<!-- SAFER: mapping user choices to known expressions -->
<xsl:variable name="allowed-sorts" select="map {
'name': 'name',
'price': 'price',
'date': '@created-date',
'rating': 'avg(review/@score)'
}"/>
<xsl:variable name="sort-expr"
select="($allowed-sorts($user-sort-choice), 'name')[1]"/>
<xsl:for-each select="//product">
<xsl:sort select="xsl:evaluate($sort-expr)"/>
<!-- ... -->
</xsl:for-each>-
Disable xsl:evaluate in the processor configuration. Most XSLT processors allow you to disable
xsl:evaluateentirely. If your transformation does not need it, turn it off. -
Restrict document access. Configure the processor's URI resolver to limit which documents can be loaded.
-
Validate expressions before evaluation. If you must accept dynamic expressions, validate them against an allowlist of functions and axes.
C# parallel: The same concerns apply to runtime code compilation and dynamic LINQ:
// DANGEROUS: executing arbitrary code
var result = await CSharpScript.EvaluateAsync(userInput);
// SAFER: constrain what the script can access
var options = ScriptOptions.Default
.WithReferences(typeof(Math).Assembly)
.WithImports("System.Math");
var result = await CSharpScript.EvaluateAsync<double>(userInput, options);#Use Cases
#Configurable Sort Keys
A report definition specifies which columns to sort by:
<!-- report-definition.xml -->
<report>
<sort-keys>
<sort-key xpath="@department" order="ascending"/>
<sort-key xpath="salary" order="descending" data-type="number"/>
</sort-keys>
</report><xsl:variable name="sort-config"
select="doc('report-definition.xml')//sort-key"/>
<xsl:template match="employees">
<table>
<!-- Dynamic multi-key sort using xsl:evaluate -->
<xsl:for-each select="employee">
<!-- Apply first sort key -->
<xsl:sort select="xsl:evaluate(string($sort-config[1]/@xpath))"
order="{$sort-config[1]/@order}"
data-type="{($sort-config[1]/@data-type, 'text')[1]}"/>
<!-- Apply second sort key -->
<xsl:sort select="xsl:evaluate(string($sort-config[2]/@xpath))"
order="{$sort-config[2]/@order}"
data-type="{($sort-config[2]/@data-type, 'text')[1]}"/>
<tr>
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="@department"/></td>
<td><xsl:value-of select="salary"/></td>
</tr>
</xsl:for-each>
</table>
</xsl:template>#Expression Evaluation in a Spreadsheet
<!-- spreadsheet.xml -->
<spreadsheet>
<cell id="A1" value="100"/>
<cell id="A2" value="200"/>
<cell id="A3" formula="$A1 + $A2"/>
<cell id="A4" formula="$A3 * 0.1"/>
</spreadsheet><xsl:template match="spreadsheet">
<results>
<xsl:for-each select="cell">
<xsl:choose>
<xsl:when test="@formula">
<cell id="{@id}">
<xsl:evaluate xpath="@formula">
<!-- Bind cell references as variables -->
<xsl:for-each select="../cell[@value]">
<xsl:with-param name="{@id}" select="number(@value)"/>
</xsl:for-each>
</xsl:evaluate>
</cell>
</xsl:when>
<xsl:otherwise>
<cell id="{@id}"><xsl:value-of select="@value"/></cell>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</results>
</xsl:template>#Data-Driven Column Extraction
A metadata file describes which fields to extract from a data file:
<!-- fields.xml -->
<fields>
<field label="Customer Name" xpath="customer/name"/>
<field label="Order Total" xpath="format-number(total, '$#,##0.00')"/>
<field label="Status" xpath="if (@status = 'C') then 'Complete' else 'Pending'"/>
</fields><xsl:variable name="fields" select="doc('fields.xml')//field"/>
<xsl:template match="orders">
<table>
<thead>
<tr>
<xsl:for-each select="$fields">
<th><xsl:value-of select="@label"/></th>
</xsl:for-each>
</tr>
</thead>
<tbody>
<xsl:for-each select="order">
<xsl:variable name="current-order" select="."/>
<tr>
<xsl:for-each select="$fields">
<td>
<xsl:evaluate xpath="string(@xpath)" context-item="$current-order"/>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</tbody>
</table>
</xsl:template>#When Not to Use xsl:evaluate
xsl:evaluate should be a tool of last resort. Before reaching for it, consider these alternatives:
#Static XPath Covers Most Cases
If the expression is known at stylesheet-authoring time, write it directly:
<!-- Do NOT do this -->
<xsl:variable name="expr" select="'price * quantity'"/>
<xsl:value-of select="xsl:evaluate($expr)"/>
<!-- Do this instead -->
<xsl:value-of select="price * quantity"/>#Use Higher-Order Functions
If you need to pass behavior as a parameter, XSLT 3.0 supports higher-order functions:
<!-- Instead of passing a string expression for sorting... -->
<xsl:variable name="sort-fn" select="function($item) { $item/price }"/>
<xsl:for-each select="//product">
<xsl:sort select="$sort-fn(.)"/>
<!-- ... -->
</xsl:for-each>#Use Template Matching
If different data shapes need different processing, template matching is more maintainable than dynamic evaluation:
<!-- Instead of evaluating a dynamic expression per type... -->
<xsl:template match="product[@type='physical']">
<xsl:value-of select="weight * shipping-rate"/>
</xsl:template>
<xsl:template match="product[@type='digital']">
<xsl:value-of select="0"/>
</xsl:template>#Performance Implications
xsl:evaluate compiles the expression at runtime, which is slower than pre-compiled static expressions. In a loop processing thousands of items, this overhead can be significant. If possible, evaluate the expression once and store the result in a variable.
C# parallel: This is the same trade-off as Reflection.Invoke() vs. direct method calls, or runtime script compilation vs. compiled code. The dynamic version is flexible but slower.