Reputation: 363
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
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
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
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