Sunday, August 3, 2008

Multiple values for XSLT keys

An XSLT user, asked following question, on xsl-list.

How do I use multiple key values?

Declaration:

<xsl:key name="keyname" match="subroot" use="ccc"/>

During the usage, I want to specify multiple values:

<xsl:variable name="keyname" select="key('keyname', '11' or '22')"/> ==> Here I want to use multiple values 11 and 22.

xsl-list members suggested useful options,

1. David Carlisle

<xsl:variable name="keyname" select="key('keyname', '22')|key('keyname', '11')"/>

2. Michael Kay

In XSLT 2.0, you can supply a sequence:

key('keyname', ('111', '222'))

In 1.0, you can supply a node-set with one value per node - but of course it's hard to set that up, you need the xx:node-set() function.

I worked upon Mike's idea for a XSLT 1.0 solution,

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common"
exclude-result-prefixes="exslt"
version="1.0">

<xsl:output method="xml" indent="yes" />

<xsl:key name="x" match="subroot" use="ccc"/>

<xsl:variable name="x-values">
<v>11</v>
<v>22</v>
</xsl:variable>

<xsl:template match="/root">
<result>
<xsl:for-each select="key('x', exslt:node-set($x-values)/v)">
<value>
<xsl:value-of select="eee" />
</value>
</xsl:for-each>
</result>
</xsl:template>

</xsl:stylesheet>

Mukul: I think, this could be better than David's suggestion, because if we want to have quite large number of different values to search by the key, we just have to change following code fragment,

<xsl:variable name="x-values">
<v>11</v>
<v>22</v>
<!-- more values -->
</xsl:variable>

G. Ken Holman responded to my post, and provided a brilliant idea,

The node-set extension can be avoided to achieve what you want.

<xsl:for-each select="key('x', exslt:node-set($x-values)/v)">

The above can be replaced with standard XSLT 1.0 to read the stylesheet file as a source node tree.

<xsl:for-each
select="key('x',document('')/*/xsl:variable[@name='x-values']/v)">

Ken further wrote,

I grant, though, that if your stylesheet is large then putting this into a small included or imported fragment would keep any overhead of building the tree small.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:output method="xml" indent="yes" />

<xsl:key name="x" match="subroot" use="ccc"/>

<xsl:include href="ken2values.xsl"/>

<xsl:template match="/root">
<result>
<xsl:for-each select="key('x',$x-values)">
<value>
<xsl:value-of select="eee" />
</value>
</xsl:for-each>
</result>
</xsl:template>

</xsl:stylesheet>

ken2values.xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:variable name="x-values-data">
<v>11</v>
<v>22</v>
</xsl:variable>

<xsl:variable name="x-values"
select="document('')/*/xsl:variable[@name='x-values-data']/v"/>

</xsl:stylesheet>

I think, Ken's idea of having an included stylesheet (ken2values.xsl, above) is brilliant, as it is memory efficient, and we are able to avoid the node-set extension (as mentioned earlier).

No comments: