Advanced matching and sorting

Match pattern priorities, multi-key sorting with data types, key-based lookup, and grouping without a group-by instruction.

How the processor chooses a rule

Several templates can match the same node. XSLT resolves the conflict by priority: a more specific pattern wins, and explicit priority attributes settle ties. If two rules have equal priority, the one declared later wins.

PatternRelative priorityWhy
book0A plain name test
book[1]0.5A predicate makes it more specific
catalog/book0.5A longer path step
*-0.5Matches anything, so it loses
node()-0.5Lowest of the built-in-style tests
book[@id='bk-101']0.5Predicate on an attribute
Explicit priority="9"9You decide, overriding the default
<xsl:template match="book[price &gt; 50]" priority="3">
  <tr class="expensive"><xsl:apply-templates/></tr>
</xsl:template>

<xsl:template match="book">
  <tr><xsl:apply-templates/></tr>
</xsl:template>
⚠️
Relying on declaration order is fragile: reordering templates silently changes output. When two patterns genuinely overlap, state the priority explicitly instead of trusting the tie-break.

Sorting with more than one key

<xsl:apply-templates select="book">
  <xsl:sort select="price" data-type="number" order="ascending"/>
  <xsl:sort select="title" lang="en" case-order="upper-first"/>
</xsl:apply-templates>

<!-- inside for-each, direct children of the current node are pre-sorted too -->
<xsl:for-each select="catalog/book">
  <xsl:sort select="@id"/>
  <xsl:value-of select="title"/>
</xsl:for-each>
  • data-type="number" is essential: the default is text, so 100 sorts before 20.
  • Multiple xsl:sort elements are applied in order, so the first is the primary key.
  • lang and case-order control collation for accented and mixed-case text.
  • Sorting changes the processing order, not the document order used by position-based predicates.

Keys and grouping

A key builds an index across the source document, turning a repeated deep search into a hash lookup. Grouping has no dedicated instruction in XSLT 1.0 — the standard technique is the Muenchian method, which uses a key plus generate-id() to find the first node of each group.

<xsl:key name="books-by-genre" match="book" use="genre"/>

<xsl:template match="catalog">
  <!-- one row per distinct genre -->
  <xsl:for-each select="book[generate-id() =
        generate-id(key('books-by-genre', genre)[1])]">
    <h2><xsl:value-of select="genre"/></h2>
    <ul>
      <!-- every book in that genre -->
      <xsl:for-each select="key('books-by-genre', genre)">
        <li><xsl:value-of select="title"/></li>
      </xsl:for-each>
    </ul>
  </xsl:for-each>
</xsl:template>
<!-- XSLT 2.0 and later replace all of the above with two lines -->
<xsl:for-each-group select="book" group-by="genre">
  <h2><xsl:value-of select="current-grouping-key()"/></h2>
  <ul>
    <xsl:for-each select="current-group()">
      <li><xsl:value-of select="title"/></li>
    </xsl:for-each>
  </ul>
</xsl:for-each-group>

FAQ

Why is my numeric sort wrong?
The default data-type is text, so values are compared as strings. Add data-type="number" on every sort key whose value is numeric.
When should I use a key?
Whenever the same lookup repeats, or when you would otherwise scan with // from the current node. One pass builds the index and every subsequent lookup is constant time.

Templates and apply-templates Predicates and functions

Last refreshed 2026-09-18.