Template Languages in XSLT
by Jason Diamond
|
Pages: 1, 2, 3
Instruction Parameters (attributes)
So far, our instructions have required no parameters but there's nothing preventing us from implementing the instructions so that they can take advantage of them. Like XSLT, our instructions can be parameterized via attributes.
For example, imagine we want to give the template designer the ability to
sort the list of albums she iterates over. Using a sort-by
attribute, she could specify whether she'd like the albums sorted by artist
name or album title with the values "artist" or "title" (which are,
conveniently, the names of the elements that hold that information).
Example 16. A parameterized sort instruction (transform8.xslt)
<xsl:template match="my:for-each-album[@sort-by]">
<xsl:variable
name="sort-by"
select="@sort-by" />
<xsl:variable
name="current"
select="." />
<xsl:for-each
select="$source/collection/album">
<xsl:sort
select="*[local-name() = $sort-by]" />
<xsl:apply-templates
select="$current/node()">
<xsl:with-param
name="current-album"
select="." />
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
Unfortunately, XSLT doesn't allow the select attribute to
contain any sort of attribute value template. Hence, we've been forced to
duplicate the existing XSLT template that implemented
my:for-each-album instruction so that we can offer both the
default sort and the customized one. The default priority rules state that
match patterns containing a predicate have a higher priority than match
patterns containing nothing but a QName so the correct XSLT template will be
chosen automatically by the processor.
Attribute Value Templates
Attribute value templates in XSLT are the XPath expressions that appear in
curly braces in attribute values. Without this extremely convenient shortcut,
we'd be forced to use the xsl:attribute instruction whenever we
needed to dynamically compute an attribute's value.
It would be nice if we could offer the same shortcut for our template
language. Consider the case of outputting a link to a collection owner's
email address. We could do this by implementing an instruction that outputs
an a element with an href attribute containing the
owner's email address but that is problematic in several ways. First, it
assumes that the template designer is outputting HTML. Second, it makes it
impossible for the template designer to add other attributes to the
a element (like id, class, or title). What we really need are
attribute value templates.
Example 17. Attribute value templates (template9.xml)
<html xmlns:my="http://xml.com/my-template-language">
<body>
<h1><my:owner-name />'s Collection</h1>
<my:if-owner-has-email>
<address>
<a href="mailto:{$owner-email}">
<my:owner-email />
</a>
</address>
</my:if-owner-has-email>
</body>
</html>
We've borrowed the syntax from XSLT but that's not required since we can't really get an XSLT compliant processor to evaluate arbitrary XPath expressions. The only form of attribute value templates that our template language supports are simple variable substitutions like the above.
In order to perform the substitutions, we have to modify the XSLT template that matches all attributes to call a recursive named template. That template will scan the attribute value for "{$" and attempt to invoke the appropriate instruction based on the string found after "{$" and before "}".
Example 18. Implementing attribute value templates (transform9.xslt)
<xsl:template match="@*">
<xsl:attribute name="{name()}">
<xsl:call-template name="attribute">
<xsl:with-param
name="value"
select="string(.)" />
</xsl:call-template>
</xsl:attribute>
</xsl:template>
<xsl:template name="attribute">
<xsl:param name="value" />
<xsl:choose>
<xsl:when test="contains($value, '{$')">
<xsl:value-of
select="substring-before($value, '{$')" />
<xsl:variable
name="name"
select="substring-before(
substring-after($value, '{$'), '}')" />
<xsl:variable
name="node"
select="document('')/
xsl:transform/
my:*[local-name() = $name]" />
<xsl:choose>
<xsl:when test="$node">
<xsl:apply-templates
select="$node" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of
select="concat('{$', $name, '}')" />
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="attribute">
<xsl:with-param
name="value"
select="substring-after(
substring-after(
$value, '{$'), '}')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of
select="$value" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<my:owner-email />
In order to try and make adding new attribute value template variables as
easy as possible, the attribute XSLT template looks for a node
with the correct namespace name and local name in the XSLT document itself.
It only uses this node to get xsl:apply-templates to evaluate
the correct XSLT template for our desired variable/instruction.
For example, to add an $owner-name attribute value template
variable, all we would need to do is add an empty my:owner-name
element to the transform. The attribute XSLT template would then
use that to invoke the my:owner-name instruction without any
requiring any other modifications.
Conclusion
Getting XSLT to process your custom templates isn't as easy as I would like it to be, but once the initial framework is created, adding new instructions and variables is relatively painless. Creating a prototype with XSLT is certainly the quickest way to go as you can easily add new instructions when your template designer needs them. I've personally used the techniques described in this article to prototype a template language with close to 200 instructions. The templates that utilized those instructions were still preferable to hardcoded XPath/XSLT, and it was possible to re-implement the template language processor in a more efficient language (a subject for another article) once the design was finalized without requiring any changes to the templates themselves.
- cosplay
2010-07-27 23:42:08 cosplaywedding - fiwedding
2010-06-18 20:06:12 fiwedding - Using this approach?
2005-11-07 12:41:40 mseashor - Help?
2005-05-30 20:49:59 sk8e8 - Namespace Issues
2003-12-10 10:26:52 John Timm - Namespace Issues
2005-11-30 00:56:53 tkk - Tips on Using this
2002-08-04 10:38:05 Wiffbi Feon - Using this approach?
2002-04-22 01:02:40 D Hohls - Why not downloadable ?
2002-04-13 07:16:27 Armin Ehrenfels - Why not downloadable ?
2002-04-13 08:50:27 Edd Dumbill - how do i run this???
2002-04-11 00:47:42 Mikkel Bruun - Performance?
2002-04-03 09:05:32 Alex Valdez - Wrong character encoding?
2002-03-28 02:49:34 Ian Young - Wrong character encoding?
2002-03-28 15:25:31 Edd Dumbill