How to Use matches, replace, and tokenize in XSLT 2.0?

XSLT 2.0 introduces native support for regular expressions through standard XPath 2.0 functions, eliminating the need for complex recursive templates or proprietary extension functions when parsing text. This guide covers how matches(), replace(), and tokenize() operate, their underlying XML Schema-based regex engine, and practical examples demonstrating text validation, pattern substitution, and string splitting.

The Regular Expression Engine in XSLT 2.0

XSLT 2.0 regular expressions follow the W3C XML Schema regular expression dialect extended with Perl-like features. All three primary functions support an optional flags parameter:

1. Pattern Testing with matches()

The matches() function evaluates whether an input string contains a match for a specified pattern, returning a boolean true() or false().

Syntax

matches($input as xs:string?, $pattern as xs:string) as xs:boolean
matches($input as xs:string?, $pattern as xs:string, $flags as xs:string) as xs:boolean

Usage and Behavior

Unlike the XML Schema pattern facet, which enforces a full-string match by default, matches() checks for a substring match unless explicitly anchored with ^ and $.

<xsl:if test="matches(@code, '^[A-Z]{3}-\d{4}$')">
    <xsl:text>Valid Product Code</xsl:text>
</xsl:if>

Case-insensitive matching can be achieved with the "i" flag:

<xsl:value-of select="matches(title, 'xslt', 'i')"/>

2. String Substitution with replace()

The replace() function searches an input string for a pattern and substitutes all occurrences with a replacement string.

Syntax

replace($input as xs:string?, $pattern as xs:string, $replacement as xs:string) as xs:string
replace($input as xs:string?, $pattern as xs:string, $replacement as xs:string, $flags as xs:string) as xs:string

Capturing Groups and Backreferences

The replacement string can reference captured groups from the pattern using $1, $2, through $n. A literal dollar sign must be escaped as \$, and a literal backslash must be escaped as \\.

<!-- Converts 'YYYY-MM-DD' to 'DD/MM/YYYY' -->
<xsl:value-of select="replace(date, '^(\d{4})-(\d{2})-(\d{2})$', '$3/$2/$1')"/>

If the pattern matches multiple occurrences across the string, replace() replaces every non-overlapping instance:

<!-- Normalizes all consecutive whitespace characters into a single space -->
<xsl:value-of select="replace(description, '\s+', ' ')"/>

3. String Splitting with tokenize()

The tokenize() function splits an input string into a sequence of strings (xs:string*) using a regex pattern as the delimiter.

Syntax

tokenize($input as xs:string?, $pattern as xs:string) as xs:string*
tokenize($input as xs:string?, $pattern as xs:string, $flags as xs:string) as xs:string*

Processing Sequences

Because tokenize() returns an XPath sequence, the result can be iterated using xsl:for-each or queried using sequence functions.

<xsl:variable name="keywords" select="tokenize('apple, banana; orange,grape', '[,;]\s*')"/>

<keywords>
    <xsl:for-each select="$keywords">
        <keyword><xsl:value-of select="."/></keyword>
    </xsl:for-each>
</keywords>

Handling Edge Cases

If the delimiter pattern matches at the start or end of the string, or matches consecutive occurrences, tokenize() produces empty strings ("") in the sequence. If the input string is an empty sequence or zero-length string, tokenize() returns an empty sequence.

Function Comparison

Function Return Type Primary Purpose Common Use Case
matches() xs:boolean Evaluates if pattern exists Schema validation, conditional logic
replace() xs:string Substitutes matched text Reformatting dates, stripping tags, cleanup
tokenize() xs:string* Splits string by delimiter regex Parsing CSV/TSV data, token processing

XSLT 2.0 also includes the <xsl:analyze-string> instruction for scenarios requiring conditional processing of matched and non-matched substrings, complementing the basic functional capabilities provided by matches(), replace(), and tokenize().