Search code examples
xsltxslt-1.0msxsl

Constructing, not selecting, XSL node set variable


I wish to construct an XSL node set variable using a contained for-each loop. It is important that the constructed node set is the original (a selected) node set, not a copy.

Here is a much simplified version of my problem (which could of course be solved with a select, but that's not the point of the question). I've used the <name> node to test that the constructed node set variable is in fact in the original tree and not a copy.

XSL version 1.0, processor is msxsl.

Non-working XSL:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />

<xsl:template match="/">

    <xsl:variable name="entries">
        <xsl:for-each select="//entry">
            <xsl:copy-of select="."/>
        </xsl:for-each>
    </xsl:variable>

    <xsl:variable name="entryNodes" select="msxsl:node-set($entries)"/>

    <xsl:for-each select="$entryNodes">
        <xsl:value-of select="/root/name"/>
        <xsl:value-of select="."/>
    </xsl:for-each>

</xsl:template>

</xsl:stylesheet>

XML input:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <name>X</name>
    <entry>1</entry>
    <entry>2</entry>
</root>

Wanted output:

X1X2

Actual output:

12

Of course the (or a) problem is the copy-of, but I can't work out a way around this.


Solution

  • There isn't a "way around it" in XSLT 1.0 - it's exactly how this is supposed to work. When you have a variable that is declared with content rather than with a select then that content is a result tree fragment consisting of newly-created nodes (even if those nodes are a copy of nodes from the original tree). If you want to refer to the original nodes attached to the original tree then you must declare the variable using select. A better question would be to detail the actual problem and ask how you could write a suitable select expression to find the nodes you want without needing to use for-each - most uses of xsl:if or xsl:choose can be replaced with suitably constructed predicates, maybe involving judicious use of xsl:key, etc.


    In XSLT 2.0 it's much more flexible. There's no distinction between node sets and result tree fragments, and the content of an xsl:variable is treated as a generic "sequence constructor" which can give you new nodes if you construct or copy them:

    <xsl:variable name="example" as="node()*">
      <xsl:copy-of select="//entry" />
    </xsl:variable>
    

    or the original nodes if you use xsl:sequence:

    <xsl:variable name="example" as="node()*">
      <xsl:sequence select="//entry" />
    </xsl:variable>