Search code examples
xmlxsltxslt-1.0xslt-2.0

Nest all un-nested XML elements into a new element using XSLT


I have an XML file that needs some corrections in its structure. Because of the size of said document this has to be done automatically.
The basic structure is the following:

<body>
    <div author='author1'>
        <lb n='1'/>Text1
        <lb n='2'/>Text2
    </div>
    <lb n='3'/>Text3
    <lb n='4'/>Text4
    <div author='author1'>
        <lb n='5'/>Text5
    </div>
    <add>xyz</add>
    <lb n='6'/>Text6
    <lb n='7'/>Text7
    <lb n='8'/>Text8
</body>

What I want to do is to put everything that isn't already in div tags into div tags and mark it with the attribute @author='author2'. The text and structure of the moved chunks should stay as it is, just nested into a div. The order has to be conserved as well.
The resulting XML should look something like this:

<body>
    <div author='author1'>
        <lb n='1'/>Text1
        <lb n='2'/>Text2
    </div>
    <div author='author2'>
        <lb n='3'/>Text3
        <lb n='4'/>Text4
    </div>
    <div author='author1'>
        <lb n='5'/>Text5
    </div>
    <div author='author2'>
        <add>xyz</add>
        <lb n='6'/>Text6
        <lb n='7'/>Text7
        <lb n='8'/>Text8
    </div>
</body>

My current XSLT (which I wrote before noticing that the XML has more elements than just lb) does put lb in the correct div, but it moves everything to the bottom and deletes the text.
The XSLT looks like this:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl" version="2.0">

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

<xsl:template match="body">
        <xsl:copy>
            <xsl:apply-templates select="*[not(self::lb)]"/>
            <div>
                <xsl:attribute name="resp">author2</xsl:attribute>
                <xsl:apply-templates select="lb"/>
            </div>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

And it returns this XML:

<?xml version="1.0"?>
<body><div author="author1">
        <lb n="1"/>Text1
        <lb n="2"/>Text2
    </div><div author="author1">
        <lb n="5"/>Text5
    </div><add>xyz</add><div resp="author2"><lb n="3"/><lb n="4"/><lb n="6"/><lb n="7"/><lb n="8"/></div></body>

What am I doing wrong?
Thanks for your help in advance!


Solution

  • As an alternative to using group-adjacent, you could use group-starting-with on the xsl:for-each-group

    Try this XSLT

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
      <xsl:output method="xml" indent="yes"/>
      <xsl:strip-space elements="*" />
    
      <xsl:template match="@* | node()">
        <xsl:copy>
          <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="body">
        <xsl:copy>
          <xsl:for-each-group select="node()" group-starting-with="div">
            <xsl:apply-templates select="." />
            <xsl:if test="current-group()[2]">
              <div resp="author2">
                <xsl:apply-templates select="current-group()[position() gt 1]"/>
              </div>
            </xsl:if>
          </xsl:for-each-group>
        </xsl:copy>
      </xsl:template>
    </xsl:stylesheet>