xmlxsltxslt-1.0

How to select ranges of siblings in XSLT?


Suppose I have the following XML:

<?xml version="1.0" encoding="utf8"?>
<test>
  <list>
    <li>a</li>
    <li>a</li>
    <li begin="true">b</li>  <!-- begin of the "b" list -->
    <li>b</li>
    <li>b</li>
    <li end="true">b</li>  <!-- end of the "b" list -->
    <li>c</li>
    <li>c</li>
  </list>
</test>

and I wanted to split this list into three lists based on the begin and end attributes using XSLT 1.0, such that the result is:

  <list>
    <li>a</li>
    <li>a</li>
  </list>

  <list>
    <li>b</li>
    <li>b</li>
    <li>b</li>
    <li>b</li>
  </list>

  <list>
    <li>c</li>
    <li>c</li>
  </list>

I managed to select the first and third list but struggle with matching the range of the middle list:

<xsl:template match="list">
  <list>
    <xsl:apply-templates select="li[@begin = 'true']/preceding-sibling::li" />
  </list>
  <list>
    <xsl:apply-templates select="li[...]" />  <!-- Hmm 🤔 -->
  </list>
  <list>
    <xsl:apply-templates select="li[@end = 'true']/following-sibling::li" />
  </list>
</xsl:template>

I tried various expressions using position() and count() but can't quite get this right.

Addendum: the attributes begin and end are unique, i.e. there is exactly one such group in the original list.


Solution

  • In the given example, with only 1 marked group (so only 3 groups in total), you could use:

    <list>
        <xsl:apply-templates select="li[@begin='true']" />
        <xsl:apply-templates select="li[@begin='true']/following-sibling::li[not(preceding-sibling::li[@end='true'])]"/>
    </list>
    

    (I have split this into 2 separate instructions for easier reading.)


    Added:

    If you are using the libxslt processor, you can take advantage of some extension functions that it supports and do:

    <xsl:template match="list">
        <xsl:variable name="head" select="set:leading(li, li[@begin = 'true'])"/>
        <xsl:variable name="tail" select="set:trailing(li, li[@end = 'true'])"/>
        <list>
            <xsl:apply-templates select="$head"/>
        </list>
        <list>
            <xsl:apply-templates select="set:difference(li, $head | $tail)"/>
        </list>
        <list>
            <xsl:apply-templates select="$tail"/>
        </list>
    </xsl:template>
    

    provided you declare:

    xmlns:set="http://exslt.org/sets"
    extension-element-prefixes="set"
    

    in the xsl:stylesheet start-tag.