Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I split an <xsl:foreach> into multiple parts?

Tags:

xml

cdata

xslt

I have a list of elements that I want to split into individual lists of 3. The end result would be something like this:

<ul>
    <li>element</li>
    <li>element</li>
</ul>
<ul>
    <li>element</li>
    <li>element</li>
</ul>
<ul>
    <li>element</li>
    <li>element</li>
</ul>

My XSLT is like this, but it doesn't work, because I can't insert </ul>, and I can't insert a less than sign (<).

<ul>
    <xsl:for-each select="$myroot/item">
        <li></li>

        <xsl:if test="position() mod $maxItemsPerColumn = 0">
            <!-- I want to close my ul, and start a new one here, but it doesn't work! -->
        </xsl:if>
    </xsl:for-each>
</ul>

Any ideas? Thanks in advance!

like image 919
John B Avatar asked Dec 18 '25 00:12

John B


2 Answers

You don't need to do anything fancy like recursion. And good lord, don't even contemplate using CDATA.

You just have to think like XSLT and ask: "What input element do I want to transform into my output element?"

Assuming that each ul is supposed to contain N items, you want to transform every Nth input item, starting with the first, into a ul:

<xsl:variable name="n" select="number(4)"/>

<xsl:template match="/">
  <output>
    <xsl:apply-templates select="/root/item[position() mod $n = 1]"/>
  </output>
</xsl:template>

Each of these item elements becomes a ul that contains the item and each of its N-1 following siblings:

<xsl:template match="item">
  <ul>
    <xsl:for-each select=". | following-sibling::item[position() &lt; $n]">
      <li>
        <xsl:value-of select="."/>
      </li>
    </xsl:for-each>
  </ul>
</xsl:template>

Assuming an input document like this:

<root>
  <item>1</item>
  <item>2</item>
  <item>3</item>
  <item>4</item>
  <item>5</item>
  <item>6</item>
  <item>7</item>
  <item>8</item>
  <item>9</item>
</root>

...you get this output, if $n is set to 4:

<output>
  <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
  </ul>
  <ul>
    <li>5</li>
    <li>6</li>
    <li>7</li>
    <li>8</li>
  </ul>
  <ul>
    <li>9</li>
  </ul>
</output>
like image 50
Robert Rossney Avatar answered Dec 19 '25 16:12

Robert Rossney


You can do it using a recursive solution:

<xsl:call-template name="group">
  <xsl:with-param name="items" select="$myroot/item" />
</xsl:call-template>

<xsl:template name="group">
  <xsl:param name="items" />
  <xsl:if test="count($items) > 0">
    <ul>
      <xsl:for-each select="$items[position() &lt;= 3]">
        <li>...</li>
      </xsl:for-each>
    </ul>
    <xsl:call-template name="group">
      <xsl:with-param name="items" select="$items[position() > 3]" />
    </xsl:call-template>
  </xsl:if>
</xsl:template>

What this does is call the group template for the whole list of items. The group template writes out the first three elements of the list (or fewer if there aren't three) inside <ul> ... </ul> tags. Then it calls itself again with the rest of the list of items omitting the first three. When the list is empty, the group template does nothing.

XSLT is a very functional language, and following the rules (ie. not using disable-output-escaping for this) will save you from much pain and suffering in the future when you need to modify your templates again.

like image 20
Greg Hewgill Avatar answered Dec 19 '25 16:12

Greg Hewgill



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!