Tuesday, November 23, 2004

Two step view pattern implementation with XSLT

For the project I'm currently working on (in Python/WebKit) I decided to use an XSL technology to handle a presentation issue, instead of a more stock options like Cheetah or PSP. The main reason for this was to escape from using niche solutions and try to employ more mainstreamo ones instead, leveraging benefits that comes from this.

For the presentation logic itself, I used a Two-Step View pattern, publicized by Martin Fowler's excellent book on enterprise patterns. The original implementation actually performed two xsl transformation. First one with a page-specific template, which were passed a page-specific data as XML input string and/or parameters dict. It returned an xml with a "logical markup" which were then passed to another xsl template, called site.xsl which is global per site and transformed this xml-based markup into proper (X)HTML.

It worked OK, except the fact that I had to call to XSL engine twice per page, in order to get a resulting (X)HTML which taken a considerable amount of time (about 0.2-0.4 seconds in total).
But recently, due to a nice tip from 4Suite co-author, I managed to fold these two steps into a single xsl transformation. The trick is a node-set() function which allows you apply the XSL engine to a fragment of output tree already produced.

Here is except from my implementation. The algorithm is as follows:
  1. Build a "logical markup" tree and save it to a variable.
  2. Apply an imported templates to transform this to required output format.

<xsl:import href="site.xsl" />


<xsl:output method="xml"
doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
encoding="UTF-8"/>


<xsl:template match="/">
<xsl:variable name="view">
<xsl:call-template name="logical" />
</xsl:variable>

<xsl:apply-templates select="exslt:node-set($view)" mode="view"/>
</xsl:template>

<xsl:template name="logical">
<view:standard-page title="Test page">

<view:content>
<p>This is a test page</p>
</view:content>
</view:standard-page>
</xsl:template>


And here is a greatly simplified version of the site.xsl page:

<?xml version="1.0" encoding="UTF-8" ?>

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns="http://www.w3.org/1999/xhtml"
xmlns:view="http://postman.com.ua/xslt/view10">

<xsl:output method="xml" encoding="UTF-8"/>

<xsl:template match="view:standard-page" mode="view">
<html>
<head>
<title><xsl:value-of select="@title" /></title>

</head>
<body>
<xsl:apply-templates select="view:content" mode="view" />
</body>

</html>
</xsl:template>

<xsl:template match="view:content" mode="view">
<div style="min-height: 400px;">

<xsl:apply-templates mode="view"/>
</div>
</xsl:template>

<xsl:template match="*" mode="view"> <xsl:copy-of select="current()" /></xsl:template>

</xsl:stylesheet>

Being an XSLT newbie myself, the actual code is a bit awkward: I have to use a special namespace (view) and mode parameter of the xsl:template to escape possible infinite loops and to get namespaces right in the output document. Hopefully, an XSLT expert could provide a more streamlined implementation of the same idea.

The node-set() extension function support both by MSXML and 4Suite XSLT which I happened to use on this project. The only minor problem is that you have to declare the namespace a bit differently for these two engines.

My conclusions? I was able to simplify the presentation logic and get a considerable speed up (in a 2x-10x range) with the price of some boilerplate code to add to each xsl page and potential portability problem between other XSL engines. Looks truly like a win to me.

PS: Glad to have a NoPaste service at hand.

0 Comments:

Post a Comment

<< Home