Style-free XSLT Style Sheets
July 26, 2000
One of the most oft-marketed advantages of XML is the separation between content and the layout achievable through applying external CSS or XSL style sheets to XML documents. However, since work started on XSL, the focus has shifted from presentation to transformation. This has given birth to a transformation-only language, XSLT, which is much more widely used than its formatting counterpart, XSL formatting objects.
This shift from presentation to transformation is leading to a massive injection of logic within style sheets. This mixing of presentation and logic is becoming questionable. In this article, I present a simple technique to isolate most of the presentation outside of XSLT, leading to "style free style sheets."
As more logic is embedded in the XSLT style sheets, they become more similar to programs than data. Keeping style within the style sheets causes the same kinds of problems as keeping data within programs--loss of flexibility, and lack of maintainability by anyone but programmers.
The fact that XSLT style sheets are generally pseudo-compiled in servlet environments, and that new techniques are being announced to compile them into Java byte code, enforces this tendency of XSLT style sheets to be more programs than style. The corollary of this trend is to remove as much style as possible, unless you are ready to maintain multiple compiled style sheets.
From an XSLT user's perspective, you can't expect web designers to develop XSLT style sheets, which their favorite tools lack support for. Wouldn't it be nice to let designers design the layout of a page and to add special "tags" to include its content?
A Simple Example
At this point, let's look at a simple example.
For our new portal web site, we want to produce a home page with 3 parts: the headers and headlines are common to all the pages of the site; the body is specific to each page and is itself sub-divided into a header and 2 columns.
The document we want to produce is basically a set of embedded tables whose outer content is site generic, while the inner "body" cell is page specific.
The Traditional Solution
The traditional way of achieving this through an XSLT transformation is to build the generic structure of the page into the XSLT style sheet itself:
<xsl:template match="/"> <html> <head> <!-- Get the head values here... --> <xsl:apply-templates select="/html/head/*" /> </head> <body> <table width="100%" border="1" bgcolor="LemonChiffon"> <tr> <td colspan="2" width="100%"> <h1>Header</h1> (common to every page of the site)</td> </tr> <tr height="300" valign="top"> <td width="75%" bgcolor="Aqua"> <!-- Get the body here --> <xsl:apply-templates select="/html/body/*"/> </td> <td width="25%"> <h1>Headlines</h1> (common to every page of the site)</td> </tr> </table> </body> </html> </xsl:template>
Download the full style sheet: style.xslt
For readability, I've not included the whole style sheet above, but you get the idea--the presentation of the generic part of the page is embedded directly within a template, while the source document contains the data and often some presentation to be used for the page-specific elements.
We are in a situation that can be represented as (download the source here):
source.xml + style.xslt -> page.html
XML file (50% data + 50% presentation) + XSLT file (50% logic + 50% presentation) -> (X)HTML page.
First Step: Removing (Most of) the Presentation from the Stylesheet.
As a first step, let's focus on the XSLT sheet and see how we can separate most of the presentation from the logic.
An easy way to do this is to isolate the template above in an XHTML document. In this document we'll add some control elements (possibly using a separate namespace--not an absolute requirement, but rather a matter of style) which will trigger templates in the style sheet.
In our case, the "template" XHTML file, layout.xml, created largely by a designer, can be:
<html> <head> <insert-head/> </head> <body> <table width="100%" border="1" bgcolor="LemonChiffon"> <tr> <td colspan="2" width="100%"> <h1>Header</h1> (common to every page of the site) </td> </tr> <tr height="300" valign="top"> <td width="75%" bgcolor="Aqua"> <insert-body/> </td> <td width="25%"><h1>Headlines</h1> (common to every page of the site)</td> </tr> </table> </body> </html>
The style sheet for processing this layout file, logic.xslt, becomes something like:
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:variable name="page" select="/"/> <xsl:variable name="layout" select="document('layout.xml')"/> <xsl:template match="/"> <xsl:apply-templates select="$layout/html"/> </xsl:template> <xsl:template match="insert-head"> <!-- Get the head values here... --> <xsl:apply-templates select="$page/html/head/*"/> </xsl:template> <xsl:template match="insert-body"> <!-- Get the body here --> <xsl:apply-templates select="$page/html/body/*"/> </xsl:template> <!-- Identity transformation --> <xsl:template match="@*|*"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
Isn't it simple? Existing style sheets can easily be converted to this technique (by just moving around the full blocks of logic like we've done above with our comments) and it does make a big difference in the way web sites are maintained.
The real "trick" here is the usage of predefined elements to switch between the template document and the XSLT transformation. Since the context node will be the matching element, you can use these elements to pass variable information to the XSLT transformation.
We now have a document (layout.xml) that is based on what your designers produce, and includes the complete layout of the page.
Note also the first two lines of the style sheet, defining both the current XML document and the template file as variables to facilitate switching between the two trees. Storing the current document is necessary, since applying templates on the layout document means that the context will change, and "/" will designate the root of the layout tree in the corresponding templates--removing any way to get back to the current document if you hadn't stored it.
In real life, you will probably keep some presentation in the style sheet, as repeating the templating process for for every single XHTML element would be quite a challenge, but our (improved) situation is now something like:
source.xml + logic.xslt + layout.xml (read by logic.xslt) -> page.html
(Download source code here).
XML file (50% data + 50% presentation) + XML generic layout (100% presentation) + XSLT file (90% logic + 10% presentation) -> (X)HTML
Next Step: Taking Care of the Main Source Document
The problem with the above solution is that our XML source document contains a mix of presentation and data still. In almost every case we want to separate these two. How do we go about doing that?
We repeat the same technique with the XML source file (source.xml) as we did with the style sheet: we separate the content from the presentation in this file, and obtain an XML description of the layout for this particular page, separate from the description of the content. This mirrors our separation of the general layout of the site from the logic.
This kind of multi-level layout is very convenient when designing portal sites, where there is an aggregation of data from different sources in order to create the "body" part of the page. Using the main document source file as a layout allows us to keep most of the (normally dynamic) data in separate files or data sources where they belong.
The same "switching" technique can then be used with high flexibility. To include headlines you might, for instance, introduce the following element into one of your layout files:
<document-rss src="decid/channel.xml" max-size="140" max-items="10" class="highlight"/>
This imaginary tag says that you want to include an RSS document located at decid/channel.xml, cutting the headlines after their 140th character, keeping only the ten most recent, and using the CSS class "highlight." Tags like this can be dealt with relatively easily by non-programmers.
In our example, taking this approach of creating elements to implement certain component functionality, this would mean a main source file, spec_layout.xml, that looked like this:
<?xml version="1.0" encoding="utf-8"?> <!-- This file is describing the layout of the specific part of the page.--> <html> <head> <title>My page</title> <meta name="title" content="My page" /> <meta name="description" content="Welcome on my new page" /> </head> <body> <insert-body-header href="content.xml"/> <table width="100%" border="1"> <tr valign="top"> <td height="250"> <insert-first-part href="content.xml"/> </td> <td > <insert-second-part href="content.xml"/> </td> </tr> </table> </body> </html>
In this example, the content for the "first part" and so on is fetched from another file content.xml--it may be under the control of an editorial system, for instance--and included where the designer has used the instruction "insert-first-part." Our XSLT sheet logic2.xslt, which contains the logic, has additional code added to it to provide these page-specific functions.
The style sheet templates to perform these actions are straightforward to write and, most of the styling being defined in the layout.xml file, these templates will be almost "style free."
Here's a summary of our final process (download source code here):
XML specific layout (10% data + 90% presentation) + XML content files (10% presentation + 90% data) + XML generic layout (100% presentation) + XSLT file (90% logic + 10% presentation) -> (X)HTML.
We have separated our material into
- presentation, which is stored into generic and specific layout files usable by web designers' (X)HTML tools;
- logic, stored in XSLT style sheets, and
- content, stored in XML data sources.
What we have lost in doing this is the "inheritance" feature of XSLT (where a specific style sheet can import and override templates from more generic style sheets), which can be very powerful when using layouts "derived" from other layouts.
We can work around this limitation though, using XML include or linking techniques, or at an XSLT level.
Our approach is XSLT-centric (the XSLT transformation is responsible for including the right information at the right place), and a more pipe-oriented approach could be considered with the same kind of layout files.
At XML Europe 2000, John McKeown from the Trinity College Dublin presented, in his session "SVG: putting XML in the picture", an implementation where SAX filters performed a similar merging of template and content before feeding the document directly into the SAX input flow to be processed by the XSLT processor. Although requiring more Java programming, this second approach is probably more scalable.
The separation between logic, presentation, and data is also a key driver for XML-based frameworks such as Cocoon.