dagblog.
← Archive

Replacing <xsl:call-template> with <xsl:apply-templates>


I'll start with a few thoughts on XML data formats for displaying the result of an SQL query. I'm interested less in particular formats than in how the XML is structured, how readable it is, and how easily XSLT can process it. Why don't I share Roman's liking for formats with generic tag names such as <datapacket> and why do I prefer names based on real-world objects? That discussion is a warm-up for the main subject, which I've been planning to write about for a long time. With an article this long, I'll use up my blog allowance for the next six months :-) Why do I prefer applied templates in XSLT (<xsl:template match=...>, <xsl:apply-templates ...>) to named ones (<xsl:template name=...>, <xsl:call-template ...>), and why can common XSLT tasks be solved much more elegantly with applied templates, even if a solution with named templates looks clearer at first glance (especially when seasoned with <xsl:for-each>, which reminds me of the saying “A real programmer can write Fortran programs in any language.” :-).

In our intranet applications, a response usually comes from the result of an SQL query. To display that result we use XML data, then generate HTML by applying an XSLT transformation. Tabular data is what we use most often, so it's important to handle it efficiently while keeping the code readable and making use of XSLT's strengths. Let's demonstrate that by displaying the result of 
select id, name, born from person.

XML data format

One question we've discussed is the format of the XML data. There are two ways to go. The first is to treat the returned data primarily as a table and name the XML tags accordingly. The root element then has a shallow tree underneath it, usually two levels deep. The XML might look like this:

<datapacket>
 <rowdata name="person">
  <coldata name="id">1</coldata>
  <coldata name="name">Adámek</coldata>
  <coldata name="born">1.2.1934</coldata>
 </rowdata>
 <rowdata name="person">
  ...
</datapacket>

Some formats of this kind are well known, though I don't know whether they're standardized. I've encountered MIDAS and DIML, for example. In any case, anyone who has read from a database and converted the data to XML must have thought of this approach. If they didn't use a known format or one defined in-house, they must have designed their own. The benefits are obvious: simple code for producing XML and a template that's easy to write. The drawback is that the XML doesn't correspond as well to real-world objects. What matters to us isn't that we have a row, but that we have a person! The row is only an implementation detail, and giving the tag that name makes the work of eventually publishing the data harder, from data exchange to readability and probably more. In my view, a name that expresses the essence of the object belongs on the tag, while an attribute describes a property the object can do without and still be what it is.

The second approach, leaving aside hybrids of the two, could use an XML format like this:

<people>
 <person>
  <id>1</id>
  <name>Adámek</name>
  <born>1.2.1934</born>
 </person>
 <person>
  ...
</people>

The format is easier to read, and the tag name immediately tells you what object it represents. Here it makes sense to give the children of a <person> tag further structure, for example <children><child id="101" /><child id="102" /></children>, whereas doing that with a <coldata> tag would be confusing.

Using applied templates

The first approach becomes even more tempting when you discover that you need to display a table the same way in two different XSL files for different queries. Say our second query is select iddoc, title, content from document. With the first approach, the easiest thing is to put <xsl:include href="common.xsl" /> in both XSL files and define the templates in common.xsl as follows:

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

<xsl:template match=“rowdata”> <tr> <xsl:apply-templates select=“coldata” /> </tr> </xsl:template>

Where you want the table to appear, just write

… <xsl:apply-templates select=“datapacket” /> …

With the second approach, you have to define a template in each file because each one matches a different tag. The first file defines the template below, and the second does the equivalent.

… <xsl:apply-templates select=“people” /> …

<xsl:template match=“people”> <table> <xsl:apply-templates select=”*” /> </table> </xsl:template>

<xsl:template match=“person”> <tr> <xsl:apply-templates select="" /> </tr> </xsl:template>

Our tags now have good names, but we’ve gained more code and repeated the same things in several places. We can’t put the templates in common.xsl as they stand, because they match different tags every time. We could put the templates <xsl:template match=“people|documents”> and <xsl:template match=“person|document”>in the common file, but that’s not general enough. Each new tag would add another alternative to the match expression. We’d also be asking for trouble if a table element and a row element had the same name. Let’s look at two other solutions instead:

1. Use named templates. Define the following template in common.xsl. Call it with <xsl:call-template select=“maketable”><xsl:with-param name=“rootnode” select=“people” >.

<xsl:template name=“maketable”>
<xsl:param name=“rootnode”>
<table>
<xsl:for-each select=“$rootnode/”>
<xsl:call-template name=“makerow”>
<xsl:with-param name=“rownode” select=”.” />
</xsl:call-template>
</xsl:for-each>
</table>
</xsl:template>

