Avbasot
Avbasot

Reputation: 363

XSLT 2.0 grouping with <xsl:for-each-group>

I'm trying to group elements using xsl:for-each in XSLT 2.0.

Here is an illustration of my input:

<root>
     <chapter>
           <heading> heading text </heading>
           <title> title </title>
           <p> 1111 </p>
           <p> 2222 </p>
           <center> text </center>
           <p> 3333 </p>
           <title>  another title </title>
           <p> 4444 </p>
           <center> text </center>
           <p> 5555 </p>
     </chapter>
     <chapter>
          <heading> heading text </heading>
          <title>  another title </title>
          <p> 6666 </p>
          <p> 7777 </p>
          <title>  another title </title>
          <p> 8888 </p>
          <p> 9999 </p>
     </chapter>
<root>

I am trying to group this document by matching on each <title> element and grouping every following element until the next <title> into an element <section>. Here is what I want my output to look like:

<root>
     <chapter>
          <heading> Heading text </heading>
          <section>
               <title>  title </title>
               <p> 1111 </p>
               <p> 2222 </p>
               <center> text </center>
               <p> 3333 </p>
          </section>
          <section>
               <title>  title </title>
               <p> 4444 </p>
               <center> text </center>
               <p> 5555 </p>
          </section>
          <section>
               <title>  title </title>
               <p> 6666 </p>
               <p> 7777 </p>
               <center> text </center>
               <p> 8888 </p>
               <p> 9999 </p>
          </section>
     <chapter>
<root>

My current template that is not working:

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

The stylesheet above does group the sections that I want however however it also groups each <heading> element into it's own <section> for some reason. Any suggestions?

Thanks in advance.

Upvotes: 3

Views: 15731

Answers (3)

Michael Kay
Michael Kay

Reputation: 163322

I usually use an xsl:choose in the body of the for-each-group to distinguish the groups that really do start with the group-starting-with element from the "leading rump" before the first "real group". You've been given a number of other solutions that work well if you know that the "leading rump" will only contain a heading element. Another more general solution (because it makes no assumptions about what's in the rump), but along the same lines, is:

<xsl:copy-of select="title[1]/preceding-sibling::*">
<xsl:for-each-group select="title[1]/(., following-sibling::*)">
  <section>
     <xsl:copy-of select="current-group()"/>
  </section>
</xsl:for-each-group>

Upvotes: 4

Sean B. Durkin
Sean B. Durkin

Reputation: 12729

I would use...

<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:strip-space elements="*" />  

<xsl:template match="/*">
  <root>
    <xsl:for-each-group select="chapter" group-by="heading">
      <chapter>
        <xsl:copy-of select="current-group()[1]/heading" />
        <xsl:for-each-group select="current-group()/(* except heading)"
                            group-starting-with="title">
          <section>
            <xsl:copy-of select="current-group()" />
          </section>
        </xsl:for-each-group>
      </chapter>
    </xsl:for-each-group>
  </root>
</xsl:template>

</xsl:stylesheet>

Upvotes: 4

Ian Roberts
Ian Roberts

Reputation: 122364

You probably want

<xsl:copy-of select="current-group()" />

rather than value-of. For the headings, if you know that each chapter will only have one heading element then you could say

<xsl:template match="chapter">
      <xsl:copy-of select="heading" />
      <xsl:for-each-group select="*[not(self::heading)]" group-starting-with="title">
             <section>
                <xsl:copy-of select="current-group()" />
             </section>
      </xsl:for-each-group>             
</xsl:template>

i.e. pull out the heading separately and then exclude it from the set of elements to be grouped.

Upvotes: 3

Related Questions