In XSLT 2.0 I would approach the problem using for-each-group
:
<xsl:template match="content">
<ul>
<xsl:for-each-group select="*" group-starting-with="sh1">
<li>
<h1><xsl:value-of select="." /></h1>
<xsl:if test="current-group()[2]">
<ul>
<xsl:apply-templates select="current-group() except ." />
</ul>
</xsl:if>
</li>
</xsl:for-each-group>
</ul>
</xsl:template>
<xsl:template match="sh2">
<li><xsl:value-of select="." /></li>
</xsl:template>
If you're restricted to 1.0 then a key based approach as suggested by michael.hor257k is probably the most efficient, but the simplest fix to your existing approach would be something like
<xsl:apply-templates select="following-sibling::sh2[
generate-id(preceding-sibling::sh1[1]) = generate-id(current())]" />
preceding-sibling::sh1[1]
is the nearest preceding sh1
to the node we're filtering at the time, and current()
is the sh1
element that the template is operating on. Comparing their generate-id
values is the XSLT 1.0 way to determine whether the two expressions select the same node (if you just compared the values rather than their generated IDs then you risk false positives as two different sh1
elements with the same text would be considered equal). In 2.0 you could just use the is
operator to do this (preceding-sibling::sh1[1] is current()
).
Alternatively, you can simulate a "while loop" using a technique I've heard referred to as "sibling recursion": apply templates to just the first sh2
under this heading, and have that template recurse to the next sibling, and so on until we run out of appropriate nodes:
<ul>
<xsl:apply-templates select="following-sibling::*[1][self::sh2]" />
</ul>
and the template:
<xsl:template match="sh2">
<li><xsl:value-of select="." /></li>
<xsl:apply-templates select="following-sibling::*[1][self::sh2]" />
</xsl:template>
The "loop" will terminate as soon as following-sibling::*[1]
is not an sh2
element.