From your comment in response to Rubens Farias's answer (and really, that's something you should edit your question to include), it seems that you want a generic way to transform any group of adjacent BulletText
elements into a list. That gets us to two questions: how do we find such groups, and having found them, how do we transform them into a list?
To find a group, we need to look for all BulletText
elements whose immediately preceding sibling isn't a BulletText
element. Each one of those starts a group, and those are the elements that we're going to transform into lists. So the first thing we want to do is create an XPath expression that will find them:
BulletText[not(preceding-sibling::*[1][name()='BulletText'])]
If you look at the predicates in that XPath expression, it's doing just what I said we need to do: it matches a BulletText
element if it's not the case that its first preceding sibling (preceding-sibling::*[1]
) has a name of BulletText
. Note that if the element has no preceding sibling, this expression will still match it.
So now we can create a template that matches these start-of-group elements. What do we put inside this template? We're going to transform these elements into LIST
elements, so the template's going to start out looking like:
<LIST>
...
</LIST>
Easy enough. But how do we find the elements that are going to populate that list? There are two cases we have to deal with.
The first is simple: if all of the following siblings are BulletText
elements, we want to populate the list with this element and all of its following siblings.
The second is harder. If there's a following sibling that's not a BulletText
element, we want our list to be all of the children of the current element's parent, starting at the current element and ending before the stop element. Here is a case where we need to use the count()
function to calculate the starting and ending indexes, and the position()
function to find the position of each element.
The completed template looks like this:
<xsl:template match="BulletText[not(preceding-sibling::*[1][name()='BulletText'])]">
<!-- find the element that we want to stop at -->
<xsl:variable name="stop" select="./following-sibling::*[name() != 'BulletText'][1]"/>
<LIST>
<xsl:choose>
<!-- first, the simple case: there's no element we have to stop at -->
<xsl:when test="not($stop)">
<xsl:apply-templates select="." mode="item"/>
<xsl:apply-templates select="./following-sibling::BulletText" mode="item"/>
</xsl:when>
<!-- transform all elements between the start and stop index into items -->
<xsl:otherwise>
<xsl:variable name="start_index" select="count(preceding-sibling::*) + 1"/>
<xsl:variable name="stop_index" select="count($stop/preceding-sibling::*)"/>
<xsl:apply-templates select="../*[position() >= $start_index
and position() <= $stop_index]"
mode="item"/>
</xsl:otherwise>
</xsl:choose>
</LIST>
</xsl:template>
You need two other templates. One converts BulletText
elements into items - we use mode
here so that we can apply it to BulletText
elements without invoking the template we're currently using:
<xsl:template match="BulletText" mode="item">
<ITEM>
<xsl:value-of select="."/>
</ITEM>
</xsl:template>
Then you also need a template that keeps BulletText
elements that our first template doesn't match from generating any output (because if we're using the identity transform, they'll just get copied if we don't):
<xsl:template match='BulletText'/>
Because of the magic of XSLT's template precedence rules, any BulletText
element that both templates match will be transformed by the first one, and this one will catch the rest.
Just add those three templates to the identity transform and you're good to go.