Jesper Tverskov, July 27, 2005

Identity Template: xsl:copy with recursion

The so-called identity template that copies everything from input.xml to output.xml, element for element, attribute for attribute, is the most important of all templates in combination with templates of exceptions.

The "identity" template was introduced with an example in the XSLT Recommendation itself: [1]

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>

In this tutorial we will look at the very basics of "xsl:copy with recursion". More advanced examples can be found in XSLT books of the "cookbook" type. [2]

1. Identity: Copy with recursion

The above template copies all attributes, @*, and all nodes being children of other nodes, node(): element nodes, text nodes, comment nodes and processing instruction nodes. Everything is copied. "Recursion" in the above case is when the template ends its job by calling other templates including itself.

Copying everything is no fun but becomes very useful when we add exceptions to the copying. It gets useful when we "copy everything but". The step by step approach in the copying is the big trick. For each attribute, element, etc., we can decide to do something else by adding other templates overruling the copying behavior.

2. Better tutorials for beginners

Many books and tutorials about XSLT mention the "identity" or "copy with recursion" template. But most users of XSLT never get to use it for real, because they don't understand it.

Most XSLT beginners are not at ease with node(), xsl:copy and xsl:apply-templates and even less with the "identity template" using all three. Beginners get fast into the habit of using less elegant and less efficient methods to achieve the same thing as copying with recursion. Most often xsl:copy-of is used for parts of the transformation and major parts of XML output is recreated the hard way, "by hand". When beginners get more experienced, they most often continue the bad habit of ignoring "copy with recursion".

Today when XML file formats in MS Office 2003 are bringing XML to the masses, we need more user friendly XSLT books and tutorials. Users of XSLT now include power users of Office applications with little programming experience.

3. match="@*|node()"

"Node()" is shorthand for child::node(). That is node() matches all nodes (element, text, comment, processing-instruction) being children of other nodes but not attributes since they are not considered children of their parent element.

To find all nodes in order to copy them, we need to match both nodes being children of other nodes, node(), and attributes, @*, that is: match="@*|node()".

Instead of the traditional shorthand way of writing the match expression, we could write:

match="@*|*|text()|comment()|processing-instruction()"

Or even better in XSLT/XPath 2.0:

match="attribute()|element()|text()|comment()|processing-instruction()"

The last "match" makes it more clear what is happening. The most common processing instruction is for adding an XSLT stylesheet and could look like this: <?xml-stylesheet type="text/xsl" href="mytest.xsl"?>. We will ignore PIs in the following.

4. Input.xml

In the examples we will use this file as xml input:

<?xml version="1.0"?>
<products author="Jesper">
  <product id="p1">
    <name>Delta</name>
    <price>800</price>
    <stock>4</stock>
    <country>Denmark</country>
  </product>
  <product id="p2">
    <name>Golf</name>
    <price>1000</price>
    <stock>5</stock>
    <country>Germany</country>
  </product>
  <product id="p3">
    <name>Alfa</name>
    <price>1200</price>
    <stock>19</stock>
    <country>Germany</country>
  </product>
  <product id="p4">
    <name>Foxtrot</name>
    <price>1500</price>
    <stock>5</stock>
    <country>Australia</country>
  </product>
<!-- p5 is a brand new product -->
  <product id="p5">
    <name>Tango</name>
    <price>1225</price>
    <stock>3</stock>
    <country>Japan</country>
  </product>
</products>

5. Not some attributes

5.1 Stylesheet.xslt

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/> [3]
  </xsl:copy>
</xsl:template>
<xsl:template match="@id"/>

The first template matches all attributes and all nodes being children of other nodes, and copies them over.

The second template matches the id attribute of the context element. The first template puts node after node in context making it possible for the second template to find the id attributes. The second template cancels the copying of the id attributes by matching them and doing nothing else.

5.2 Output.xml

<products author="Jesper">
  <product>
    <name>Delta</name>
    <price>800</price>
    <stock>4</stock>
    <country>Denmark</country>
  </product>
<!-- etc -->
</products>

6. Not some elements

6.1 Stylesheet.xslt

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>
<xsl:template match="country"/>

The first template matches all attributes and all nodes being children of other nodes, and copies them over.

