[xsl] Recursion and for-each-group (XSLT 2.0)

Subject: [xsl] Recursion and for-each-group (XSLT 2.0)
From: JBryant@xxxxxxxxx
Date: Tue, 5 Apr 2005 13:33:46 -0500
Hi, all,

Apologies in advance for a long message. I'm trying to give a complete 
description of the problem.

I have defined a set of index entries as nested indexentry elements in my 
source file. Then, in the XSL, I am trying to use a single template to 
group the entries at each level. That much is working, but I am getting 
multiple results. I've tried a number of solutions, but I'm stumped.

Here's the source file:

<book>
  <chapter title="Chapter1">
    <indexentries>
      <indexentry term="word1">
        <indexentry term="word11">
          <indexentry term="word111">
            <indexentry term="word1111"/>
          </indexentry>
        </indexentry>
      </indexentry>
      <indexentry term="word1">
        <indexentry term="word11">
          <indexentry term="word112">
            <indexentry term="word1121"/>
          </indexentry>
        </indexentry>
      </indexentry>
      <indexentry term="word1">
        <indexentry term="word12"/>
      </indexentry>
      <indexentry term="word2"/>
    </indexentries>
  </chapter>
  <chapter title="Chapter2">
    <indexentries>
      <indexentry term="word3">
        <indexentry term="word31">
          <indexentry term="word311">
            <indexentry term="word3111"/>
          </indexentry>
        </indexentry>
      </indexentry>
      <indexentry term="word3">
        <indexentry term="word32"/>
      </indexentry>
      <indexentry term="word4"/>
    </indexentries>
    <topic title="Topic21">
    <indexentries>
      <indexentry term="word5">
        <indexentry term="word51">
          <indexentry term="word511">
            <indexentry term="word5111"/>
          </indexentry>
        </indexentry>
      </indexentry>
      <indexentry term="word5">
        <indexentry term="word52"/>
      </indexentry>
      <indexentry term="word6"/>
    </indexentries>
    </topic>
  </chapter>
</book>

Here's my current best shot at a transform:

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

  <xsl:template match="/">
    <xsl:variable name="indexentries">
      <indexentries>
        <xsl:for-each select="//indexentries/indexentry">
          <indexentry term="{@term}" 
target="{generate-id(ancestor::*[indexentries][1])}">
            <xsl:copy-of select="indexentry"/>
          </indexentry>
        </xsl:for-each>
      </indexentries>
    </xsl:variable>
    <xsl:for-each select="$indexentries">
      <xsl:apply-templates/>
    </xsl:for-each>
  </xsl:template>
 
  <xsl:template match="indexentry">
    <xsl:variable name="term" select="@term"/>
    <xsl:for-each-group select="//indexentry[@term=$term]" 
group-by="@term">
      <indexentry term="{@term}">
        <xsl:choose>
          <xsl:when test="indexentry">
            <xsl:apply-templates select="current-group()/indexentry"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:attribute name="target" 
select="ancestor-or-self::*[@target]/@target"/>
          </xsl:otherwise>
        </xsl:choose>
      </indexentry>
    </xsl:for-each-group>
  </xsl:template>

</xsl:stylesheet>

Here's the output from applying that stylesheet to the source file:

<?xml version="1.0" encoding="UTF-8"?>
<indexentry term="word1">
  <indexentry term="word11">
    <indexentry term="word111">
      <indexentry term="word1111" target="d1e3"/>
    </indexentry>
    <indexentry term="word112">
      <indexentry term="word1121" target="d1e3"/>
    </indexentry>
  </indexentry>
  <indexentry term="word11">
    <indexentry term="word111">
      <indexentry term="word1111" target="d1e3"/>
    </indexentry>
    <indexentry term="word112">
      <indexentry term="word1121" target="d1e3"/>
    </indexentry>
  </indexentry>
  <indexentry term="word12" target="d1e3"/>
</indexentry>
<indexentry term="word1">
  <indexentry term="word11">
    <indexentry term="word111">
      <indexentry term="word1111" target="d1e3"/>
    </indexentry>
    <indexentry term="word112">
      <indexentry term="word1121" target="d1e3"/>
    </indexentry>
  </indexentry>
  <indexentry term="word11">
    <indexentry term="word111">
      <indexentry term="word1111" target="d1e3"/>
    </indexentry>
    <indexentry term="word112">
      <indexentry term="word1121" target="d1e3"/>
    </indexentry>
  </indexentry>
  <indexentry term="word12" target="d1e3"/>
</indexentry>
<indexentry term="word1">
  <indexentry term="word11">
    <indexentry term="word111">
      <indexentry term="word1111" target="d1e3"/>
    </indexentry>
    <indexentry term="word112">
      <indexentry term="word1121" target="d1e3"/>
    </indexentry>
  </indexentry>
  <indexentry term="word11">
    <indexentry term="word111">
      <indexentry term="word1111" target="d1e3"/>
    </indexentry>
    <indexentry term="word112">
      <indexentry term="word1121" target="d1e3"/>
    </indexentry>
  </indexentry>
  <indexentry term="word12" target="d1e3"/>
</indexentry>
<indexentry term="word2" target="d1e3"/>
<indexentry term="word3">
  <indexentry term="word31">
    <indexentry term="word311">
      <indexentry term="word3111" target="d1e38"/>
    </indexentry>
  </indexentry>
  <indexentry term="word32" target="d1e38"/>
</indexentry>
<indexentry term="word3">
  <indexentry term="word31">
    <indexentry term="word311">
      <indexentry term="word3111" target="d1e38"/>
    </indexentry>
  </indexentry>
  <indexentry term="word32" target="d1e38"/>
</indexentry>
<indexentry term="word4" target="d1e38"/>
<indexentry term="word5">
  <indexentry term="word51">
    <indexentry term="word511">
      <indexentry term="word5111" target="d1e61"/>
    </indexentry>
  </indexentry>
  <indexentry term="word52" target="d1e61"/>
</indexentry>
<indexentry term="word5">
  <indexentry term="word51">
    <indexentry term="word511">
      <indexentry term="word5111" target="d1e61"/>
    </indexentry>
  </indexentry>
  <indexentry term="word52" target="d1e61"/>
</indexentry>
<indexentry term="word6" target="d1e61"/>

And here's what I am aiming for:

<?xml version="1.0" encoding="UTF-8"?>
<indexentry term="word1">
  <indexentry term="word11">
    <indexentry term="word111">
      <indexentry term="word1111" target="d1e3"/>
    </indexentry>
    <indexentry term="word112">
      <indexentry term="word1121" target="d1e3"/>
    </indexentry>
  </indexentry>
  <indexentry term="word12" target="d1e3"/>
</indexentry>
<indexentry term="word2" target="d1e3"/>
<indexentry term="word3">
  <indexentry term="word31">
    <indexentry term="word311">
      <indexentry term="word3111" target="d1e38"/>
    </indexentry>
  </indexentry>
  <indexentry term="word32" target="d1e38"/>
</indexentry>
<indexentry term="word4" target="d1e38"/>
<indexentry term="word5">
  <indexentry term="word51">
    <indexentry term="word511">
      <indexentry term="word5111" target="d1e61"/>
    </indexentry>
  </indexentry>
  <indexentry term="word52" target="d1e61"/>
</indexentry>
<indexentry term="word6" target="d1e61"/>

As you can see, multiple nodes are appearing in the output tree. I'm using 
Saxon 8.3, but I very much doubt the processor is the issue.

Thanks much.

Jay Bryant
Bryant Communication Services
(presently consulting at Synergistic Solution Technologies)

Current Thread