2

I have the following XSLT stylesheet (simplified):

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

<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:variable name="categories">
    <category name="one"/>
    <category name="two"/>
    <category name="three"/>
</xsl:variable>

<xsl:template match="/">
<result>

<xsl:for-each select="exsl:node-set($categories)/category">
<xsl:element name="{@name}">

<!-- THIS IS THE PROBLEMATIC PART -->
    <xsl:for-each select="/input/object">
        <item>
            <xsl:value-of select="."/>
        </item>
    </xsl:for-each>

</xsl:element>
</xsl:for-each>
</result>
</xsl:template>
</xsl:stylesheet>

This refers to the following source XML document (also simplified):

<?xml version="1.0" encoding="utf-8"?>
<input>
    <object category="one">a</object>
    <object category="one">b</object>
    <object category="two">c</object>
    <object category="one">d</object>
    <object category="three">e</object>
</input>

The reference to the source document produces no result; the output is just empty elements, one for each category:

<?xml version="1.0" encoding="UTF-8"?>
<result>
  <one/>
  <two/>
  <three/>
</result>

How can I "fill" the elements with items from the source document?


Just to clarify, the "real" problem behind this is already solved, using a different approach. I am just trying to understand why this approach is not working.

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51

1 Answers1

5

In an xsl:for-each XPaths are interpreted in the context of the selected "document", i.e. / refers to node-set($categories). You can see for yourself by trying the following code:

<xsl:template match="/">
  <xsl:variable name="root" select="/"/>
  <result>

  <xsl:for-each select="exsl:node-set($categories)/category">
    <xsl:element name="{@name}">

    <xsl:for-each select="$root/input/object">
        <item>
            <xsl:value-of select="."/>
        </item>
    </xsl:for-each>

    </xsl:element>
  </xsl:for-each>
  </result>
</xsl:template>

It uses the variable root to pass access to the document selected by the template to the inner xsl:for-each loop. (Note: The variable could as well been placed outside of the template.)

You can double-check that / actually uses the node-set by replacing select="/input/object" by select="/category" in your original code. You will get empty items (one per category) in your elements.

halfbit
  • 3,414
  • 1
  • 20
  • 26
  • 1
    Thank you very much for the clear and well-formulated answer. I didn't realize the node-set function created an independent "document" with a root of it own. May I ask what exactly is stored in the $root variable? Is it merely a reference to the root of the "calling" XML document, or does it store the entire tree? The source XML could get rather large, so I am concerned about the possible effect of this solution on performance. – michael.hor257k Nov 22 '13 at 01:15
  • 1
    In fact I do not know for sure. But it must be node references because it would not make any sense to store copies of immutable nodes. Memory is probably only consumed by the node-set (of node references), which in case of `$root` is tiny (one element). Note that you _can_ have variables with copies of nodes by using `xsl:copy` and `xsl:copy-of`, see http://www.biglist.com/lists/xsl-list/archives/200410/msg00710.html. – halfbit Nov 22 '13 at 08:01
  • 3
    Well, okay then. I've done some more searching (since you have pointed out that the _real_ issue here is returning to the calling document, I could focus my search on that) and I see that storing the root in a variable is the commonly given advice - so I guess I'll just stop worrying about it. Thank you once again, it's been a pleasure. – michael.hor257k Nov 22 '13 at 08:58