My XHTML input:
<h1 class="section">Text</h1>
<h2 class="section">More text</h2>
Desired XHTML output:
<div class="section">
<h1 class="section">Text</h1>
<h2 class="section">More text</h2>
</div>
Therefore, the group-adjacent approach seems suitable. The code below will do the right thing with the h1 and h2 nodes, but it will erase everything else in the <body> node including the <body> tags themselves.
Clearly I’m making an error, but I’m not enough of an expert with for-each-group to understand where it is.
Thanks.
<xsl:template match="xhtml:body">
<xsl:for-each-group select="xhtml:h1|xhtml:h2" group-adjacent="@class">
<xsl:choose>
<xsl:when test="current-grouping-key()='section'">
<xsl:element name="div">
<xsl:attribute name="class">
<xsl:value-of select="current-grouping-key()"/>
</xsl:attribute>
<xsl:apply-templates select="current-group()"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
Update: What I didn’t understand is that for-each-group essentially acts as a filter on whatever node you point it at. Therefore, if you want to preserve every subnode, the original command has to include select="*". Furthermore, the grouping rule has to ensure that every subnode will end up in a group. This means group-adjacent is not right tool for the job; group-starting-with is.
The template below separates the whole <body> of the XHTML file into groups that start with h1. (Be warned: This grouping rule relies on the assumption that an h1 is always the first subnode in the <body> of the XHTML.) Then I loop over the groups, using a conditional to look at the first two nodes in each group to see if they match my criteria. If so, I wrap them in my <div>.
I’d still be interested whether there’s a more idiomatic XSLT solution, as what I’ve done is basically write Python within XSLT.
<xsl:template match="xhtml:body">
<xsl:copy>
<!--Divide file into groups of nodes starting with h1 tags-->
<xsl:for-each-group select="*" group-starting-with="xhtml:h1">
<xsl:choose>
<!-- if the group starts with h1.section + h2.section -->
<xsl:when test="current-group()[1][@class='section'] and current-group()[2][name() = 'h2'] and current-group()[2][@class = 'section']">
<!--wrap in a div tag-->
<div class="section">
<xsl:apply-templates select="current-group()[1]"/>
<xsl:apply-templates select="current-group()[2]"/>
</div>
<!--then process the rest of the nodes in this group normally-->
<xsl:apply-templates select="current-group()[position()>=3]"/>
</xsl:when>
<xsl:otherwise>
<!--process normally-->
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
In your update to the question, you’ve identified part of the answer. However, it’s possible to solve this kind of problem with group-adjacent. The usual pattern is to compute a grouping key of “true” for the elements to be wrapped as a group, and “false” for the others. So the coding pattern is: