Re: [xsl] XML structure to HTML unordered list

Subject: Re: [xsl] XML structure to HTML unordered list
From: Abel Braaksma <abel.online@xxxxxxxxx>
Date: Sun, 09 Sep 2007 14:06:08 +0200
Kaz wrote:

And here's my index.xsl file


<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>


<xsl:output method="html" omit-xml-declaration="yes"/>

   <xsl:template match="root_element">
       <html>
       <body>
           <xsl:call-template name="unordered-list">
               <xsl:with-param name="items" select="item"/>
           </xsl:call-template>
       </body>
       </html>
   </xsl:template>

   <xsl:template name="unordered-list">
       <xsl:param name="items" select="/.."/>
       <ul>
           <xsl:for-each select="$items">
               <li>
                   <xsl:value-of select="text()"/>
                   <xsl:if test="item">
                       <xsl:call-template name="unordered-list">
                           <xsl:with-param name="items" select="item"/>
                       </xsl:call-template>
                   </xsl:if>
               </li>
           </xsl:for-each>
       </ul>
   </xsl:template>

</xsl:stylesheet>

The above produces what I want, and it seems quite solid. But is there perhaps another (better?) way to solve this task? I'm limited to XSLT 1.0.


Yes, in fact, there is. You seem to use skills with a large resemblance to structured programming in an imperative language (i.e., in normal English: you define a template with a name and call it, analogous to a function in C++/VB/Ruby/Python/PHP etc). In XSLT you give the processor a set of nodes (xsl:apply-templates select="nodeselection") to be applied to specified templates (xsl:template match) that match a certain node in the set. The processor will to the math for you: it will find the most suitable matching template and you will have to do less coding and your code is more adaptable to change.


In general, if you find you are doing something like <xsl:if test="somenode"> you should consider replacing it with an <xsl:apply-templates select="somenode" />. The templates will not be applied if the node is not there (nothing to apply), so that's an inherent "if statement" already. Furthermore, the processor will choose the right matching template (which you still have to write for "somenode" of course). This looks as follows:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
<xsl:output method="html" />
<xsl:template match="root_element">
<html>
<body>
<ul>
<xsl:apply-templates select="item" />
</ul>
</body>
</html>
</xsl:template>
<xsl:template match="item">
<li>
<xsl:value-of select="text()"/>
<xsl:apply-templates select="item" />
</li>
</xsl:template>
<xsl:template match="item[item]">
<li>
<xsl:value-of select="text()"/>
<ul>
<xsl:apply-templates select="item" />
</ul>
</li>
</xsl:template>
</xsl:stylesheet>



If you write a stylesheet like that, think in reverse: think by yourself "I want an html list with <ul> elements, what is the rule that outputs a <ul> element?". Then you look into your source and you consider that the rule is any <item> element with a child of one or more <item> elements: <xsl:template match="item[item]">.


Then you consider "what is the rule that outputs a <li> element?" And your answer will likely be "with any <item> element, both the ones with and the ones without <item> children". Which is why the <li> tags are put inside both matches.

Finally, you want to know what to process next, after you output an <ul> or an <li> node. In this case it is simple: all <item> children. This results in the <xsl:apply-templates select="item" /> on both places.

You may wonder why I split the item and the item[item]. The reason is: readability and adaptability. Now you have two places where you output <li>, but you also have more control on how to do markup for either case: with children or without.

I changed two other small things: the encoding (I believe it is best to do any XML related work in UTF-8/16 unless you have compelling reasons not to internationalize your code and/or make it more interoperable), which should also be changed in your input, and the 'omit-xml-declaration', which has no place in an xsl:output when the output method is "html" (html output method *never* has an xml declaration, 'omit-xml-declaration', whatever its setting, will be ignored).

Have fun with it (and with XSLT of course)!

Cheers,
-- Abel Braaksma

Current Thread