Chapter 63: XSLT
1. What does <xsl:sort> really do?
<xsl:sort> is the only built-in way in XSLT to sort a list of nodes before processing them.
It can only be used inside these two elements:
- <xsl:for-each>
- <xsl:apply-templates>
When you put one or more <xsl:sort> instructions as the first children of these elements, the processor will reorder the selected nodes before executing the loop/template on them.
Very important facts from the very beginning:
- <xsl:sort>must be the first child inside <xsl:for-each> or <xsl:apply-templates> → anything before it is invalid
- You can have multiple <xsl:sort> one after another (multi-level sort)
- Sorting happens before the loop/template body is executed
- The original input order is lost — the nodes are processed in the sorted order
2. Basic syntax – the pattern you will see 95% of the time
|
0 1 2 3 4 5 6 7 8 9 10 11 |
<xsl:for-each select="products/product"> <!-- Sorting instructions come FIRST --> <xsl:sort select="name" order="ascending" data-type="text"/> <li><xsl:value-of select="name"/></li> </xsl:for-each> |
or with <xsl:apply-templates> (more elegant style):
|
0 1 2 3 4 5 6 7 8 |
<xsl:apply-templates select="products/product"> <xsl:sort select="price" order="ascending" data-type="number"/> </xsl:apply-templates> |
3. The most important attributes of <xsl:sort>
| Attribute | Meaning / Possible values | Default value | Most common real usage | Required? |
|---|---|---|---|---|
| select | XPath expression that returns the sort key | . (current node text) | name, price, @date, substring(title,1,1) | Yes |
| order | ascending or descending | ascending | descending for newest-first, highest-price-first | No |
| data-type | text or number | text | number for prices, quantities, years | No |
| case-order | upper-first or lower-first | upper-first | lower-first for case-insensitive sort | No |
| lang | Language code for collation (sorting rules) | system default | en, de, fr… | No |
| collation | (XSLT 2.0+) URI of custom collation rules | — | Very rare in XSLT 1.0 | No |
4. Step-by-step examples – from simple to realistic
Example 1 – Sort product names alphabetically
Input XML
|
0 1 2 3 4 5 6 7 8 9 10 |
<catalog> <product><name>USB-C Hub</name></product> <product><name>Wireless Mouse</name></product> <product><name>Mechanical Keyboard</name></product> </catalog> |
XSLT
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
<xsl:template match="catalog"> <ul> <xsl:for-each select="product"> <xsl:sort select="name" order="ascending" data-type="text"/> <li><xsl:value-of select="name"/></li> </xsl:for-each> </ul> </xsl:template> |
Output
|
0 1 2 3 4 5 6 7 8 9 10 |
<ul> <li>Mechanical Keyboard</li> <li>USB-C Hub</li> <li>Wireless Mouse</li> </ul> |
Example 2 – Sort products by price (numeric!)
Important: without data-type=”number”, “1499” < “349” (string comparison!)
|
0 1 2 3 4 5 6 7 8 9 10 11 |
<xsl:for-each select="product"> <xsl:sort select="price" data-type="number" order="ascending"/> <li> <xsl:value-of select="name"/> – ₹<xsl:value-of select="price"/> </li> </xsl:for-each> |
Output
|
0 1 2 3 4 5 6 7 8 |
<li>USB-C Hub – ₹349.00</li> <li>Wireless Mouse – ₹1499.00</li> <li>Mechanical Keyboard – ₹3999.00</li> |
Example 3 – Multi-level sort (first by category, then by name)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<xsl:for-each select="product"> <!-- First sort key: category --> <xsl:sort select="@category" order="ascending" data-type="text"/> <!-- Second sort key: name --> <xsl:sort select="name" order="ascending" data-type="text"/> <li> [<xsl:value-of select="@category"/>] <xsl:value-of select="name"/> </li> </xsl:for-each> |
Output
|
0 1 2 3 4 5 6 7 8 |
<li>[accessories] USB-C Hub</li> <li>[electronics] Mechanical Keyboard</li> <li>[electronics] Wireless Mouse</li> |
Example 4 – Sort descending by stock (most stocked first)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
<xsl:for-each select="product"> <xsl:sort select="number(stock)" order="descending" data-type="number"/> <li> <xsl:value-of select="name"/> – stock: <xsl:value-of select="stock"/> </li> </xsl:for-each> |
Output
|
0 1 2 3 4 5 6 7 8 |
<li>Wireless Mouse – stock: 45</li> <li>USB-C Hub – stock: 12</li> <li>Mechanical Keyboard – stock: 8</li> |
Example 5 – Sort by first letter of title (case-insensitive)
|
0 1 2 3 4 5 6 7 8 9 10 |
<xsl:for-each select="product"> <xsl:sort select="translate(substring(title,1,1),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')"/> <li><xsl:value-of select="title"/></li> </xsl:for-each> |
→ sorts ignoring case (A,a → same group)
Lesson 5 – Common beginner mistakes & how to avoid them
Mistake 1 Putting <xsl:sort> after other content
|
0 1 2 3 4 5 6 7 8 9 |
<xsl:for-each select="product"> <li>...</li> <xsl:sort select="name"/> <!-- WRONG – too late --> </xsl:for-each> |
Fix — <xsl:sort> must be the first child
Mistake 2 Forgetting data-type=”number” when sorting numbers
|
0 1 2 3 4 5 6 |
<xsl:sort select="price"/> <!-- string sort: 1499 < 349 ! --> |
Fix
|
0 1 2 3 4 5 6 |
<xsl:sort select="price" data-type="number"/> |
Mistake 3 Sorting inside <xsl:template> without <xsl:for-each> or <xsl:apply-templates>
|
0 1 2 3 4 5 6 7 8 |
<xsl:template match="catalog"> <xsl:sort select="product/name"/> <!-- invalid --> </xsl:template> |
Fix — <xsl:sort> is only allowed inside for-each or apply-templates
Lesson 6 – Try yourself exercises (do these!)
- List all products sorted alphabetically by name
- Show products sorted by price ascending (use data-type=”number”)
- Show products sorted by stock descending (most stocked first)
- Sort books first by category ascending, then by title ascending
- Create a numbered list sorted by price descending
- Sort case-insensitively by title (hint: translate())
Summary – <xsl:sort> Quick Reference
| Feature / Question | Answer / Pattern | Important note / trap |
|---|---|---|
| Basic syntax | <xsl:sort select=”name”/> | Must be first child inside for-each or apply-templates |
| Sort by text | <xsl:sort select=”name”/> | Default data-type=”text” |
| Sort by number | <xsl:sort select=”price” data-type=”number”/> | Without number, 1499 < 349 ! |
| Descending order | order=”descending” | Default is ascending |
| Multi-level sort | Multiple <xsl:sort> one after another | First <xsl:sort> is primary key |
| Case-insensitive sort | translate(…, ‘ABC…’, ‘abc…’) | Very common need |
Would you like to continue with one of these next?
- Multi-level / multi-criteria sorting (category → name → price)
- Sorting by calculated value (price after tax, name length…)
- Sorting with xsl:apply-templates vs xsl:for-each
- Case-insensitive & locale-aware sorting
- Real-world patterns — invoice lines by amount, products by category, report rows by date
- Debugging when sorting gives unexpected order
Just tell me which direction feels most useful or interesting for you right now! 😊
