Search code examples
xsltxslt-3.0

Can you call xsl:evaluate and retain the context of the results?


I am using xsl:evaluate to evaluate xpaths. However, the result is different to if I directly evaluate xpaths, because in the latter case I can get access to the ancestors of the results but in the former I can't.

Is there any way to retain information about the context of the results of <xsl:evaluate>?

Here is my XSLT:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:output method="text" encoding="UTF-8" />

<xsl:template match="/one">
    <xsl:value-of select="name()" />
    <xsl:text>&#xa;</xsl:text>
    <xsl:variable name="xpath">
        <xsl:text>//three</xsl:text>
    </xsl:variable>
    <xsl:variable name="matches1">
        <xsl:evaluate xpath="$xpath" context-item="." />
    </xsl:variable>
    <xsl:for-each select="$matches1/*">
        <xsl:value-of select="name()" />
        <xsl:text>,</xsl:text>
        <xsl:value-of select="name(parent::*)" />
        <xsl:text>&#xa;</xsl:text>
    </xsl:for-each>
    <xsl:variable name="matches2" select="//three" />
    <xsl:for-each select="$matches2">
        <xsl:value-of select="name()" />
        <xsl:text>,</xsl:text>
        <xsl:value-of select="name(parent::*)" />
        <xsl:text>&#xa;</xsl:text>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Here is the input file:

<one>
    <two>
        <three />
        <three />
    </two>
    <two />
</one>

and here is the output:

one
three,
three,
three,two
three,two

Solution

  • Use

    <xsl:variable name="matches1" as="item()*">
        <xsl:evaluate xpath="$xpath" context-item="." />
    </xsl:variable>
    

    It has really nothing to do with xsl:evaluate but rather how xsl:variable (and xsl:param) work in XSLT 2 and later, mainly due to backwards compatibility as to how things worked in XSLT 1; if you don't provide an as attribute you are creating a new document node (https://www.w3.org/TR/xslt-30/#temporary-trees) that is populated by a copy of the items/nodes returned by the sequence constructor inside, i.e. in your case by the nodes selected/returned by xsl:evaluate. If you provide as="item()*" (or for your case of nodes you could also use as="node()*") then no new document node is constructed and populated but the variable is rather bound directly to the items/nodes returned by the sequence constructor (i.e. in your case the xsl:evaluate) inside.

    The gory details are in the XSLT 2 and 3 spec https://www.w3.org/TR/xslt-30/#variable-values and in Michael Kay's XSLT bible https://www.oreilly.com/library/view/xslt-20-and/9780470192740/.