Search code examples
xmlxsltxslt-2.0

Split / Group in XSLT with parent elements


I'm trying to split an XML document into fixed blocks. I want so split this document into n Message-Nodes where each contains a maximum of x (here 2) line elements.

My Source looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<Messages>
    <Message>
        <Control>
            <ctrl1>aaa</ctrl1>
            <ctrl2>...</ctrl2>
        </Control>
        <Body>
            <header1>bbb</header1>
            <header2>bbb</header2>
            <header3>
                <something>ccc</something>
            </header3>
            <line>
                <content>ddd</content>
            </line>
            <line>
                <content>eee</content>
            </line>
            <line>
                <content>fff</content>
            </line>
            <line>
                <content>ggg</content>
            </line>
            <line>
                <content>...</content>
            </line>
        </Body>
    </Message>
</Messages>

With the following XSLT I was able to create the needed Message Nodes and also have the line-spit working. But I'm not able to manage it to copy all the other elements (Control, Body, header...) into each Message node.

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="Message">
        <xsl:for-each-group select="Body/line" group-adjacent="(position() - 1) idiv 2">
            <xsl:element name="Message">
                <xsl:copy-of select="current-group()"/>
            </xsl:element>
        </xsl:for-each-group>
    </xsl:template>

</xsl:stylesheet>

Current Result:

<?xml version="1.0" encoding="UTF-8"?>
<Messages>
   <Message>
      <line>
         <content>ddd</content>
      </line>
      <line>
         <content>eee</content>
      </line>
   </Message>
   <Message>
      <line>
         <content>fff</content>
      </line>
      <line>
         <content>ggg</content>
      </line>
   </Message>
   <Message>
      <line>
         <content>...</content>
      </line>
   </Message>
</Messages>

But should be:

<?xml version="1.0" encoding="UTF-8"?>
<Messages>
    <Message>
        <Control>
            <ctrl1>aaa</ctrl1>
            <ctrl2>...</ctrl2>
        </Control>
        <Body>
            <header1>bbb</header1>
            <header2>bbb</header2>
            <header3>
                <something>ccc</something>
            </header3>
            <line>
                <content>ddd</content>
            </line>
            <line>
                <content>eee</content>
            </line>
        </Body>
    </Message>
    <Message>
        <Control>
            <ctrl1>aaa</ctrl1>
            <ctrl2>...</ctrl2>
        </Control>
        <Body>
            <header1>bbb</header1>
            <header2>bbb</header2>
            <header3>
                <something>ccc</something>
            </header3>
            <line>
                <content>fff</content>
            </line>
            <line>
                <content>ggg</content>
            </line>
        </Body>
    </Message>
    <Message>
        <Control>
            <ctrl1>aaa</ctrl1>
            <ctrl2>...</ctrl2>
        </Control>
        <Body>
            <header1>bbb</header1>
            <header2>bbb</header2>
            <header3>
                <something>ccc</something>
            </header3>
            <line>
                <content>...</content>
            </line>
        </Body>
    </Message>
</Messages>

Thanks a lot!


Solution

  • If you know the name of the elements you are looking for the easiest would be to select and construct them:

       <xsl:template match="Message">
            <xsl:for-each-group select="Body/line" group-adjacent="(position() - 1) idiv 2">
                <Message>
                    <xsl:copy-of select="ancestor::Message/Control"/>
                    <Body>
                       <xsl:copy-of select="ancestor::Message/Body/*[not(self::line)], current-group()"/>
                    </Body>
                </Message>
            </xsl:for-each-group>
        </xsl:template>