XSLT generic template to generate lists in HTML

204 views Asked by At

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.

2

There are 2 answers

3
michael.hor257k On BEST ANSWER

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.

4
Michael Kay On

In XSLT 3.0 (with XPath 3.1) you could do

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

and

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

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

By wrapping your "collection" into an array, you make it a single item, matched by a single invocation of a template rule, which can output the required <ul> element.