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>
开发者_JAVA技巧</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!
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 item
s, 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() < $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>
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() <= 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.
you could try something like this (not tested, but you've got the idea )
(...)
<xsl:call-template name="recursive">
<xsl:with-param name="root" select="$myroot"/>
<xsl:with-param name="index" select="number(1)"/>
</xsl:call-template>
(...)
<xsl:template name="recursive">
<xsl:param name="root"/>
<xsl:param name="index"/>
<ul>
<li><xsl:value-of select="$root/item[$index]"/></li>
<li><xsl:value-of select="$root/item[$index +1 ]"/></li>
</ul>
<xsl:if test="$root/item[$index+2]">
<xsl:call-template name="recursive">
<xsl:with-param name="root" select="$root"/>
<xsl:with-param name="index" select="$index+2"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
you can do
<xsl:text disable-output-escaping="yes"><![CDATA[</ul>]]></xsl:text>
<xsl:text disable-output-escaping="yes"><![CDATA[<ul>]]></xsl:text>
so in your case:
<ul>
<xsl:for-each select="$myroot/item">
<li></li>
<xsl:if test="position() mod $maxItemsPerColumn = 0">
<xsl:text disable-output-escaping="yes"><![CDATA[</ul>]]></xsl:text>
<xsl:text disable-output-escaping="yes"><![CDATA[<ul>]]></xsl:text>
</xsl:if>
</xsl:for-each>
</ul>
精彩评论