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: Wendell Piez <wapiez@xxxxxxxxxxxxxxxx>
Date: Fri, 27 Apr 2007 15:55:56 -0400
Yves,

While you can't restrict preceding-sibling to look only at members of the current group, you might be able to get somewhere with either of these approaches:

* The XPath 2.0 "intersect" operator can return those members common to two sequences of nodes, so (preceding-sibling::node() intersect current-group()) will return just those members of the current group that are on the preceding-sibling axis relative to the context.

* If, rather than using grouping constructs to select from the nodes in the source, you processed them into temporary trees, you could construct those trees exactly the way you wanted, including nesting elements in such a way that preceding-sibling would be useful. Such as:

<xsl:variable name="intermediate">
  <xsl:for-each-group select="*" group-by=".">
    <group>
      <xsl:copy-of select="current-group()"/>
    </group>
  </xsl:for-each-group>
<xsl:variable>

<xsl:for-each select="$intermediate/group">
  ... inside each group element, members of the group appear as siblings ...
</xsl:for-each>

But I'm not sure either of these are actually necessary here. You have only presented your problem in fragmentary form, so it's hard to say; but to get the result you say you want, I'd do something much simpler:

<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:with-param name="this-group" select="current-group()[position() gt 1]"/>
</xsl:apply-templates>
</xsl:for-each-group>
</xsl:template>


  <xsl:template match="B" mode="groups_at_root_level">
    <xsl:param name="this-group" select="()"/>
    <B_new>
      <xsl:apply-templates select="$this-group"/>
    </B_new>
  </xsl:template>

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

I hope that helps,
Wendell

At 01:18 PM 4/27/2007, you 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?

Yves


======================================================================
Wendell Piez                            mailto:wapiez@xxxxxxxxxxxxxxxx
Mulberry Technologies, Inc.                http://www.mulberrytech.com
17 West Jefferson Street                    Direct Phone: 301/315-9635
Suite 207                                          Phone: 301/315-9631
Rockville, MD  20850                                 Fax: 301/315-8285
----------------------------------------------------------------------
  Mulberry Technologies: A Consultancy Specializing in SGML and XML
======================================================================

Current Thread