views:

336

answers:

3

Is it possible to make a for-each loop in XSLT not for a node set, but for my own collection of elements? For example, I split some string and have a string collection as a result. And I need to create a node for each item in the collection. I know that issue can be solved with a recursive template, but I want to know if it is possible to avoid a recursion.

+1  A: 

It is possible to do that using the XPath extension function node-set(). This function is supported e.g. by the msxsl and exslt extensions.

MSDN gives an example for how to use the msxsl:node-set() function with an xsl:for-each:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                xmlns:user="http://www.contoso.com"
                version="1.0">
    <xsl:variable name="books">
        <book author="Michael Howard">Writing Secure Code</book>
        <book author="Michael Kay">XSLT Reference</book>
    </xsl:variable>

    <xsl:template match="/">
        <authors>
            <xsl:for-each select="msxsl:node-set($books)/book"> 
                <author><xsl:value-of select="@author"/)</author>
            </xsl:for-each>
        </authors>
    </xsl:template>
</xsl:stylesheet>
0xA3
See my answer for two solutions -- neither of them needs to use extension functions. :)
Dimitre Novatchev
A: 

Which platform do you use, which XSLT processor exactly do you use? You will need to provide your collection of strings in the form of a data type your XSLT processor supports. Which one exactly that is depends completely on the API your XSLT processor supports. For instance with .NET's XslCompiledTransform you would need to provide an XPathNodeIteratator.

Martin Honnen
+1  A: 

There are two obvious, straightforward solutions, one of which is supported only in XSLT 2.0:

I. A general solution

This works both with XSLT 1.0 and XSLT 2.0.

Define your own namespace and place your node-set as children of an element in this namespace, which element is globally placed in the stylesheet (child of the <xsl:stylesheet> instruction.)

Here is an example:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:my="my:my" exclude-result-prefixes="my"
 >
 <xsl:output method="text"/>

 <my:nodes>
   <string>Hello </string>
   <string>World</string>
 </my:nodes>

 <xsl:variable name="vLookup"
    select="document('')/*/my:nodes/*"/>

 <xsl:param name="pSearchWord" select="'World'"/>

 <xsl:template match="/">
   <xsl:if test="$pSearchWord = $vLookup">
     <xsl:value-of select=
       "concat('Found the word ', $pSearchWord)"/>
   </xsl:if>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on any XML document (not used), the result is:

Found the word World

Do note that we do not need the xxx:node-set() extension function at all.

II. An XSLT 2.0 / XPath 2.0 solution

In XSLT 2.0 / XPath 2.0 one can always use the sequence type. For example, one can simply define a variable to contain a sequence of strings in this way:

 <xsl:variable name="vLookup" as="xs:string*"
    select="'Hello', 'World'"/>

and use it as in the following transformation:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 >
 <xsl:output method="text"/>

 <xsl:variable name="vLookup" as="xs:string*"
    select="'Hello', 'World'"/>

 <xsl:param name="pSearchWord" select="'World'"/>

 <xsl:template match="/">
   <xsl:if test="$pSearchWord = $vLookup">
     <xsl:value-of select=
       "concat('Found the word ', $pSearchWord)"/>
   </xsl:if>
 </xsl:template>
</xsl:stylesheet>
Dimitre Novatchev