Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

split child elements into sub sections

Tags:

xslt

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>
like image 737
MitchBroadhead Avatar asked Nov 28 '25 12:11

MitchBroadhead


1 Answers

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)

like image 155
Ian Roberts Avatar answered Nov 30 '25 06:11

Ian Roberts



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!