How to Evaluate Dynamic XPath in XSLT 3.0?

XSLT 3.0 introduced the <xsl:evaluate> instruction, providing a standard, native mechanism to construct and execute dynamic XPath expressions at runtime without relying on proprietary vendor extensions like saxon:evaluate or EXSLT. This article outlines the architecture and syntax of <xsl:evaluate>, demonstrates how to supply context items and dynamic parameters, and details essential security and performance practices for running dynamic expressions in XML pipelines.

The Need for Dynamic XPath in XSLT

In earlier versions of XSLT (1.0 and 2.0), all XPath expressions in templates, select attributes, and match patterns had to be static strings known at stylesheet compile time. When applications required configurable business rules, user-defined filtering, or runtime path resolution from external configuration files, developers were forced to generate and compile stylesheets on the fly or rely on non-standard extension functions.

The <xsl:evaluate> instruction standardizes dynamic evaluation across all compliant XSLT 3.0 processors. It takes an XPath expression supplied as a runtime string, parses it, evaluates it against a specified context, and returns the result to the transformation.

Syntax and Key Attributes

The basic syntax of <xsl:evaluate> revolves around the xpath attribute along with several optional configuration attributes:

<xsl:evaluate xpath="string-expression"
              as="sequence-type"
              context-item="expression"
              namespace-context="element-node"
              schema-aware="yes | no">
    <xsl:with-param name="qname" select="expression"/>
</xsl:evaluate>

Practical Example: Dynamic Field Filtering

Consider an XML document containing employee records where the filtering criterion is passed dynamically as a parameter to the stylesheet.

Input XML

<company>
    <employee id="101" department="Engineering" active="true">
        <name>Alice Smith</name>
        <salary>95000</salary>
    </employee>
    <employee id="102" department="Marketing" active="false">
        <name>Bob Jones</name>
        <salary>72000</salary>
    </employee>
    <employee id="103" department="Engineering" active="true">
        <name>Charlie Brown</name>
        <salary>88000</salary>
    </employee>
</company>

XSLT 3.0 Stylesheet

<xsl:stylesheet version="3.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs">

    <xsl:output method="xml" indent="yes"/>
    
    <!-- Dynamic filter expression passed as parameter -->
    <xsl:param name="filterCriteria" as="xs:string" select="'@department = ''Engineering'' and salary > 90000'"/>

    <xsl:template match="/company">
        <filtered-employees>
            <xsl:for-each select="employee">
                <xsl:variable name="matches" as="xs:boolean">
                    <xsl:evaluate xpath="$filterCriteria" context-item="." as="xs:boolean"/>
                </xsl:variable>
                
                <xsl:if test="$matches">
                    <xsl:copy-of select="."/>
                </xsl:if>
            </xsl:for-each>
        </filtered-employees>
    </xsl:template>

</xsl:stylesheet>

Passing Variables Using <xsl:with-param>

Dynamic expressions often need access to values calculated inside the stylesheet. Rather than concatenating raw values into the dynamic XPath string, parameters should be passed explicitly using child <xsl:with-param> elements. Inside the dynamic XPath string, these parameters are referenced with standard variable syntax ($paramName).

<xsl:variable name="threshold" select="80000" as="xs:integer"/>
<xsl:variable name="dynamicQuery" select="'salary >= $minSalary'" as="xs:string"/>

<xsl:evaluate xpath="$dynamicQuery" context-item="employee" as="xs:boolean">
    <xsl:with-param name="minSalary" select="$threshold"/>
</xsl:evaluate>

This approach avoids string-escaping issues, preserves data types without unnecessary conversions to strings, and prevents syntax breakage caused by unexpected characters.

Managing Namespaces

Dynamic XPath expressions that reference prefixed elements or attributes require namespace resolution. By default, <xsl:evaluate> uses the static in-scope namespaces of the <xsl:evaluate> element itself.

When evaluating paths defined in external documents with arbitrary namespaces, the namespace-context attribute can point to an element node in the source document to inherit its active namespace bindings:

<xsl:evaluate xpath="$externalXPath" 
              context-item="." 
              namespace-context="/*"/>

Performance and Security Considerations

While <xsl:evaluate> adds significant flexibility, it introduces specific operational trade-offs:

  1. Compilation Overhead: Dynamic expressions cannot be pre-compiled during the initial stylesheet compilation phase. The processor must parse and compile the expression at runtime. When executed repeatedly within loops, some processors cache compiled forms of identical dynamic strings, but dynamic evaluation remains slower than static XPath.
  2. XPath Injection: Constructing XPath strings via raw string concatenation using untrusted user inputs can lead to XPath injection attacks, potentially exposing sensitive nodes or executing unauthorized operations. Parameter passing via <xsl:with-param> should always be preferred over manual string concatenation.
  3. Type Safety: Unchecked dynamic expressions can yield runtime type mismatches. Always declare the as attribute to validate the output sequence against the stylesheet’s expected schema types.