Search code examples
htmlxmlxslthtml-listsnode-set

XSLT generic template to generate lists in HTML


I would like to write some generic templates to transform collections of nodes into HTML lists. Each element of the collection should correspond to one list item. Ideally I would write

<xsl:apply-templates select="..." mode="ul"/>

along with a template which matches the individual elements in the selection, and the resulting HTML should look like

<ul>
  <li>Transformation of first element in selection</li>
  <li>Transformation of second element</li>
  ...
</ul>

That is, the content of each <li> is generated by a non-generic template; but the list structure itself is generated by a generic one. The problem is to write a generic template that produces this list structure for any non-empty collection, and no output for an empty collection.

I tried the following:

<xsl:template match="*" mode="ul">
  <xsl:if test="count(*) > 0">
    <ul>
      <xsl:apply-templates select="*" mode="li"/>
    </ul>
  </xsl:if>
</xsl:template>

<xsl:template match="*" mode="li">
  <li>
    <xsl:apply-templates select="." />
  </li>
</xsl:template>

But this doesn't work: each element of the collection will individually become a <ul>. Conceptually, what I want is a way to transform the collection itself into a <ul>, and then turn the elements of the collection into individual <li>s.

Important here:

  1. The test for the non-empty collection should be in the generic template, because I don't want to wrap every call to this template with a conditional, and I don't want to output empty <ul> elements when the collection is empty.

  2. In the XML documents I'm transforming, there is in general no common parent of the elements in the collection. That means I cannot transform the parent into the <ul> and its children into <li>s; there is no element in the source document which corresponds to the <ul>.

Is this possible? The searching I've done increasingly suggests to me that it isn't, but that seems insane to me, since this must an incredibly common use case.


Solution

  • At least in theory, you should be able to do something like this:

        <xsl:call-template name="ul">
            <xsl:with-param name="nodes" select="..."/>
        </xsl:call-template>
    

    and then:

    <xsl:template name="ul">
        <xsl:param name="nodes"/>
        <xsl:if test="$nodes">
            <ul>
                <xsl:apply-templates select="$nodes" mode="li"/>
            </ul>
        </xsl:if>
    </xsl:template>
    
    <xsl:template match="*" mode="li">
        <li>
            <xsl:apply-templates select="." />
        </li>
    </xsl:template>
    

    This would create a ul wrapper around the selected nodes before applying the li template to each element in the selection (which is, I think, what you call a collection).


    Is this a worthwhile effort? Probably not. XML inputs come in a very wide variety of schemas, and rarely can a generic stylesheet fit all. Much easier to write a specific stylesheet that handles a known schema.