Search code examples
xsltstructureiterative-deepening

XSLT deepening content structure


given the following structure:

<TITEL1>...</TITEL1>  
<p>..</p>
<TITEL2>...</TITEL2>  
<TITEL3>...</TITEL3>
<TITEL3>...</TITEL3>  
<P>...<P>  

is there a way to get to this:

<TITEL1>
    <TITEL>...</TITEL>  
    <p>...</p>
    <TITEL2>
        <TITEL>...</TITEL>  
        <TITEL3>
            <TITEL>...</TITEL>
            <P>...</P>
        </TITEL3>
        <TITEL3>
            <TITEL>...</TITEL>
            <P>...</P>
        </TITEL3>
    </TITEL2>
</TITEL1>

or in other words,is there a way to have higher level titels inclose lower level titels and all content that follows them, thus creating a nested structure. The content of each TITEL1,2 and 3 tag should go into a new <TITEL>-element


Solution

  • With XSLT 2.0 (as implemented by Saxon 9 or AltovaXML tools) you can use xsl:for-each-group group-starting-with and a recursive function:

    <xsl:stylesheet
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      version="2.0"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema"
      xmlns:mf="http://example.com/2010/mf"
      exclude-result-prefixes="xsd mf">
    
      <xsl:output indent="yes"/>
    
      <xsl:function name="mf:nest" as="element()*">
        <xsl:param name="elements" as="element()*"/>
        <xsl:param name="level" as="xsd:integer"/>
        <xsl:for-each-group select="$elements" group-starting-with="*[starts-with(local-name(), concat('TITEL', $level))]">
          <xsl:choose>
            <xsl:when test="self::*[starts-with(local-name(), concat('TITEL', $level))]">
              <xsl:element name="TITEL{$level}">
                <xsl:apply-templates select="."/>
                <xsl:sequence select="mf:nest(current-group() except ., $level + 1)"/>
              </xsl:element>
            </xsl:when>
            <xsl:otherwise>
              <xsl:apply-templates select="current-group()"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:for-each-group>
      </xsl:function>
    
      <xsl:template match="ROOT">
        <xsl:sequence select="mf:nest(*, 1)"/>
      </xsl:template>
    
      <xsl:template match="@* | node()">
        <xsl:copy>
          <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="*[starts-with(local-name(), 'TITEL')]">
        <TITEL>
          <xsl:apply-templates select="@* | node()"/>
        </TITEL>
      </xsl:template>
    
    </xsl:stylesheet>
    

    With that stylesheet the input

    <ROOT>
    <TITEL1>Titel 1, 1</TITEL1>  
    <p>..</p>
    <TITEL2>Titel 2, 1</TITEL2>  
    <TITEL3>Titel 3, 1</TITEL3>
    <TITEL3>Titel 3, 2</TITEL3>  
    <P>...</P>
    </ROOT>
    

    is transformed to the output

    <TITEL1>
       <TITEL>Titel 1, 1</TITEL>
       <p>..</p>
       <TITEL2>
          <TITEL>Titel 2, 1</TITEL>
          <TITEL3>
             <TITEL>Titel 3, 1</TITEL>
          </TITEL3>
          <TITEL3>
             <TITEL>Titel 3, 2</TITEL>
             <P>...</P>
          </TITEL3>
       </TITEL2>
    </TITEL1>