It’s been a while since I created stylesheets for transforming XML using XSLT, but I came across a problem that I don’t remember solving before. I am using XSLT to transform some XML documents where the schema has gone through considerable changes. For the most part I traversing the DOM tree and generate new XML elements valid to the new schema.

But I do have an element with complex content — call it <configuration> – that I want to simply copy as is from the input tree to the output tree, except that it has unit attributes where the valid values (an enumeration) have changed. I want to translate these values, for example from “in” to “inch” or from “lbf” to “pound_force.” I have an XSL template that does the translation brute force. Nothing fancy nor elegant, but it does the job:

<xsl:template name="translateUnit">
  <xsl:param name="unit"/>
  <xsl:choose>
    <xsl:when test="$unit='lbf'">pound_force</xsl:when>
    <xsl:when test="$unit='rad'">radian</xsl:when>
    <xsl:when test="$unit='in'">inch</xsl:when>
    <xsl:when test="$unit='sq-in'">square_inch</xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$unit"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

The problem with <configuration> is that the structure of the content varies widely, depending on context of ancestor elements, so I don’t want to traverse the <configuration> node tree, just to change a few attribute values. Michael Kay’s excellent XSLT Programmer’s Reference (I’m still using the 2nd ed. for XSLT 1.0), shows a nifty template for cloning node trees:

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

This template processes the node tree recursively until all nodes have been copied from the input tree to the output tree. This is great because now I can create a “copy” mode template with higher priority to match my attribute:

<xsl:template match="@unit" mode="copy">
  <xsl:attribute name="unit">
    <xsl:call-template name="translateUnit">
      <xsl:with-param name="unit" select="."/>
    </xsl:call-template>
  </xsl:attribute>
</xsl:template>

So when the more general copy template encounters the unit attribute, apply-templates matches the more specific @unit copy mode template instead of continuing by matching the enclosing template recursively. Neat! I can filter on the node tree without knowing anything about its structure. I’m sure I’ll find use for this again.



No Responses to “XSLT — Cloning complex content with filtering”  

  1. No Comments

Leave a Reply