Tuesday, January 22, 2008

The identity transform for XSLT 2.0

I was looking at the standard identity transform the other day and realised that for nodes other than elements, the call to apply-templates is redundant.

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

Also, although it might be intuitive to think that attributes have separate nodes for their name and value, they are in fact a single node that's copied in it's entirety by xsl:copy.

I raised this on xsl-list and suggested seperating out the attribute into a template of its own with just xsl:copy for its body:

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

<xsl:template match="@*">
  <xsl:copy/>
</xsl:template>


Mike Kay suggested a more logical version would be:

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

<xsl:template match="attribute()|text()|comment()|processing-instruction()">
  <xsl:copy/>
</xsl:template>

This turned out to be ideal for three reasons:

- the comma between @* and node() will mean the selected nodes will be processed in that order, removing the sorting and deduplication that takes place with union |
- apply-templates is only called when it will have an effect
- it's clearer that attributes are leaf nodes

So there it is... the identity transform for XSLT 2.0

6 comments:

dwardu said...

Why not

<xsl:template match="/">
  <xsl:copy-of select="."/>
</xsl:template>

?

Andrew Welch said...

That will just copy the entire input to the output - any specific templates wouldn't be called.

dwardu said...

Yes, but isn't that what an “identity transform” is supposed to do, copy the entire input to the output?

I know what you mean. But then you're talking about the ideal overridable identity transform. Or maybe I'm just being fussy…

I ♥ XSLT. Great blog :)

Anonymous said...

I see a problem with

xsl:apply-templates select="@*, node()"/

because this changes position() in unexpected ways, because attributes are counted as well as nodes, so the first child node will have a position of 1 + number of attributes.

This is not wrong, of course, but unexpected.

Andrew Welch said...

Good point, but I don't think you should rely on position() in a template where you haven't also explicitly selected the nodes in that template.

If you really did want the element's position amongst its siblings in the source tree, then use xsl:number

Max Toro said...

what's missing is a match for document-node()