XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

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.



1 to 11 of 11
  1. cosplay
    2010-07-27 23:42:08 cosplaywedding
  2. fiwedding
    2010-06-18 20:06:12 fiwedding
  3. Using this approach?
    2005-11-07 12:41:40 mseashor
  4. Help?
    2005-05-30 20:49:59 sk8e8
  5. Namespace Issues
    2003-12-10 10:26:52 John Timm
  6. Tips on Using this
    2002-08-04 10:38:05 Wiffbi Feon
  7. Using this approach?
    2002-04-22 01:02:40 D Hohls
  8. Why not downloadable ?
    2002-04-13 07:16:27 Armin Ehrenfels
  9. how do i run this???
    2002-04-11 00:47:42 Mikkel Bruun
  10. Performance?
    2002-04-03 09:05:32 Alex Valdez
  11. Wrong character encoding?
    2002-03-28 02:49:34 Ian Young
1 to 11 of 11