3

I have an XSLT like this:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                         xmlns:xalan="http://xml.apache.org/xalan">

  <xsl:variable name="fooDocument" select="document('fooDocument.xml')"/>

  <xsl:template match="/">
    <xsl:apply-templates select="$fooDocument//*"/>
  </xsl:template>

  <xsl:template match="nodeInFooDocument">
    <xsl:variable name="valueFromSource" select="//someSourceElement"/>
  </xsl:template>
</xsl:transform>

In the second template, which matches nodes in the fooDocument.xml which is loaded with document(), I want to access nodes in the XML source the transformation is executed upon. This does not work with //someSourceElement, because apparently, XPath executes this path in the context of fooDocument.

A first workaround that comes to mind is this:

...
<!-- global variable -->
<xsl:variable name="root" select="/"/>

...
<!-- in the template -->
<xsl:variable name="valueFromSource" select="$root//someSourceElement"/>

...

But I cannot use this workaround, because actually, my variable is selected like this:

<xsl:variable name="valueFromSource" select="xalan:evaluate($someXPathString)"/>

$someXPathString is not crafted in the XSLT file, but loaded from fooDocument (and contains an absolute path like the one used above). Still, I need to somehow change the XPath context back to the XML source. A very hacky workaround I found is this:

<xsl:for-each select="$root[1]">
  <xsl:variable name="valueFromSource" select="xalan:evaluate($someXPathString)"/>
</xsl:for-each>

The (useless) for-each loop changes the context back to the main XML source, thus the XPath evaluates correctly. But obviously, this is not an acceptable solution.

Is there a way to do this right, or can someone suggest a better workaround?

flyx
  • 35,506
  • 7
  • 89
  • 126

2 Answers2

3

Even if you think your attempt with a for-each select="$root" to change the context document is not acceptable this is the right approach. So use that, there is no other way.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • 1
    I agree that for-each is a fine way to change the context document (if somewhat non-intuitive), but surely Martin you don't mean that there is no other way. E.g. `apply-templates select="$root"` (with or without mode) will also set the context document, right? – LarsH Dec 16 '11 at 22:09
  • I did not find any other feasible way to change the context document. – flyx Jan 12 '12 at 10:50
2

Have you considered doing all the computation that constructs $someXPathString using a series of global variables?

<xsl:variable name="fooDocument" select="document('fooDocument.xml')"/>

<xsl:variable name="temp1"
  .. some computation using fooDocument ..
</xsl:variable>

<xsl:variable name="temp2"
  .. some computation using temp1 ..
</xsl:variable>

<xsl:variable name="someXPathString"
  .. some computation using temp2 ..
</xsl:variable>

<xsl:variable name="root" select="xalan:evaluate($someXPathString)"/>
Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • This would work if I chose to copy all the contents of fooDocument into a global variable and use xalan:nodeset() on it. As fooDocument contains a sequence of arbitrary length, I cannot split it into a fixed number of global variables holding primitive values. For readability, this is definitely an option. EDIT: On second thought, when I use `apply-templates` on this global variable, my context would still not be on the XML source, but on the variable which contains the result of a xalan:nodeset() call. – flyx Dec 19 '11 at 07:11