xslt-2.0

Split XML based on Xpath


I am in need to split the XML file based on this Xpath //book/part/chapter/sect

XML coding:

<book>
<prelims>This is prelims</prelims>
<part>This is part
<chapter>This is Chapter
<level><sect>This is sect1</sect></level>
<level><sect>This is sect2</sect></level>
<level><sect>This is sect3</sect></level>
<level><sect>This is sect4</sect></level>
</chapter>
</part>
</book>

Split XML output 1 (Prelims if exist then will split with the first sect):

<book>
<prelims>This is prelims</prelims>
<part>This is part
<chapter>This is Chapter
<level><sect>This is sect1</sect></level>
</chapter>
</part>
</book>

Split XML output 2:

<book>
<part>This is part
<chapter>This is Chapter
<level><sect>This is sect2</sect></level>
</chapter>
</part>
</book>

Split XML output 3:

<book>
<part>This is part
<chapter>This is Chapter
<level><sect>This is sect3</sect></level>
</chapter>
</part>
</book>

Split XML output 4:

<book>
<part>This is part
<chapter>This is Chapter
<level><sect>This is sect3</sect></level>
</chapter>
</part>
</book>

I am struck with the below XSLT

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="2.0">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="no" exclude-result-prefixes="#all"/>
    <xsl:template match="@*|node()">
    <xsl:copy><xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
    </xsl:template>
    
    <xsl:template match="//sect">        
        <xsl:for-each select=".">
            <xsl:variable name="increment" select="position()"/>
     <xsl:result-document href="try{$increment}.xml">         
         <xsl:copy><xsl:apply-templates/></xsl:copy>
     </xsl:result-document>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

Solution

  • EDIT after change example in question from

    <sect>This is sect1</sect>

    to

    <level><sect>This is sect1</sect></level>

    Try this:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      exclude-result-prefixes="xs"
      version="2.0">
      <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="no" exclude-result-prefixes="#all"/>
    
      <xsl:template match="@*|node()">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
      </xsl:template>
      
      <!-- Use the root as context to build your splits -->
      <xsl:template match="/">
        <xsl:for-each select="book/part/chapter/level/sect">
          <xsl:variable name="increment" select="position()"/>
          <xsl:result-document href="try{$increment}.xml">         
            <xsl:apply-templates select="/*">
              <xsl:with-param name="increment" select="$increment" tunnel="yes" />
            </xsl:apply-templates>
          </xsl:result-document>
        </xsl:for-each>
      </xsl:template>
      
      <!-- Only use prelims if its's the first result-document -->
      <xsl:template match="prelims">
        <xsl:param name="increment" tunnel="yes"/>
        <xsl:if test="$increment=1">
          <xsl:next-match/>
        </xsl:if>
      </xsl:template>
      
      <!-- Use that level that is equal to the postion of the result-document 
      Since we might also have a text-node (This is Chapter) we use the count(preceding-sibling::level)
      -->
      <xsl:template match="level">
        <xsl:param name="increment" tunnel="yes"/>
        <xsl:if test="$increment - 1 =  count(preceding-sibling::level)">
          <xsl:next-match/>
        </xsl:if>
      </xsl:template>
    
    </xsl:stylesheet>