views:

122

answers:

1

I'm having troubles to figure out a way to group items xslt 1.0. I have a source xml similar to the one below:

<client name="client A">
    <project name = "project A1"/>
    <project name = "project A2"/>
    <project name = "project A3"/>
    <project name = "project A4"/>
</client>
<client name="client B">
    <project name = "project B1"/>
    <project name = "project B2"/>
</client>
<client name="client C">
    <project name = "project C1"/>
    <project name = "project C2"/>
    <project name = "project C3"/>
</client>

I'd like to select all projects, sort them and then group every 3 project in one boundle as in the example below:

<boundle>
  <project name="project A1">
  <project name="project A2">
  <project name="project A3">
</boundle>
<boundle>
  <project name="project A4">
  <project name="project B1">
  <project name="project B2">
</boundle>
<boundle>
  <project name="project C1">
  <project name="project C2">
  <project name="project C3">
</boundle>

Currently to do so I'm using to open a boundle tag and close it later. Can you think about any better solution?

+2  A: 

No grouping necessary.

<xsl:param name="perGroup" select="3" />

<xsl:variable name="allProjects" select="/client/project" />

<xsl:template match="/">
  <xsl:apply-templates select="$allProjects" mode="counted" />
</xsl:template>

<xsl:template match="project" mode="counted">
  <xsl:if test="position() mod $perGroup = 1">
    <xsl:variable name="pos" select="position()" />
    <boundle>
      <xsl:copy-of select="$allProjects[
        position() &gt;= $pos and position() &lt; ($pos + $perGroup)
      ]" />
    </boundle>
  </xsl:template>
</xsl:template>
Tomalak
I like this solution, since it avoids repetition. I wonder though if it could be simpler, since you now basically loop over all projects, and for every third project, copy the last three projects, instead of copying a project in the first place and just put the boundles around them...
OregonGhost
This cannot be made much simpler. You could do `<xsl:apply-templates select="$allProjects[position() mod 3 = 1]" ...` and remove the `<xsl:if>` in exchange, but in terms of processing this is pretty much the same thing. Also, the way I did it, the `<project>` template is self-contained. Regarding the potential use of an XSL key: since there is no consecutive range of projects, `position() mod x` will not produce the desired results. Apart from that, the above is parameterized, an XSL key would have to be hard-coded.
Tomalak
@Tomalak is right. In making my solution work (now deleted) it gets hopelessly complex dealing with different edge cases where the 2nd or 3rd project in a bundle moves into the next, or next plus one client nodes.
Richard