This is the mail archive of the
xsl-list@mulberrytech.com
mailing list .
Re: Recursive grouping won't recurse...
- From: Dimitre Novatchev <dnovatchev at yahoo dot com>
- To: xsl-list at lists dot mulberrytech dot com
- Date: Thu, 4 Jul 2002 13:21:58 -0700 (PDT)
- Subject: [xsl] Re: Recursive grouping won't recurse...
- Reply-to: xsl-list at lists dot mulberrytech dot com
--- "Hunsberger, Peter" <Peter dot Hunsberger at stjude dot org> wrote:
> I'm still shaky on grouping with keys so I've probably missed
> something
> obvious, but I can't get a grouping to work when it has to group on a
> recursive structure. The input looks essentially like the following:
>
> <list>
> <a type="1" flag="false"/>
> <b type="2" flag="false"/>
> <c type="3" flag="false">
> <d type="4" flag="true"/>
> <e type="4" flag="true"/>
> <f type="5" flag="true"/>
> <g type="5" flag="true"/>
> </c>
> </list>
> <list>
> <a type="1" flag="false"/>
> <b type="2" flag="false"/>
> <c type="3" flag="false">
> <d type="7" flag="false">
> <e type="4" flag="true"/>
> <e type="4" flag="true"/>
> </d>
> </c>
> </list>
>
> Where, if there are adjacent nodes with the same type than the flag
> will be
> true, otherwise the flag will always be false. It could be possible
> for
> adjacent nodes to have the same type with the flag set to false. The
> same
> structure could go many more levels deep than shown here. The desired
> output
> is
>
> <list>
> <a type="1" flag="false"/>
> <b type="2" flag="false"/>
> <c type="3" flag="false">
> <group>
> <d type="4" flag="true"/>
> <e type="4" flag="true"/>
> </group>
> <group>
> <f type="5" flag="true"/>
> <g type="5" flag="true"/>
> </group>
> </c>
> </list>
> <list>
> <a type="1" flag="false"/>
> <b type="2" flag="false"/>
> <c type="3" flag="false">
> <d type="7" flag="false">
> <group>
> <e type="4" flag="true"/>
> <e type="4" flag="true"/>
> </group>
> </d>
> </c>
> </list>
>
> Where any adjacent nodes of the same type and with "flag" = true are
> enclosed in a group. The XSLT I have resembles the following
> (extracted
> from a larger XSLT with other things going on):
>
> <xsl:key select="." name="menus" match="*" use="@type"/>
>
> <xsl:template match="list">
> <xsl:apply-templates select="." mode="menu"/>
> </xsl:template>
>
> <xsl:template match="*" mode="menu">
> <xsl:for-each select="*[generate-id() = generate-id(key('menus',
> @type))]">
> <xsl:choose>
> <xsl:when test="@flag='true'">
> <group>
> <xsl:copy select=".">
> <xsl:apply-templates select="@* | text()"/>
> <xsl:apply-templates select="." mode="menu"/>
> </xsl:copy>
> <xsl:for-each
> select="current()/following-sibling::*[current()/@type = @type]">
> <xsl:copy select="current()">
> <xsl:apply-templates select="@* | text()"/>
> <xsl:apply-templates select="." mode="menu"/>
> </xsl:copy>
> </xsl:for-each>
> </group>
> </xsl:when>
> <xsl:otherwise>
> <xsl:copy select=".">
> <xsl:apply-templates select="@* | text()"/>
> <xsl:apply-templates select="." mode="menu"/>
> </xsl:copy>
> <xsl:for-each
> select="current()/following-sibling::*[current()/@type = @type]">
> <xsl:copy select="current()">
> <xsl:apply-templates select="@* | text()"/>
> <xsl:apply-templates select="." mode="menu"/>
> </xsl:copy>
> </xsl:for-each>
> </xsl:otherwise>
> </xsl:choose>
> </xsl:for-each>
> </xsl:template>
>
> This will produce the desired results for a first level group (as in
> the
> first list), but does not produce the desired results for a second
> level
> group (as in the second list). The issue is, I'm sure, with the way
> the key
> works, but I'm not clear what the problem is?
>
> I've also tried doing the same thing with using named templates
> instead
> of
> modes with the same results. I'll have to add additional case
> handling
> templates once I've got this working so I'd rather stick with modes
> if
> possible...
>
> BTW, is there a nice way to say "self and siblings" so that you don't
> have
> to copy the current node and then copy the siblings?
>
Hi Peter,
This is a positional grouping problem. Here's one possible solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="kGrp1" match="a|b|c|d|e|f|g"
use="number(
@flag = 'true'
and
not(@type = preceding-sibling::*[1]/@type)
and
following-sibling::*[1]/@flag = 'true'
and
@type = following-sibling::*[1]/@type
)"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/ | @* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a|b|c|d|e|f|g">
<xsl:choose>
<xsl:when test="count(. | key('kGrp1', '1'))
=
count(key('kGrp1', '1'))">
<group>
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
<xsl:variable name="vOutOfGroupSibling"
select="following-sibling::*[not(@type = current()/@type
and
@flag = 'true'
)
][1]"/>
<xsl:variable name="vGroupLength">
<xsl:choose>
<xsl:when test="$vOutOfGroupSibling">
<xsl:value-of
select="count($vOutOfGroupSibling/preceding-sibling::*)
- count(preceding-sibling::*)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="count(../*)
- count(preceding-sibling::*)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:apply-templates mode="inGroup"
select="following-sibling::*
[position() < $vGroupLength]"/>
</group>
</xsl:when>
<xsl:when test="not(@type = preceding-sibling::*[1]/@type
and @flag = 'true'
and preceding-sibling::*[1]/@flag = 'true'
)">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match="a|b|c|d|e|f|g" mode="inGroup">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This transformation works correctly on your original xml file. It also
works with nested groupings, e.g. when applied on the following source
xml:
<lists>
<list>
<a type="1" flag="false"/>
<b type="2" flag="false"/>
<c type="3" flag="true">
<d type="4" flag="true"/>
<e type="4" flag="true"/>
<f type="5" flag="true"/>
<g type="5" flag="true"/>
</c>
<c type="3" flag="true"/>
</list>
<list>
<a type="1" flag="false"/>
<b type="2" flag="false"/>
<c type="3" flag="false">
<d type="7" flag="false">
<e type="4" flag="true"/>
<e type="4" flag="true"/>
</d>
</c>
</list>
</lists>
the result correctly contains nested groups:
<lists>
<list>
<a type="1" flag="false"/>
<b type="2" flag="false"/>
<group>
<c type="3" flag="true">
<group>
<d type="4" flag="true"/>
<e type="4" flag="true"/>
</group>
<group>
<f type="5" flag="true"/>
<g type="5" flag="true"/>
</group>
</c>
<c type="3" flag="true"/>
</group>
</list>
<list>
<a type="1" flag="false"/>
<b type="2" flag="false"/>
<c type="3" flag="false">
<d type="7" flag="false">
<group>
<e type="4" flag="true"/>
<e type="4" flag="true"/>
</group>
</d>
</c>
</list>
</lists>
Hope this helped.
Cheers,
Dimitre Novatchev.
__________________________________________________
Do You Yahoo!?
Sign up for SBC Yahoo! Dial - First Month Free
http://sbc.yahoo.com
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list