XML Tutorial · XPath, XSLT and XQuery · Lesson 15 of 28

XSLT: Advanced Techniques

The identity transform, modes, parameters, keys and grouping, named templates, and what XSLT 2.0 and 3.0 add.

Create a free account to save this page, take notes and track your progress. Sign in

The identity transform

Copy everything unchanged, then override only what should change. It's the starting point of most real stylesheets:

Identity transform with overridesTry it yourself
from lxml import etree

shop = etree.fromstring(b"""<shop>
  <product sku="TEA-01" category="drinks"><name>Green tea</name><price>3.50</price><stock>40</stock></product>
  <product sku="COF-02" category="drinks"><name>Coffee beans</name><price>9.90</price><stock>0</stock></product>
  <product sku="MUG-07" category="kitchen"><name>Camel mug</name><price>12.00</price><stock>15</stock></product>
  <product sku="PAN-03" category="kitchen"><name>Frying pan</name><price>29.00</price><stock>4</stock></product>
</shop>""")
transform = etree.XSLT(etree.fromstring(b"""
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>
  <!-- identity: copy every node and attribute -->
  <xsl:template match="@* | node()">
    <xsl:copy><xsl:apply-templates select="@* | node()"/></xsl:copy>
  </xsl:template>
  <!-- overrides -->
  <xsl:template match="stock"/>                                    <!-- drop stock -->
  <xsl:template match="@category">                                 <!-- rename an attribute -->
    <xsl:attribute name="type"><xsl:value-of select="."/></xsl:attribute>
  </xsl:template>
  <xsl:template match="product[stock = 0]"/>                       <!-- drop sold-out products -->
</xsl:stylesheet>"""))
print(etree.tostring(transform(shop)).decode())

Output

<shop><product sku="TEA-01" type="drinks"><name>Green tea</name><price>3.50</price></product><product sku="MUG-07" type="kitchen"><name>Camel mug</name><price>12.00</price></product><product sku="PAN-03" type="kitchen"><name>Frying pan</name><price>29.00</price></product></shop>

Parameters, keys and grouping

  • xsl:param takes values from the caller (xslt(doc, currency="'EUR'") in lxml; note the quotes, because the value is an XPath expression).
  • xsl:key builds an index for fast lookups: key('by-category', 'drinks').
  • XSLT 1.0 has no grouping instruction; the classic Muenchian method uses a key and picks the first node of each group. XSLT 2.0 replaced it with xsl:for-each-group.
Parameters, keys and groupingTry it yourself
from lxml import etree

shop = etree.fromstring(b"""<shop>
  <product sku="TEA-01" category="drinks"><name>Green tea</name><price>3.50</price><stock>40</stock></product>
  <product sku="COF-02" category="drinks"><name>Coffee beans</name><price>9.90</price><stock>0</stock></product>
  <product sku="MUG-07" category="kitchen"><name>Camel mug</name><price>12.00</price><stock>15</stock></product>
  <product sku="PAN-03" category="kitchen"><name>Frying pan</name><price>29.00</price><stock>4</stock></product>
</shop>""")
report = etree.XSLT(etree.fromstring(b"""
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>
  <xsl:param name="currency" select="'USD'"/>
  <xsl:key name="by-category" match="product" use="@category"/>
  <xsl:template match="/shop">
    <!-- Muenchian grouping: the first product of each category -->
    <xsl:for-each select="product[generate-id() = generate-id(key('by-category', @category)[1])]">
      <xsl:variable name="group" select="key('by-category', @category)"/>
      <xsl:value-of select="concat(@category, ': ', count($group), ' products, ',
        format-number(sum($group/price), '0.00'), ' ', $currency, '&#10;')"/>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>"""))
print(str(report(shop)), end="")
print(str(report(shop, currency="'EUR'")), end="")

Output

drinks: 2 products, 13.40 USD
kitchen: 2 products, 41.00 USD
drinks: 2 products, 13.40 EUR
kitchen: 2 products, 41.00 EUR

Modes and named templates

mode="toc" lets the same nodes be processed differently in different passes (a table of contents, then the body). xsl:call-template name="…" calls a template like a function.

XSLT 2.0 and 3.0

Feature 1.0 2.0 / 3.0
Grouping Muenchian keys xsl:for-each-group
Functions Named templates xsl:function, higher-order functions (3.0)
Several outputs Extensions xsl:result-document
Regular expressions No xsl:analyze-string, matches(), replace()
JSON No json-to-xml(), xml-to-json(), maps and arrays (3.0)
Huge files Whole tree in memory Streaming (3.0)

Saxon implements XSLT 3.0 for Java, .NET, C, Python (saxonche) and JavaScript (SaxonJS).

Test yourself

What does the identity transform do on its own?

  1. Copies the input document unchanged
  2. Deletes every element
  3. Validates the input
  4. Converts XML to JSON
Show answer

Answer: Copies the input document unchanged

It matches every node and copies it, so you only write templates for the parts that change.

Was this page helpful?