And yet another post! Is it a trend or an aberration? Only time will tell.
In a
previous post, I offered some solutions for problems that arise from having to deal with nodes that are functionally identical, yet still different. Sort of how two cars can be absolutely identical in terms of brand, model, year, color, etc. and yet still remain two distinct cars. (Just try to argue with the tax man that those two identical cars are actually one and the same.)
This problem can arise very easily when you deal with variables in XSLT. Consider the following:
<xsl:variable name="foo">
<a/>
<xsl:variable>
<xsl:variable name="bar">
<a/>
<xsl:variable>
If you compare the nodes in these variables with "$foo/a is $bar/a", the result will be "false", indicating that while these nodes may look awfully identical, XPath doesn't consider them to be the same node. And XPath does have a point, because these <a> elements will be distinct copies in memory.
In fact, you may not realize just how many copies your XSLTs are making. It's not just these hard coded bits of XML in variables, it's also any time you use an xsl:copy, xsl:copy-of, or xsl:element, as well as when you use included content in an xsl:variable, xsl:param, or xsl:with-param without a type declaration. (And some
other, less common constructs as well.)
Not only can this be most inconvenient (for instance when you want to use set operators), but you may also be wasting machine resources in your performance critical application.
Fortunately, it's relatively easy to reduce the number of copies. You just have to know the tricks of the trade. And those are just what I'm going to tell you right now.
Don't duplicate hard coded XML content
Whenever you have hard coded XML content, this will result in nodes being created. If you create the same XML content in multiple places (such as we did in the foo and bar variables earlier), those will be duplicates. We could have avoided this by just copying foo to bar, like so:
<xsl:variable name="bar" select="$foo">
Now XSLT will create a new node for foo, but not for bar, as the latter will simply point to the same node that was already created for foo.
Replace xsl:copy-of with xsl:sequence
Unlike xsl:copy-of, xsl:sequence can return existing nodes. And since everything is a sequence anyway in XSLT 2.0 (including the result of xsl:copy-of), there's really no reason to not just use xsl:sequence instead of xsl:copy-of. The same goes for xsl:copy's without children, but those tend to be uncommon.
So rather than this:
<xsl:copy-of select="//baz">
Use this:
<xsl:sequence select="//baz">
Simple!
Make sure xsl:variable, xsl:param, xsl:with-param have either a "select" or an "as" attribute (or both)
The elements xsl:variable, xsl:param, xsl:with-param
always make a copy, unless you specify either the "select" attribute, or the "as" attribute (or both).
Whenever possible, use the select attribute, as in those cases you'll never get a copy. If that's not possible, and you really do have to use the element content, you can specify the variable's type with the "as" attribute. In such a case, XSLT will not force the copy to be make. However, if you use hard coded XML, or an xsl:copy-of in the variable content, then those'll still result in copies!
So this is good:
<xsl:variable name="baz" select="//bazElem">
As is:
<xsl:variable name="baz" as="node()">
<xsl:sequence select="//bazElem"/>
</xsl:variable>
But this is going to create a new node in any case:
<xsl:variable name="baz" as="node()">
<bazElem/>
</xsl:variable>
And any nodes here will also be copies:
<xsl:variable name="baz" as="node()">
<xsl:copy-of select="//bazElem"/>
</xsl:variable>
Pro-tip: if you're unsure about the type of your variable, just specify "item()*". That'll allow any sort of sequence.
And that's all you need to know to get rid of most of those unnecessary copies. :-)