Make Pop-Up Windows Visible to Search Engines

In the InformIT article Make Pop-Up Windows Visible to Search Engines I'm describing how you can make content in pop-up windows visible to search engines and accessible to visitors without JavaScript support.

Optimized presentation of XML content

In the Inform-IT article Optimized Presentation of XML Content I'm describing how you can serve XML content in its raw form to XSLT-compliant browsers (which do the transformation into HTML locally) while generating HTML pages from the same content for non-XSLT browsers (for example, early releases of Opera) and the search engines.

Firefox: HTML DOM fails if a local XSL transformation does not specify xsl:output

This nasty beast took me a while to figure out: if you do local XSL transforms in Firefox 1.5 and don't have the xsl:output directive in your main XSL stylesheet, HTML-specific DOM calls might not work.

The results are weird: for example, whatever is produced with the XSL transformation behaves like a HTML DOM object, but if you create a new element, it lacks HTML-specific DOM calls.

And the obvious solution: make sure you're always including ...
<xsl:output method="html" encoding="utf-8"
doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
... in your XSL stylesheet.

IE7 bug: addEventListener does not work with table row mouseover event

If you want to implement your JavaScript code using DOM-compliant events, you'd be unpleasantly surprised to learn that table rows in Internet Explorer do not respond to mouseover events attached to them with addEventListener. Firefox and Opera work as expected. The only way to achieve cross-browser compatibility is to use the old onmouseover properties: tablerow.onmouseover = myfunc; instead of tablerow.addEventListener("mouseover",myfunc).

Firefox 1.5 bug: table.rows property is empty when using XSL

If you generate a table in Firefox 1.5 with an XSL stylesheet within the browser from an XML document loaded from the server, the table's rows property will contain no rows. You can still get the rows using the childNodes property though.

To make matters worse, Firefox does not generate an implicit TBODY element in the DOM tree, but IE7 and Opera do. Makes for a nice nightmare if you aim at cross-browser support ... the workaround code is included below:
function recurseChildNodes(parent,tag,callback) {
if (! parent.nodeName) return;
if (parent.nodeName.toLowerCase() == tag) { callback(parent); return; }
if (! parent.childNodes) return;

for (var i = 0; i < parent.childNodes.length; i++) {
recurseChildNodes(parent.childNodes[i],tag,callback);
}
}


recurseChildNodes(table,"tr",callbackFunction);

Web caching, Part 3: Database Integration

The last article in my Web caching series published by InformIT.com details the database integration methods. As most SQL databases don't give you record modification timestamps, you have to implement them yourself. In this article, you'll see how you can use SQL triggers to implement table-level, record-level and join-level timestamps that can be used to set the last-modified date on an HTTP response.

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.

Web Caching, Part 1: Explicit Content Expiration

InformIT.com has published Reap the Benefits of Web Caching, Part 1: Explicit Content Expiration; the first article in my three-part series covering usage of web caching mechanisms in dynamic web pages.

Flash and Search Engines

This article gives you an excellent idea how to use Flash in your web sites while still having them completely visible to the search engines.