More group slicing in XSLT


To continue on my previous example on slicing (I made the term up, by the way), today we'll do something a little different.


Let's say that instead of just inserting the slice elements and a few items, we want to copy the whole tree n times (where n is the number of slices), each time with a different slice of items. That includes elements beyond the things we're currently slicing.


So let's say we have this document, similar to the previous one.


<group groupattribute='1'>
  <repeat-me>each time</repeat-me>
  <item item='1'>1 <sub>text</sub> </item>
  <item item='2'>2 <sub>text</sub> </item>
  <item item='3'>3 <sub>text</sub> </item>
  <item item='4'>4 <sub>text</sub> </item>
  <item item='5'>5 <sub>text</sub> </item>
</group>


And we want to get this effect.


<root>
  <group groupattribute="1">
    <repeat-me>each time</repeat-me>
    <item item="1">1 <sub>text</sub></item>
    <item item="2">2 <sub>text</sub></item>
  </group>
  <group groupattribute="1">
    <repeat-me>each time</repeat-me>
    <item item="3">3 <sub>text</sub></item>
    <item item="4">4 <sub>text</sub></item>
  </group>
  <group groupattribute="1">
    <repeat-me>each time</repeat-me>
    <item item="5">5 <sub>text</sub></item>
  </group>
</root>


Here is a stylesheet that can accomplish this, using parameters to pass around the replacements nodes for each copy of the tree. As always, variations in the comments are welcome.


<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
 <xsl:output method='xml' indent='yes'/>

 <!-- Just copy everything. -->
 <xsl:template match='@* | node()' mode='everything'>
  <xsl:copy>
   <xsl:apply-templates select='@* | node()' mode='everything'/>
  </xsl:copy>
 </xsl:template>

 <!-- Copy everything but replace items with the replacement parameter. -->
 <xsl:template match='@* | node()' mode='withreplacement'>
  <xsl:param name='replacement' />
  <xsl:copy>
   <!-- Copy everything except item elements. -->
   <xsl:apply-templates select='@* | node()[local-name() != "item"]' mode='withreplacement'>
    <xsl:with-param name='replacement' select='$replacement' />
   </xsl:apply-templates>

   <!-- If this is where items are, apply the replacement node-set. -->
   <xsl:if test='item'>
    <xsl:apply-templates select='$replacement' mode='everything' />
   </xsl:if>
  </xsl:copy>
 </xsl:template>

 <!--
 We matched the first of a slice of items.
 Copy everything from the root, passing the current item
 and its sibling(s) as the replacement.
 -->
 <xsl:template match='item' mode='firstofslice'>
  <xsl:apply-templates select='/group' mode='withreplacement'>
   <xsl:with-param
    name='replacement'
    select='. | following-sibling::item[1]'
    />
  </xsl:apply-templates>
 </xsl:template>

 <!-- Match to root, and put everything under a single element. -->
 <xsl:template match='/'>
  <root>
   <!-- Grab every second item to create a new group. -->
   <xsl:apply-templates select='group/item[position() mod 2 = 1]' mode='firstofslice' />
  </root>
 </xsl:template>
</xsl:stylesheet>


Enjoy!

Skip to main content