Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations IamaSherpa on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

XPath filter to return all nodes between two nodes

Status
Not open for further replies.

honestrosewater

Programmer
May 14, 2010
3
US
I have a set of sibling item elements. Some of them have a child head element (headed items), and some do not (non-headed items).
Code:
<source>
 <item><head/>1</item>
 <item>2</item>
 <item>3</item>
 <item><head/>4</item>
 <item>5</item>
 <item>6</item>
 <item>7</item>
<source>
I want to use their headedness to group them in the output:
Code:
<output>
 <list>
  <item>1</item>
  <item>2</item>
  <item>3</item>
 </list>
 <list>
  <item>4</item>
  <item>5</item>
  <item>6</item>
  <item>7</item>
 </list>
</output>
So I want each headed item to generate a list with itself as the first child and every non-headed item between it and the next headed item as the tail (remaining children).

So I want a while loop, but I am not sure how to implement this in XSLT. I am trying various combinations of for-each, choose, and if with no success yet.

Thanks!

P.S. The distance between each successive headed items is unpredictably variable.
 
I figured out one way to do it.

For each headed item, count its following siblings and subtract from this the number of siblings following the next headed item, effectively giving the position of the next headed item. (This could be shortened with an improved position function.) Then process each of the following siblings according to whether their position is less than the position of the next headed item.

This seems too ugly, so I'm still looking for a better solution. The code ends up looking like this:
Code:
<xsl:template match="item[head]">
 <xsl:param name="following-siblings" 
   select="./following-sibling::item"/>
 <xsl:param name="pos-next-headed" 
   select="count($following-siblings) - count($following-siblings[head][1]/following-sibling::item)"/>
 <list>
  <xsl:copy-of select="."/>
  <xsl:for-each select="$following-siblings">
   <xsl:choose>
    <xsl:when test="position() < $pos-next-headed">
     <xsl:copy-of select="."/>
    </xsl:when>
    <xsl:otherwise/>
   </xsl:choose>
  </xsl:for-each>
 </xsl:element>
</xsl:template>
 
You can do it by calling a named template recursively, like this.
[tt]
<xsl:template match="source">
<source>
<xsl:apply-templates select="item[head]" />
</source>
</xsl:template>
<xsl:template match="item[head]">
<list>
<xsl:copy-of select="." />
<xsl:call-template name="continue">
<xsl:with-param name="next" select="following-sibling::item[1]" />
</xsl:call-template>
</list>
</xsl:template>
<xsl:template name="continue">
<xsl:param name="next" />
<xsl:choose>
<xsl:when test="$next/head">
<!-- do nothing -->
</xsl:when>
<xsl:eek:therwise>
<xsl:copy-of select="$next" />
<xsl:if test="$next/following-sibling::item">
<xsl:call-template name="continue">
<xsl:with-param name="next" select="$next/following-sibling::item[1]" />
</xsl:call-template>
</xsl:if>
</xsl:eek:therwise>
</xsl:choose>
</xsl:template>[/tt]
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top