The second template matches the country elements of the context element and cancels the copying of those elements by doing nothing else. The first template puts node after node in context making it possible for the second template to match the country elements.

6.2 Output.xml

<products author="Jesper">
  <product id="p1">
    <name>Delta</name>
    <price>800</price>
    <stock>4</stock>
  </product>
<!-- etc -->
</products>

7. Not comment nodes

In this case we could have excluded them in the first place but it is nice always to use match="@*|node()" making it easy to transfer the identity template to a separate template library we can include in stylesheets.

7.1 Stylesheet.xslt

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>
<xsl:template match="comment()"/>

The first template matches all attributes and all nodes being children of other nodes, and copies them over.

The second template matches the comment nodes being children of the context node element and cancels the copying of the comment nodes.

7.2 Output.xml

<products author="Jesper">
<!-- etc -->
  <product id="p4">
    <name>Foxtrot</name>
    <price>1500</price>
    <stock>5</stock>
    <country>Australia</country>
  </product>
  <product id="p5">
    <name>Tango</name>
    <price>1225</price>
    <stock>3</stock>
    <country>Japan</country>
  </product>
<!-- etc -->
</products>

Notice that the comment node between product 4 and 5, <!-- p5 is a brand new product -->, is no longer there. The two <!-- etc --> is only used to indicate that not all the products are shown.

8. Rename an attribute

8.1 Stylesheet.xslt

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>
<xsl:template match="@id">
  <xsl:attribute name="pId" select="."/> [4]
</xsl:template>

The first template matches all attributes and all nodes being children of other nodes, and copies them over.

The second template matches the "id" attribute of the context element. The first template puts node after node in context making it possible for the second template to find the "id" attributes.

The second template cancels the copying of the "id" attributes by creating a new attribute named "pId". The context, ".", the value of the canceled "id" attribute, is copied over.

8.2 Output.xml

<products author="Jesper">
  <product pId="p1">
    <name>Delta</name>
    <price>800</price>
    <stock>4</stock>
    <country>Denmark</country>
  </product>
<!-- etc -->
</products>

9. Rename an element

9.1 Stylesheet.xslt

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>
<xsl:template match="products">
  <catalog>
    <xsl:apply-templates select="@*|node()"/>
  </catalog>
</xsl:template>

The first template matches all attributes and all nodes being children of other nodes, and copies them over.

The second template matches the "products" element and cancels the copying. "Products" is renamed to "catalog" and the copying is restarted. We could have used <xsl:element name="catalog"/> instead of <catalog/>.

9.2 Output.xml

<catalog>
  <product id="p1">
    <name>Delta</name>
    <price>800</price>
    <stock>4</stock>
    <country>Denmark</country>
  </product>
<!-- etc -->
</catalog>

10. Add a new attribute

10.1 Stylesheet.xslt

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>
<xsl:template match="products">
  <xsl:copy>
    <xsl:attribute name="dateUpdated">
      <xsl:value-of select="current-dateTime()"/>
    </xsl:attribute>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>

In the second template we cancel the copying of the products element. After having created the new attribute we "restart" the copying. Please note that the current-dateTime() function is new in XPath 2.0.

10.2 Output.xml

<products dateUpdated="2005-07-18T15:38:43+02:00" author="Jesper">
  <product id="p1">
    <name>Delta</name>
    <price>800</price>
    <stock>4</stock>
    <country>Denmark</country>
  </product>
<!-- etc -->
</products>

11. Add a new element

11.1 Stylesheet.xslt

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>
<xsl:template match="stock">
  <description>n/a</description>
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>

In the second template we match "stock" because we want to create a new element, "description", in front of "stock".

Since we don't want to delete the "stock" element we restart the copying after having created the new "description" element.

11.2 Output.xml

<catalog author="Jesper">
  <product id="p1">
    <name>Delta</name>
    <price>800</price>
    <description>n/a</description>
    <stock>4</stock>
    <country>Denmark</country>
  </product>
<!-- etc -->
</catalog>

12. Change elements to uppercase

12.1 Stylesheet.xslt

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>
<xsl:template match="*">
  <xsl:element name="{upper-case(local-name())}">
    <xsl:apply-templates select="@*|node()"/>
  </xsl:element>
