Templates

Templates are the cornerstones of XSL style sheets.

Looping with apply-templates

We have already seen an example of a template in the XSL 101 chapter.

<xsl:template match="/Books/Book">
  <fo:block font-weight="bold">Book #<xsl:value-of select="position()" /></fo:block>
  <fo:block>Name: <xsl:value-of select="./Name" /></fo:block>
  <fo:block>Author: <xsl:value-of select="./Author" /></fo:block>
</xsl:template>

Used as:

<xsl:apply-templates select="/Books/Book"/>

When processed, the template will be executed for every individual book, outputting its position in the collection, as well as its name and author.

Named templates

Now, let's look at a completely different scenario: suppose you would like to display a block with rounded corners. Since XSL does not support border-radius, this can become a bit of a pickle. The best way to achieve this we have found is using SVG:

<fo:instream-foreign-object
  content-type="image/svg+xml"
  content-width="100"
  content-height="100">
  <svg width="100" height="100">
    <rect x="0" y="0"
      rx="10" ry="10"
      width="100" height="100"
      style="fill:#dddddd;stroke:black;stroke-width:1"/>
  </svg>
</fo:instream-foreign-object>

The snippet of code above creates a square with grayish background (#dddddd), framed by a black border. It has rounded corners and the corner radius is specified via the rx and ry attributes. In our case, it is 10pt.

Great, we have a hack for rounded corners. But imagine writing this every single time! That would certainly be no fun. But fortunately, there is a better way of doing it: named templates.

<xsl:template name="roundedCorners">
  <xsl:param as="xs:number" name="width"/>
  <xsl:param as="xs:number" name="height"/>
  <xsl:param as="xs:string" name="background" select="'#dddddd'"/>
  <xsl:param as="xs:number" name="radius" select="10" />

  <fo:instream-foreign-object
    content-type="image/svg+xml"
    content-width="{$width}"
    content-height="{$height}">
    <svg width="{$width}" height="{$height}">
      <rect
        x="0"
        y="0"
        rx="{$radius}"
        ry="{$radius}"
        width="{$width}"
        height="{$height}"
        style="fill:{$background};stroke:black;stroke-width:1"/>
    </svg>
  </fo:instream-foreign-object>
</xsl:template>

With this beauty around, when you want to create the exact same block as we created above, you simply call:

<xsl:call-template name="roundedCorners">
  <xsl:with-param name="width" select="100"/>
  <xsl:with-param name="height" select="100"/>
</xsl:call-template>

Explanation

Nice, isn't it? Let's briefly go over the syntax:

  1. A named template is declared using the <xsl:template name="..."> tag.
  2. Parameters are specified using the <xsl:param name="..."> tag and they can have a default value, provided via the select attribute. Default values! Plenty of "real programming languages" do not offer that functionality.
  3. A named template returns the last object in its declaration. In our case, it is the entire <fo:instream-foreign-object> element.
  4. It is invoked using the <xsl:call-template> tag, parameter values supplied via its child <xsl:with-param> elements.

Named templates vs. Functions

You might be asking: "So what's the difference between named templates and functions?". It's certainly a valid question as they share a significant portion of their properties. While I am no guru on the topic, the following differences come to mind:

  1. If you want to use a result of a function call in <xsl:value-of select="call(...)">, you need to be invoking a function. There is no way to shove a named template in an attribute.
  2. Functions do not offer the possibility of having default values for parameters, while named templates do.

In general, I tend to use functions for smaller operations, typically returning individual plain values. Named templates come in handy when you want to have more flexibility and you want nodes or node sets to be returned.

results matching ""

    No results matching ""