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.
Share your comments and questions about this article with the author and other readers in our forum.
(* You must be a member of XML.com to use this feature.)
Comment on this Article
| Titles Only | Titles Only | Newest First |
- Using this approach?
2005-11-07 12:41:40 mseashor [Reply]
One aspect of this article that was sorely underlooked is a simple sentence as to its actual use (see comments over the last three years). To an experienced XSLT developer, it should be obvious, but if you're new, it might not.
Long story short: YOU NEED TO DEFINE THE PARAMETER "template-uri" to be equal to the templates name!!! Using xalan, it works like this to run template9: java org.apache.xalan.xslt.Process -IN collection.xml -XSL transform9.xslt -PARAM template-uri template9.xml -OUT result9.html
A skim through the article shows that the author considered telling us how to run, saying:
"There's a couple of things to note about this example. First, this transform requires a parameter named template-uri. This URI is used as the input to the document function to retrieve the actual contents of our template."
Unfortunately, though this makes sense to those experienced with XSL. This contains jargon like URI and an offhanded reference to "parameter," which without a knowledge of XSL's parameter tag, doesn't make much sense (why would we need a parameter...is it just passed in by another file?).
Hopefully, in the future, a small section on how the author actually ran the transformations (maybe just in a README file of the example) would be most helpful.
- Help?
2005-05-30 20:49:59 sk8e8 [Reply]
The first Element in my XML is this: <dsTimeSheets xmlns="http://www.tempuri.org/dsTimeSheets.xsd"> and I cannot capture it using <template match=> bit in xslt because of the xmlns bit in there. Is there anything I can do to get around it.
Thanks
- Namespace Issues
2003-12-10 10:26:52 John Timm [Reply]
I want the output of the transform to be fully-compliant XHTML. The only way to do this seems to be to make the default namespace of the transform that of XHTML strict. The <xsl:element> method of copying elements from the source tree to the result tree doesn't preserve the default namespace of the template document even if I have a root element of:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:my="http://xml.com/my-template-language" xml:lang="en" lang="en">
I can I do this without losing the genericity of the transform (output agnostic).
Thanks,
JT
- Namespace Issues
2005-11-30 00:56:53 tkk [Reply]
A bit late, but I recently had the same issue as John Timm in creating an identity transform XSLT - to preserve the namespace of the original document elements in the output document you need to add a "namespace" attribute to your <xsl:element>, eg instead of:
<xsl:element name="{name()}">
use:
<xsl:element name="{name()}" namespace="{namespace-uri()}">
Hope that helps someone out there!
- Namespace Issues
- Tips on Using this
2002-08-04 10:38:05 Wiffbi Feon [Reply]
Hi,
as the first Post asked, how to use this, I recommend reading the cited article "Style-free XSLT Style Sheets" by Eric van der Vlist at http://www.xml.com/pub/a/2000/07/26/xslt/xsltstyle.html
One Problem occurred to me (I work with the XSLT Sablotron and PHP): It only works if you add the attribute version="1.0" to the xsl:stylesheet-TAG.
Hope that helps,
wiffbi
- Using this approach?
2002-04-22 01:02:40 D Hohls [Reply]
How do I get the two tranformation files (transform1.xslt and template1.xml) to operate on the source document (collection.xml)??
Thanks.
- Why not downloadable ?
2002-04-13 07:16:27 Armin Ehrenfels [Reply]
Great article,
it's a pitty that articles like this can't be downloaded in HTML or PDF format.
- Why not downloadable ?
2002-04-13 08:50:27 Edd Dumbill [Reply]
You can save the HTML version: select the "Print this article" link to get a printable version, and then save that.
- Why not downloadable ?
- how do i run this???
2002-04-11 00:47:42 Mikkel Bruun [Reply]
Could anyone please show me how to run these samples? or just give me some pointers...
I know how to transform xml via xsl, but here we have a temaplate, xml source document and xsl
preferably using xalan...
thx
- Performance?
2002-04-03 09:05:32 Alex Valdez [Reply]
What are the performance implications of this approach compared to having XSL and HTML together? The answer probably depends on the XSLT engine although I suspect there's going to be a slight performance penalty in most cases. Any thoughts?
- Wrong character encoding?
2002-03-28 02:49:34 Ian Young [Reply]
"Sigur Rós", "Ágćtis Byrjun"
- Wrong character encoding?
2002-03-28 15:25:31 Edd Dumbill [Reply]
We had a little production issue with the encoding, since fixed. But thanks for reporting it. ED.
- Wrong character encoding?
