xslt to HTML Transform question

A

Adam dR.

I have an xml file similar to:
<menu>
<menuitem name="home" show="false"/>
<menuitem name="about" show="true"/>
<menuitem name="links" show="true"/>
<menuitem name="games" show="true"/>
<menuitem name="learn" show="true"/>
<menuitem name="order" show="true"/>
<menuitem name="contact" show="true"/>
<menuitem name="support" show="false"/>
<menuitem name="feedback" show="true"/>
....
</menu>

What I am trying to achieve is a table that takes every four menuitems
that are show="true" and put them in a table row and then put each
menuitem is a <td>..

Desired output:
<table>
<tr>
<td>about</td>
<td>links</td>
<td>games</td>
<td>learn</td>
</tr>
<tr>
<td>order</td>
<td>contact</td>
<td>feedback</td>
</tr>
</table>

I cannot figure out how to loop four and place them in a <tr> tag

What I have tried:
<xsl:template match="/">
<table>
<xsl:apply-templates select="menu"/>
</table>
</xsl:template>

<xsl:template match="menu">
<xsl:for-each select="menuitem[@show='true']">

<xsl:if test="position() mod 4 = 1">
<xsl:text >&lt;tr&gt;</xsl:text>
</xsl:if>

<td><xsl:value-of select="@name"/></td>

<xsl:if test="position() mod 4 = 0">
<xsl:text>&lt;tr&gt;</xsl:text>
</xsl:if>

</xsl:for-each>
</xsl:template>

There are a couple problems with this, 1. the position is still the
position of all menuitems true or false so I am not guaranteed 4 items
per row. 2. and most of all if actually right "<tr>" rather then
placing the <tr> tag in the html doc.

Any help would be great!

~Adam dR.
 
X

xmlator

Michael Kay's XSLT 2nd Edition has a decent explanation of
this class of problem, which can be found by looking in the index
for "tail recursion".
There are a couple problems with this, 1. the position is still the
position of all menuitems true or false

Couldn't quite grok that statement. Seems to me position()
would reflect the position of the nodelist formed by the
for-each select clause. xsltproc seemed to agree with my
assumption. Maybe I've missed something here.
per row. 2. and most of all if actually right "<tr>" rather then
placing the <tr> tag in the html doc.

That gets you to the nut: using recursion instead of iteration.
Here's one possible solution:

<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
version="1.0"<xsl:eek:utput method="html" indent="yes"/>

<xsl:template name="EmitRows">
<xsl:param name="MenuItemList" />

<xsl:if test="count($MenuItemList) != 0">
<tr>
<xsl:for-each select="$MenuItemList[position() &lt; 5]">
<td><xsl:value-of select="@name" /></td>
</xsl:for-each>
</tr>
<xsl:call-template name="EmitRows">
<xsl:with-param name="MenuItemList"
select="$MenuItemList[position() &gt; 4]" />
</xsl:call-template>
</xsl:if>

</xsl:template>

<xsl:template match="/">
<table>
<xsl:apply-templates select="menu"/>
</table>
</xsl:template>

<xsl:template match="menu">
<xsl:call-template name="EmitRows">
<xsl:with-param name="MenuItemList"
select="./menuitem[@show='true']" />
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>

xsltproc applies this XSLT file to your example XML data to produce:

<table>
<tr>
<td>about</td>
<td>links</td>
<td>games</td>
<td>learn</td>
</tr>
<tr>
<td>order</td>
<td>contact</td>
<td>feedback</td>
</tr>
</table>

Basic technique is to write a callable template that just returns
if the list is empty. If it's not empty, it does something useful with
one or more items at the front of the list, then passes all the other
items to itself recursively.

Hope this helps,
Ron Burk
www.xmlator.com
 
D

Dimitre Novatchev

Michael Kay's XSLT 2nd Edition has a decent explanation of
this class of problem, which can be found by looking in the index
for "tail recursion".

Not all XSLT processors handle tail recursion gracefully. It is safer to use
another general method that just shortens dramatically the maximum size of
the call-stack needed -- DVC (Divide and Conquer). For example, to process
recursively a list of 1000000 (1M) items, the DVC method will use a stack
with maximum depth ~ 19 (log2(N) ).

One can read more about implementing DVC in XSLT here:

http://www.topxml.com/code/default.asp?p=3&id=v20020107050418


Or I'd recommend that one uses some of the most generic functions for
recursive processing, such as foldl(), implemented in the FXSL library. It
comes with most functions already implemented DVC, so one will not have to
do anything in addition but just use the functions.

Find more about FXSL here:

http://fxsl.sf.net (especially read the latest paper for the "Extreme
Markup Languages 2007" conference).


Cheers,
Dimitre Novatchev


Michael Kay's XSLT 2nd Edition has a decent explanation of
this class of problem, which can be found by looking in the index
for "tail recursion".
There are a couple problems with this, 1. the position is still the
position of all menuitems true or false

Couldn't quite grok that statement. Seems to me position()
would reflect the position of the nodelist formed by the
for-each select clause. xsltproc seemed to agree with my
assumption. Maybe I've missed something here.
per row. 2. and most of all if actually right "<tr>" rather then
placing the <tr> tag in the html doc.

That gets you to the nut: using recursion instead of iteration.
Here's one possible solution:

<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
version="1.0"<xsl:eek:utput method="html" indent="yes"/>

<xsl:template name="EmitRows">
<xsl:param name="MenuItemList" />

<xsl:if test="count($MenuItemList) != 0">
<tr>
<xsl:for-each select="$MenuItemList[position() &lt; 5]">
<td><xsl:value-of select="@name" /></td>
</xsl:for-each>
</tr>
<xsl:call-template name="EmitRows">
<xsl:with-param name="MenuItemList"
select="$MenuItemList[position() &gt; 4]" />
</xsl:call-template>
</xsl:if>

</xsl:template>

<xsl:template match="/">
<table>
<xsl:apply-templates select="menu"/>
</table>
</xsl:template>

<xsl:template match="menu">
<xsl:call-template name="EmitRows">
<xsl:with-param name="MenuItemList"
select="./menuitem[@show='true']" />
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>

xsltproc applies this XSLT file to your example XML data to produce:

<table>
<tr>
<td>about</td>
<td>links</td>
<td>games</td>
<td>learn</td>
</tr>
<tr>
<td>order</td>
<td>contact</td>
<td>feedback</td>
</tr>
</table>

Basic technique is to write a callable template that just returns
if the list is empty. If it's not empty, it does something useful with
one or more items at the front of the list, then passes all the other
items to itself recursively.

Hope this helps,
Ron Burk
www.xmlator.com
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,982
Messages
2,570,185
Members
46,736
Latest member
AdolphBig6

Latest Threads

Top