MitchBroadhead
MitchBroadhead

Reputation: 899

split child elements into sub sections

Given the following XML

<section>
    <title>Title1</title>
    <item>Item1</item>
    <item>Item2</item>
    <title>Title2</title>
    <item>Item1</item>
    <item>Item2</item>
</section>

What is the easiest way to split at second title into this

<section>
    <subsection>
        <title>Title1</title>
        <item>Item1</item>
        <item>Item2</item>
    </subsection>
    <subsection>
        <title>Title2</title>
        <item>Item1</item>
        <item>Item2</item>
    </subsection>
</section>

for example the following template was one idea but the xpath seems tricky.

<xsl:template match="section">
    <xsl:copy>
         <subsection>
              <xsl:apply-templates select="...those before second title..."/>
         </subsection>
         <subsection>
              <xsl:apply-templates select="...those including and after second title..."/>
         </subsection>
    </xsl:copy>
</xsl:template>

Upvotes: 3

Views: 269

Answers (1)

Ian Roberts
Ian Roberts

Reputation: 122364

With XSLT 2.0 or later it's a straightforward use of for-each-group:

<xsl:template match="section">
    <xsl:copy>
         <xsl:for-each-group select="*" group-starting-with="title">
             <subsection>
                  <xsl:apply-templates select="current-group()"/>
             </subsection>
         </xsl:for-each-group>
    </xsl:copy>
</xsl:template>

In 1.0 you can achieve a similar effect with this kind of logic:

<xsl:template match="section">
    <xsl:copy>
         <xsl:for-each select="title">
             <subsection>
                  <xsl:apply-templates select=". | following-sibling::item[
                                  generate-id(preceding-sibling::title[1])
                                = generate-id(current())]"/>
             </subsection>
         </xsl:for-each>
    </xsl:copy>
</xsl:template>

The predicate finds those following sibling item elements of the current title whose nearest preceding title is the one we started from.

One difference between the two approaches is if you have any other elements in a section before the first title, then the for-each-group approach will put them in a leading subsection (which will have no title), whereas the 1.0 approach will ignore them.


If you always want exactly two subsections (with everything after the second title in the second subsection, including any further title elements) then you can just hard-code it as

<xsl:template match="section">
    <xsl:copy>
         <subsection>
              <xsl:apply-templates select="title[2]/preceding-sibling::*"/>
         </subsection>
         <subsection>
              <xsl:apply-templates select="title[2] | title[2]/following-sibling::*"/>
         </subsection>
    </xsl:copy>
</xsl:template>

(Note that if there are fewer than two title elements this will result in two completely empty <subsection/> elements)

Upvotes: 3

Related Questions