Re: [xsl] xsl:for-each-group: start groups depending on number of group members?

Subject: Re: [xsl] xsl:for-each-group: start groups depending on number of group members?
From: "Andrew Welch" <andrew.j.welch@xxxxxxxxx>
Date: Fri, 27 Apr 2007 22:00:51 +0100
On 4/27/07, Yves Forkl <Y.Forkl@xxxxxx> wrote:
Relying heavily on xsl:for-each-group in the context of rather
complicated transformations, I have run into this situation (and am
stuck there...): When specifying, in group-starting-with, the pattern
that may start some specific group, I would like to take into account
the number of items available for grouping.

More precisely, my group starting pattern must include a filter using
preceding-sibling which I do not want to "look beyond" the current
sequence of items to be grouped.

Showcasing this in a minimal example (more details below):

INPUT:

<root>
   <A/>
   <sub>a1</sub>
   <B/>
   <sub>b1</sub>
   <sub>b2</sub>
   <sub>b3</sub>
   <C/>
   <sub>c1</sub>
   <sub>c2</sub>
</root>

OUTPUT:

<B_new>
    <sub_new>b1</sub_new>
    <sub_new>b2</sub_new>
    <sub_new>b3</sub_new>
</B_new>

STYLESHEET:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
                 version="2.0">

<xsl:output indent="yes"/>

   <xsl:template match="/">
     <xsl:for-each-group
       select="root/*"
       group-starting-with="A|B|C">
       <xsl:apply-templates
         select="current-group()[1]"
         mode="groups_at_root_level"/>
     </xsl:for-each-group>
   </xsl:template>

   <!-- don't care for them now -->
   <xsl:template match="A|C" mode="groups_at_root_level"/>

   <xsl:template match="B" mode="groups_at_root_level">
     <B_new>
       <xsl:variable name="items_total" select="count(current-group())"/>
       <xsl:for-each-group
         select="current-group()"
         group-starting-with="
           B|
           sub[not(
             preceding-sibling::*[position() lt $items_total][A])]">
         <xsl:apply-templates select="current-group()"/>
       </xsl:for-each-group>
     </B_new>
   </xsl:template>

   <xsl:template match="sub">
     <sub_new>
       <xsl:apply-templates/>
     </sub_new>
   </xsl:template>

<xsl:template match="*"/>

</xsl:stylesheet>


I would like to ask your opinion on the preceding-sibling line. If it just read preceding-sibling::A, the output would of course miss all "sub" instances. (Unfortunately, for reasons difficult to explain here, I can't just drop the negated preceding-sibling filter because I need it in some of my group starting patterns.)

Obviously, my above try at restricting the number of preceding siblings
to consider to those which are among the items to group is rather
limited: When xsl:for-each-group examines the very first item, the first
preceding sibling of that will already be outside of the groupable items
(while the last item will have many more "acceptable" preceding siblings
than the first). This makes me dream of a kind of dynamic position
counter returning the position of the current item that
xsl:for-each-group is trying to put into a group. Is there such a thing?

Alternatively, how could I restrict sibling tests to the items which are
being grouped, inside the pattern of group-starting-with?

I'm not too sure what you're asking there, but this transform:


<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>

<xsl:template match="/">
	<root>
	    <xsl:for-each-group select="root/*" group-starting-with="A|B|C">
	       <group>
			<xsl:copy-of select="current-group()[not(self::A|self::B|self::C)]"/>
	       </group>
	    </xsl:for-each-group>
	</root>
</xsl:template>

</xsl:stylesheet>

gives this output:

<root>
	<group>
		<sub>a1</sub>
	</group>
	<group>
		<sub>b1</sub>
		<sub>b2</sub>
		<sub>b3</sub>
	</group>
	<group>
		<sub>c1</sub>
		<sub>c2</sub>
	</group>
</root>

Is that what you were after?

Current Thread