An web-application is providing me an XML-feed, which I can\'t change. What I want to do is split this XML-feed into several unordered lists. I\'m trying to do this with the
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" encoding="utf-8" />
<xsl:template match="/NavigationTree">
<xsl:apply-templates select="Page[position() mod 3 = 1]" mode="ul"/>
</xsl:template>
<xsl:template match="Page" mode="li">
<li class="{position() mod 3}">
<a href="{@FriendlyHref}">
<xsl:value-of select="@MenuText" disable-output-escaping="no"/>
</a>
</li>
</xsl:template>
<xsl:template match="Page" mode="ul">
<ul>
<xsl:apply-templates select=". | following-sibling::Page[position() < 3]" mode="li"/>
</ul>
</xsl:template>
</xsl:stylesheet>
Output:
<ul>
<li class="1">
<a href="/nl-nl/klantenservice/bestellen.aspx">Bestellen</a>
</li>
<li class="2">
<a href="/nl-nl/klantenservice/betalen.aspx">Betalen</a>
</li>
<li class="0">
<a href="/nl-nl/klantenservice/retourneren.aspx">Retourneren</a>
</li>
</ul>
<ul>
<li class="1">
<a href="/nl-nl/klantenservice/garantie.aspx">Garantie</a>
</li>
<li class="2">
<a href="/nl-nl/klantenservice/veel-gestelde-vragen.aspx">Faq</a>
</li>
<li class="0">
<a href="/nl-nl/klantenservice/contact.aspx">Contact</a>
</li>
</ul>
I. XSLT 1.0 .
Here is a short and parameterized solution showing how to split siblings elements in groups of predefined size. No explicit conditional XSLT instructions are used:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pGroupSize" select="3"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<nums>
<xsl:apply-templates select=
"num[position() mod $pGroupSize = 1]"/>
</nums>
</xsl:template>
<xsl:template match="num">
<group>
<xsl:copy-of select=
".|following-sibling::*
[not(position() > $pGroupSize -1)]"/>
</group>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
the wanted, correct result is produced:
<nums>
<group>
<num>01</num>
<num>02</num>
<num>03</num>
</group>
<group>
<num>04</num>
<num>05</num>
<num>06</num>
</group>
<group>
<num>07</num>
<num>08</num>
<num>09</num>
</group>
<group>
<num>10</num>
</group>
</nums>
II. XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pGroupSize" select="3"/>
<xsl:template match="/*">
<nums>
<xsl:for-each-group select="*"
group-by="(position() -1) idiv $pGroupSize">
<group>
<xsl:sequence select="current-group()"/>
</group>
</xsl:for-each-group>
</nums>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), the same correct result is produced:
<nums>
<group>
<num>01</num>
<num>02</num>
<num>03</num>
</group>
<group>
<num>04</num>
<num>05</num>
<num>06</num>
</group>
<group>
<num>07</num>
<num>08</num>
<num>09</num>
</group>
<group>
<num>10</num>
</group>
</nums>
Explanation:
Use of <xsl:for-each-group>
where the selected nodes are grouped by the sequential number of the group they belong to.
Use of the standard XSLT 2.0 function current-group()
.
To do this, you need to match on the Page element that it is position 1, 4, 7, etc... In other words, where there position() mod 3 equals 1.
<xsl:if test="position() mod 3 = 1">
This gives the first element of the list. You can then get the remaining 2 elements in the this, like so
<xsl:apply-templates select=".|following-sibling::Page[position() < 3]" mode="list"/>
Putting this altogether, gives the following XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" encoding="utf-8"/>
<xsl:param name="html-content-type"/>
<xsl:param name="group-size" select="3"/>
<xsl:template match="/NavigationTree">
<xsl:if test="count(//Page) > 0">
<xsl:apply-templates select="Page"/>
</xsl:if>
</xsl:template>
<xsl:template match="Page">
<xsl:if test="position() mod $group-size = 1">
<ul>
<xsl:apply-templates select=".|following-sibling::Page[position() < $group-size]" mode="list"/>
</ul>
</xsl:if>
</xsl:template>
<xsl:template match="Page" mode="list">
<li class="{position()}">
<xsl:text disable-output-escaping="yes"><![CDATA[» ]]></xsl:text>
<a>
<xsl:attribute name="href">
<xsl:value-of select="@FriendlyHref" disable-output-escaping="yes"/>
</xsl:attribute>
<xsl:value-of select="@MenuText" disable-output-escaping="no"/>
</a>
</li>
</xsl:template>
</xsl:stylesheet>
When run on your input XML, this should generate the following output
<ul>
<li class="1">» <a href="/nl-nl/klantenservice/bestellen.aspx">Bestellen</a></li>
<li class="2">» <a href="/nl-nl/klantenservice/betalen.aspx">Betalen</a></li>
<li class="3">» <a href="/nl-nl/klantenservice/retourneren.aspx">Retourneren</a></li>
</ul>
<ul>
<li class="1">» <a href="/nl-nl/klantenservice/garantie.aspx">Garantie</a></li>
<li class="2">» <a href="/nl-nl/klantenservice/veel-gestelde-vragen.aspx">Faq</a></li>
<li class="3">» <a href="/nl-nl/klantenservice/contact.aspx">Contact</a></li>
</ul>
Do note I have parameterised the group size, allowing you to easily change to 4 or 5 elements per list, for example.