</xsl:template>

The first template matches all nodes one by one and "apply templates" including itself for each node. In the second template we match all elements, "*", of context node and recreate them using upper-case. Note that the upper-case() function is new in XPath 2.0.

12.2 Output.xml

<PRODUCTS author="Jesper">
  <PRODUCT id="p1">
    <NAME>Delta</NAME>
    <PRICE>800</PRICE>
    <STOCK>4</STOCK>
    <COUNTRY>Denmark</COUNTRY>
  </PRODUCT>
</PRODUCTS>

13. Change first letter to uppercase

13.1 Stylesheet.xslt

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>
<xsl:template match="*">
  <xsl:element name="{upper-case(substring(local-name(), 1, 1))}{substring(local-name(), 2, string-length(local-name()) - 1)}">
    <xsl:apply-templates select="@*|node()"/>
  </xsl:element>
</xsl:template>

In the second template we match all elements, "*", and recreate them. In the first "{}" we take the first letter out of the old element's local-name and turn it into upper-case. In the second "{}" we reuse the rest of the local-name.

Instead of two "{}", (xsl:select), we could have used one and the concat() function.

13.2 Output.xml

<Products author="Jesper">
  <Product id="p1">
    <Name>Delta</Name>
    <Price>800</Price>
    <Stock>4</Stock>
    <Country>Denmark</Country>
  </Product>
<!-- etc -->
</Products>

14. Change attributes to elements

14.1 Stylesheet.xslt

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>
<xsl:template match="@*">
  <xsl:element name="{local-name()}">
    <xsl:value-of select="."/>
  </xsl:element>
</xsl:template>

In the second template we match all attributes one by one and recreate them as elements with the same name. <value-of select="."/> transfer the value of the old attribute to the new element. We don't need to "apply-templates" at the end of the second template. Attributes only contain values.

14.2 Output.xml

<products>
  <author>Jesper</author>
  <product>
    <id>p1</id>
    <name>Delta</name>
    <price>800</price>
    <stock>4</stock>
    <country>Denmark</country>
  </product>
<!-- etc -->
</products>

15. Identity template in its own stylesheet

In many XSLT books the identity template is put into its own file (identity.xslt or copy.xslt). The "identity.xslt" or "copy.xslt" is then included or imported into all the stylesheet examples only showing the exception templates directly. This is nice in a book to keep it short but in an online tutorial it is easier to understand the examples when you can see all templates side by side.

In the real world we would probably never put the identity template in its own file except if the file is a template library with many other generic templates.

Footnotes

[1]

Identity template is mentioned in XSL Transformations (XSLT), Version 1.0, W3C Recommendation 16 November 1999: 7.5 Copying.

[2]

Most books about XSLT mention the "identity template" and more advanced books have whole chapters dedicated to it like the three books below.

  • XSLT Cookbook, Sal Mangano, O'Reilly 2003, ISBN 0-596-00372-2, chapter 6.
  • XSLT and XPath, Jenni Tennison, Unlimited Edition 2001, ISBN 0-7654-4776-3, chapter 5.
  • Office 2003 XML, Evan Lenz, Mary McRae & Simon St. Laurent, O'Reilly 2004, ISBN 0-596-00538-5, pages 116-125.

My own favorite book about XSLT, XSLT 2.0 Programmer's Reference, 3rd edition, Michael Kay, Wrox 2004, ISBN 0-764-56909, is surprisingly weak on this most important subject from an ordinary programmer's point of view.

[3]

Why select="@*|node()" in the <xsl:apply-templates/> element? In many XSLT stylesheets "apply-templates" is used without a select attribute?

We need to use select="@*|node()" for exactly the same reasons we need to say match="@*|node()". If we don't use a select attribute in the apply-templates element, it defaults to select="node()". Without a select attribute everything is copied over as it should except the attributes.

If we want the attributes to be copied over we must use match="@*" but now all the nodes being children of other nodes are not copied. We need to say match="@*|node()" to take care of it all.

[4]

The select attribute is new for the xsl:attribute element in XSLT 2.0. In XSLT 1.0 we must use <xsl:attribute name="pId "><xsl:value-of select="."/></xsl:attribute>.

Updated 2009-08-06