Define makerow and the others similarly. In my view, this is the second-worst way to do it. The only worse one would be to inline the body of makerow into maketable and end up with a template that looks more like a procedural program, one long string with several nested loops. Using named templates with <xsl:for-each> doesn’t suit these cases because it

  • changes the context node and disrupts the natural traversal of the document through applied templates
  • prevents use of the default applied template
  • lets you define output for several kinds of node in one template, so each node no longer owns its output and the code becomes harder to read
  • allows no duplicate named template, even though multiple applied templates can match a node and follow a conflict-resolution rule. Duplicate named templates are an error. The “later wins” rule doesn’t apply, which makes exceptional behavior impossible to define that way.

All in all, this approach leads to procedural programming, which may explain its popularity, and leaves the main strengths of a declarative language unused. Named templates still have uses. They’re a good substitute for functions that, surprisingly, XSLT 1.0 lacks, such as replacing part of a string or working with dates. They’re also useful for generating repeated fragments of HTML or fragments unrelated to any node in the XML, like a row of navigation buttons. They can be useful even with nodes as parameters, if the work lies outside the natural traversal of nodes with <xsl:apply-templates>. For example, suppose we want to color rows according to people’s ages. We use <xsl:apply-templates> to reach the <person>tag, then in the matching template we call <xsl:call-template name=“colorize”><xsl:with-param name=“bornnode” select=“born”>, which only sets the background of the <tr>element. To produce output for the <born> node, we go back to <xsl:apply-templates>.

2. Use applied templates with a mode. The last drawback of the first solution brings us back to declarative programming and applied templates. We need a way to keep the idea of naming the template while still having it applied. That’s where modes come in. This time the call will look like <xsl:apply-templates select=”.” mode=“maketable”> and the template in common.xsl will look like this:

<xsl:template match="" mode=“maketable”>
<table>
<xsl:apply-templates select="" mode=“makerow” />
</table>
</xsl:template>

This removes practically all the drawbacks of the first solution. When we build the table, we traverse the nodes with <xsl:apply-templates> and each node’s template contains only the code directly responsible for displaying it. If a table, row or cell must render differently, we can make an exception simply by defining a higher-priority template, declared later than the common one. We can still have differently named nodes each time, and anyone using the first data format can use the same technique.

We’ve replaced a named template with an applied template, turning the template name into a mode name. The rootnode node, previously passed as a parameter, is now the template’s context node. In general a named template needn’t take any parameters. In that case, match can specify any node that occurs once in the XML. I tend to write match=”/”. By the way, you can pass parameters to an applied template too, with <xsl:with-param>.

A framework for similar documents

This substitution makes things easier when we’re creating several documents with the same skeleton and different details. We can define the skeleton in common.xsl and put only the details in the individual stylesheets. For example, say we want the first page to greet us with an alert, while another page needs special styles. Here’s what goes in common.xsl:

<xsl:template match=”/”>
<html>
<head>
<xsl:apply-templates match=”/” mode=“specialni-styly” />
</head>
<body>
<xsl:apply-templates match=”/” mode=“uvitaci-alert” />
<xsl:apply-templates select=“root” />
</body>
</html>
</xsl:template>

<xsl:template match=”/” mode=“specialni-styly” />

<xsl:template match=”/” mode=“uvitaci-alert” />

Then the XSL file for the first page only needs this:

<xsl:template match=”/” mode=“uvitaci-alert”>
<xsl:attribute name=“onload”>alert(‘ahoj Hele, tady Jů!’);</xsl:attribute>
</xsl:template>

Similarly, the XSL file for the page with special styles will have only a template with mode=“specialni-styly”. Of course we have to include common.xsl at the start so the “last wins” rule takes effect. If we used <xsl:call-template>, we’d have to define an empty body in all the stylesheets except the special one, and adding a template to the skeleton would require changing every stylesheet. This way including a template is optional. If a template is important enough that every XSL file must implement it, then a named template is appropriate.

Conclusion

XML formats for tabular data with different tag names and the same or similar structure can be transformed into output in a uniform way. XSL is expressive enough to minimize repetition in the code. It therefore needn’t constrain the choice of XML format. Exceptions can be expressed easily by overriding selected parts, affecting only as many templates as necessary. The instruction <xsl:apply-templates> can replace <xsl:call-template>, which lets us design the skeletons more efficiently.

Medium version · Archived original page

Related articles

Related CZ Podcast episodes