Parse repeating child elements in non-repeating parent node

I have the following input xml and was wondering if XSLT can handle such transformation. If it can, how can it be achieve?

input xml:

<foo>
  <bar>
    <A>xxx</A>
    <B>yyy</B>
    <C>zzz</C>
    <A>aaa</A>
    <B>bbb</B>
    <C>ccc</C>
     ...
     ..
  </bar>
</foo>

output xml:

<data>
   <A>xxx</A>
   <B>yyy</B>
   <C>zzz</C>
</data>
<data>
   <A>aaa</A>
   <B>bbb</B>
   <C>ccc</C>
</data>
....

There could be more repeating A, B, C nodes in the example above. Since the repetition isn't in a repeating parent node, it's not possible to use for each. I was exploring the option of for-each-group but not sure if that is applicable. Would appreciate any advise.

Last updated:11/6/2014 3:12:31 AM

1 Answers

Jayden Bell
Jayden Bell

Using XSLT 1.0, following solution is working - tested with your example XML with 6 elements:

<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:output method='xml' indent='yes'/>
<xsl:template match='/*'>
 <xsl:apply-templates select='/foo/bar'/>
</xsl:template>
 
<xsl:template match='bar'>
 <xsl:for-each select='*/node()'>
   <xsl:if test='(position()-1) mod(3) = 0'>
    <data>
     <xsl:call-template  name='grouping'>
      <xsl:with-param name='position' select='position()'/>
      <xsl:with-param name='amount' select='3'/>
     </xsl:call-template>
    </data>
   </xsl:if>
  </xsl:for-each>
</xsl:template>
 
<xsl:template  name='grouping'>
<xsl:param name='position' select='0'/>
<xsl:param name='amount' select='0'/>
<xsl:copy-of select='//bar/*[$position]'/>
   <xsl:if test='$amount > $position or   ($position mod(3) > 0)'>
    <xsl:call-template  name='grouping'>
      <xsl:with-param name='position' select='$position + 1'/>
      <xsl:with-param name='amount' select='$amount'/>
    </xsl:call-template>
   </xsl:if>
 </xsl:template>
</xsl:stylesheet>

Result:

<?xml version='1.0' encoding='UTF-8'?>
<data>
   <A>xxx</A>
   <B>yyy</B>
   <C>zzz</C>
</data>
<data>
   <A>aaa</A>
   <B>bbb</B>
   <C>ccc</C>
</data>

As short explanation - in the template matching 'bar' all element-nodes are processed in

<xsl:for-each select='*/node()'>

By using <xsl:if test='(position()-1) mod(3) = 0'> (which is 0 for the 1st node and for every 3rd node) the template named 'grouping' is called to provide generating groups of 3 nodes. This template is called with the parameters  position - the position of the current node - and amount - the amount of nodes to be copied in each group.

Answer