XSL: create optional class attributes in HTML output

When generating styled HTML output from XML, you might need to attach a special class to the first and/or the last element in a series (for example, first and last child). An only child would obviously have both classes attached to it (don't forget, class attribute can list multiple classes separated by a whitespace). The code to generate optimal list of HTML classes (or doing a generic join of list elements) is pretty simple:
<xsl:variable name="class">
<xsl:if test="position() = 1"> FirstInSeries</xsl:if>
<xsl:if test="position() = last()"> LastInSeries</xsl:if>
</xsl:variable>

<xsl:if test="$class != ''">
<xsl:attribute name="class">
<xsl:value-of select="substring-after($class,' ')" />
</xsl:attribute>
</xsl:if>
Please note the following:
  • Each value in the list has a leading whitespace. The list values are thus properly separated, but the result has an extra leading whitespace.
  • The last xsl:if instruction tests if the class attribute is needed (detailed description of this trick).
  • The substring-after function removes the leading whitespace, resulting in the desired value for the class attribute.

XSL: avoid empty attributes in output stream

Whenever you create an attribute with an xsl:attribute instruction containing conditional instructions (xsl:if or xsl:choose), the result might be empty, resulting in an empty attribute in the output stream. For example, both my simplistic WordProcessingML to HTML stylesheet or similar code published on OpenXML Developer could create <span style=""> elements.

To avoid this problem, capture the conditional results into a variable and create the attribute only if the variable is non-empty, for example:
<xsl:variable name="style">
<xsl:if test="w:rPr/w:i">font-style: italic;;</xsl:if>
<xsl:if test="w:rPr/w:b">font-weight: bold;</xsl:if>
</xsl:variable>

<xsl:if test="$style != ''">
<xsl:attribute name="style"><xsl:value-of select="$style" /></xsl:attribute>
</xsl:if>

XSL: transform an element only if it's a descendant of another element

I recently had to work on WordProcessingML documents and wanted to transform only those w:p elements that were within the subtree of the w:body element. My initial solution was "a bit" complex: match all w:p elements that have a w:body ancestor.
<xsl:template match="w:p[ancestor::w:body]">
There is (as always) a much more elegant solution:
<xsl:template match="w:body//w:p">

XSL: Detect first-of-type element in a list

Sometimes you want your XSL transformation to process first element of a type in a child list in different manner. For example, using the following data ...
<?xml version="1.0" encoding="UTF-8" ?>
<list>
<author>John Brown</author>
<author>Jim Small</author>
<editor>Jane Doe</editor>
<editor>Grace Kelly</editor>
</list>
... you might want to process the first editor in a slightly different manner. There are two simple solutions:

(A) Write two transformation rules, one for the first element, one for the remaining ones:
<xsl:template match="editor[1]">
<!-- transform the first editor in list -->
</xsl:template>

<xsl:template match="editor">
<!-- transform the remaining editor elements -->
</xsl:template>
(B) Use preceding-sibling axis to check whether an element is the first of its type:
<xsl:template match="editor">
<xsl:if test="not(preceding-sibling::editor)">Editors:</xsl:if>
<xsl:value-of select="text()" /> (<xsl:value-of select="position()" />)
</xsl:template>
You should use the first method when the handling of the first element is radically different from the rest and the second one when you only need to set a few attributes or write a lead-in text.

IE7 breaks some AJAX libraries

Probably this is very old news (and Internet is full of related blog entries), but somehow I've managed to miss it ... If you use XMLHTTP Request object and initialize it similar to this:
if (IE) {
x = new ActiveXObject("Microsoft.XMLHTTP")
} else {
x = new XMLHttpRequest();
}
... Internet Explorer 7 will constantly complain about the page trying to use ActiveX controls (at least IE6 was silent unless you've disabled ActiveX in which case AJAX broke anyway). The proper way to deal with this particular quirk of IE is to test for window.XMLHttpRequest first (instead of relying on browser type) and use ActiveX only if needed.

Note: If you use Sarissa (which broke my AJAX application), download the latest version, it contains all relevant IE7 fixes.

XPath position() function returns unexpected results

If you process XML documents generated "manually" (including documents composed within a program without proper XML tools), you might be surprised by the results returned by with position() function. For example, the input XML document ...
<?xml version="1.0" encoding="UTF-8" ?>
<list>
<author>John Brown</author>
<editor>Jane Doe</editor>
<author>Jim Small</author>
<editor>Grace Kelly</editor>
</list>
... processed with a simple XML stylesheet ...
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:output method="text" />

<xsl:template match="list">
Results: <xsl:apply-templates />
</xsl:template>

<xsl:template match="author|editor">
<xsl:value-of select="text()" /> (<xsl:value-of select="position()" />)
</xsl:template>

</xsl:stylesheet>
... returns surprising results:
  Results:
John Brown (2)

Jane Doe (4)

Jim Small (6)

Grace Kelly (8)
The reason for this unexpected behavior are whitespace text nodes between the author and editor elements which are also counted by the position() function. To skip them, either create XML documents without extra whitespace or use more specific xsl:apply-template statements, for example:
<xsl:template match="list">
Results: <xsl:apply-templates select="*"/>
</xsl:template>
The select="*" option in the last example selects only child nodes of the current XML node and thus skips over text fragments (including whitespace nodes).

Web Caching, Part 2: Reduce the Download Time

In my second article in the Web caching series published by InformIT.com, I've focused on caching dynamically generated pages in the browser cache. The article explains the in-depth details of browser-side HTTP caching and the HTTP headers you have to process in server scripts to make your dynamic pages cacheable.

When properly implemented, this solution can drastically reduce the amount of information downloaded to visitor's browser, thus increasing the overall responsiveness of your web application.