2

I studied the solution for "XSLT split result in groups of 3" with interest. The elements in the cited example are all under one node. When I extended the example data to contain 2 branches of , like it is shown below,

<Root>
    <nums>
        <num>01</num>
        <num>02</num>
        <num>03</num>
        <num>04</num>
    </nums>
    <nums>
        <num>11</num>
        <num>12</num>
        <num>13</num>
        <num>14</num>
    </nums>
</Root> 

Using the adapted xslt as follows:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space elements="*"/>

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

    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="/Root/*">
        <nums>
            <xsl:apply-templates select=
                "num[position() mod $pGroupSize = 1]"/>
        </nums>
    </xsl:template>

    <xsl:template match="num">
        <group>
            <xsl:copy-of select=
                ".|following-sibling::*
                [not(position() > $pGroupSize -1)]"/>
        </group>
    </xsl:template>
</xsl:stylesheet>

The output is as follows:

<Root>
   <nums>
      <group>
         <num>01</num>
         <num>02</num>
         <num>03</num>
      </group>
      <group>
         <num>04</num>
      </group>
   </nums>
   <nums>
      <group>
         <num>11</num>
         <num>12</num>
         <num>13</num>
      </group>
      <group>
         <num>14</num>
      </group>
   </nums>
</Root>

However, let's say the desired output should take the following form as shown below. How can this be done?

<Root>
   <nums>
      <group>
         <num>01</num>
         <num>02</num>
         <num>03</num>
      </group>
      <group>
         <num>04</num>
         <num>11</num>
         <num>12</num>
      </group>
      <group>
         <num>13</num>
         <num>14</num>
      </group>
   </nums>
</Root>

Thanks!

Bumblevee
  • 69
  • 1
  • 5

1 Answers1

1

For XSLT 1 you would need to change your selection to descendant::num and the following-sibling::* to following::num:

<xsl:template match="/Root">
    <nums>
        <xsl:apply-templates select=
            "descendant::num[position() mod $pGroupSize = 1]"/>
    </nums>
</xsl:template>

<xsl:template match="num">
    <group>
        <xsl:copy-of select=
            ".|following::num
            [not(position() > $pGroupSize -1)]"/>
    </group>
</xsl:template>

Full example https://xsltfiddle.liberty-development.net/jyH9rMA

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space elements="*"/>

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

    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="/Root">
        <nums>
            <xsl:apply-templates select=
                "descendant::num[position() mod $pGroupSize = 1]"/>
        </nums>
    </xsl:template>

    <xsl:template match="num">
        <group>
            <xsl:copy-of select=
                ".|following::num
                [not(position() > $pGroupSize -1)]"/>
        </group>
    </xsl:template>
</xsl:stylesheet>

With the XSLT 2 or 3 for-each-group you can simply select and group as needed:

<xsl:template match="/Root">
    <xsl:copy>
        <nums>
            <xsl:for-each-group select="nums/num" group-adjacent="(position() - 1) idiv $pGroupSize">
                <group>
                    <xsl:sequence select="current-group()"/>
                </group>
            </xsl:for-each-group>
        </nums>            
    </xsl:copy>
</xsl:template>

https://xsltfiddle.liberty-development.net/jyH9rMA/2:

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

    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space elements="*"/>

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

    <xsl:mode on-no-match="shallow-copy"/>

    <xsl:template match="/Root">
        <xsl:copy>
            <nums>
                <xsl:for-each-group select="nums/num" group-adjacent="(position() - 1) idiv $pGroupSize">
                    <group>
                        <xsl:sequence select="current-group()"/>
                    </group>
                </xsl:for-each-group>
            </nums>            
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

I have used group-adjacent instead of the group-by (shown in https://stackoverflow.com/a/7320527/252228) as that way the solution adapts better to streaming with Saxon 9.8 EE or Exselt

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

    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space elements="*"/>

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

    <xsl:mode on-no-match="shallow-copy" streamable="yes"/>

    <xsl:template match="/Root">
        <xsl:copy>
            <nums>
                <xsl:for-each-group select="nums/num" group-adjacent="(position() - 1) idiv $pGroupSize">
                    <group>
                        <xsl:sequence select="current-group()"/>
                    </group>
                </xsl:for-each-group>
            </nums>        
        </xsl:copy>

    </xsl:template>

</xsl:stylesheet>

so you could use the stylesheet for huge XML inputs without running into memory problems.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • Dear Martin, I've been getting acquainted with _descendant_ and _following_. A question: For Solution 1, when the _ ... I use In the output, a couple of __ elements of the 2nd __ branch appear (duplicated) in the 1st __. Would you please explain why it is so? – Bumblevee Aug 14 '18 at 14:39
  • Consider to ask that with a new question where you show minimal but complete samples of XML input, XSLT you have, the result you want and the one you get. – Martin Honnen Aug 14 '18 at 18:53