Client-side development environment

Scenario: XSLT deployment in a browser/server scenario with XSLT transformations performed on the browser (JavaScript or with <?xsl-stylesheet ?>)

Potential tools:

  • Saxon is great for off-line testing (primarily while developing small bits of code or trying new programming techniques). It cannot fetch the information from the server, so you have to provide test data in local XML files
  • Firefox is the best tool if you want to test client-server integration and use <?xsl-stylesheet ?> approach as it can recover from XSLT errors (which IE can't). However, it doesn't detect all errors, so the stylesheets working with Firefox might break in IE (but not due to an IE bug)
  • IE is a nightmare when using <?xsl-stylesheet ?> directive. Whenever something breaks (error in stylesheet or even server-side error resulting in HTML response instead of XML response), its cache gets messed up and you have to restart the browser to continue the tests. I therefore use IE only in the final tests to verify that my XSLT has no weird errors not detected by Firefox.
  • The best testing mechanism is probable the manual (JavaScript) client-side transformation. You can do it in IE or Firefox; in this case I would prefer IE, as it has better XSLT error reporting (MSXML is not that bad :).

Example: Use XSLT transformation in browser

Several readers were asking for an code sample that performs XSLT transformation in the browser, so here it is :) To start with, you should use a wrapper library to isolate yourself from browser version/platform inconsistencies. I'm using Sarissa to deal with XML, XSLT and HttpRequest and X library to deal with DHTML-related functions.

The most common XSLT-related operation that you do in your browser is this:

  • Download XML document from the web server using XMLHttpRequest;
  • Transform the XML into application-specific HTML markup with an XSLT transformation;
  • Insert the resulting HTML markup into the web page.

Sarissa library has a function (Sarissa.updateContentFromURI) that does almost exactly what you need to get there, so you only need to perform the following steps:

  • Load the XSLT stylesheet as XML document (sync load is easier to do).
  • Import the loaded XML document into an XSL processor (special object that can perform the XSLT transformations).
  • Call the updateContentFromURI function.

The resulting code is very simple:

// load XSLT stylesheet, done only once.
var xsl = loadURI("moveRoutes_lists.xsl");
if (!xsl) throw("Cannot load XSL stylesheet");

var xform = new XSLTProcessor();
xform.importStylesheet(xsl);

// the following call transforms a remote XML document
// into HTML fragment and inserts it into the target
// element
Sarissa.updateContentFromURI(remoteURI, xGetElementById(targetElement),xform);

Notes:

  • sarissa.js as well as sarissa_dhtml.js libraries have to be included in the source document.
  • xGetElementById function is part of the X library.
  • loadURI function is documented here.

JavaScript progressive enhancement in practice

In the JavaScript Progressive Enhancement in Practice article I'm describing the progressive enhancement philosophy and techniques through a simple example: frequently asked questions page.

Server-side XSLT transformations

If you need to transform XML with XSLT on the server, you could use MSXML on Microsoft platforms (ASP) or built-in XSL functions in PHP 5. The beauty of the PHP functions is that they provide the same API as the Firefox browser and the Sarissa library, so you don't have to reinvent the wheel.

I would often use XSLT on the server to separate data (XML) from its presentation. Its usage also results in cleaner server-side code, as the markup generation is separated from the programming logic. It's also extremely useful if you want to deploy XML/XSLT-based solution that has to support low-end clients (for example, search engine spiders).

Error handling Sarissa wrapper

In a previous post I've included a simple Sarissa wrapper that could be used to load an XML document (for example, XSLT stylesheet). If you want to perform more error checking and generate sensible errors, you could use these two functions:

  • loadFromServer function requests object load. If you provide a callback parameter, the load is asynchronous, otherwise it waits for the load to complete and return the wrapper object.
  • checkLoadStatus checks the various error conditions and throws appropriate errors.
var AWS = new Object();

AWS.loadFromServer = function (url,cb) {
var lso = new Object() ;

function loadHandler() {
if (lso.request.readyState == 4) cb(lso); }

lso.savedURL = url ;
lso.request = new XMLHttpRequest() ;
lso.request.open("GET", lso.savedURL, cb ? true,false);
if (cb) lso.request.onreadystatechange = loadHandler;
lso.request.send(null);

return lso;
}
AWS.checkLoadStatus = function (lso) {
var url = lso.savedURL ;
var xr = lso.request ;
var msg;

if (!url) throw("AWS.checkLoadStatus expects loadFromServer object") ;
if (xr.status != 200) throw (url + " failed: "+xr.statusText);
try {
var de = xr.responseXML.documentElement;
if (!de) msg = url + ": response is not a valid XML document";
} catch(err) {
msg = url + ": response is not a valid XML document (" + err + ")";
}
if (msg) throw(msg);
return xr.responseXML;
}

Offline XSLT processor

If you need an off-line XSLT processor, nothing beats SAXON. It supports XSLT 1.0 and XSLT 2.0 and the lower-end version is free. I am strictly using the 1.0-only version to ensure my code is compliant with major browsers, but you might want to get the 2.0-version for special projects.

I'm primarily using SAXON to test my XSLT code (or develop new programming techniques) before deploying it in a distributed environment. I was also using it to develop the WordML transformations that are now executed within the context of Save As ... function in Word.

Simple high-level wrapper for Sarissa

In some cases, Sarissa calls are too low-level for my needs. For example, sometimes I need a function that would load an XML document synchronously and just throw an error if something fails. Here it is:
function loadURI(uri,async) {
var d = Sarissa.getDomDocument();
d.async = false;
d.load(uri);
if (d.parseError) throw("Error loading "+uri+": "+Sarissa.getParseErrorText(d));
return d;
}

Make pop-up windows visible to search engines

The article Make Pop-Up Windows Visible to Search Engines shows you how to open the pop-up windows from your web pages (although that's more and more often considered a bad practice that should be limited to special cases) while still retaining links to the pop-up content that can then be discovered by search engines.

Recommended wrapper libraries

In the last year, I've settled on two JavaScript browser wrapper libraries that satisfy almost all my needs to achieve browser version/platform independency: Sarissa handles XML, XSLT and XMLHTTPRequest and the X library handles a large number of DHTML- and DOM-related functions.

Note: I decided not to use a framework library. If you want to go down that route, these two would be way too low-level.

Automate the pagination of web pages

A lot of articles of the web (for example, this one) document how you can present a web page as a series of screen-sized pages. In most cases, the page author has to provide the pagination (in form of DIV elements, for example). In the InformIT article Automate the Pagination of Your Web Pages, I'm describing a method of generating the pages from linear text automatically in the browser, based on (for example) heading elements.

Using WordProcessingML to Generate Clean HTML from Word

Generating clean and simple HTML output from Microsoft Word is very difficult if you rely on its "Save As Web Page" feature. In my InformIT article, Using WordProcessingML to Generate Clean HTML from Word, I'm describing how you can use WordProcessingML together with XSL transformations to generate clean, strict (X)HTML-compliant documents from Word sources.

MSXML is still only DOM-1 compliant

If you use DOM calls to manipulate XML documents returned with XMLHttpRequest object (and who doesn't), you'll be sooner or later highly upset when the code you've tested on Firefox or Opera fails in IE (including IE7). The reason - Microsoft still did not implement Node.getElementById function. So here's the "replacement":
function getDomById(e,id) {
if (e.getElementById) { return e.getElementById(id); }
if (e.documentElement) e = e.documentElement;

var cn = e.childNodes;
for (var i = 0 ; i != cn.length; i++) {
var n = cn[i];
if (n.nodeType == 1) {
if (n.getAttribute("id") == id) return n ;
}
}
for (var i = 0 ; i != cn.length; i++) {
var n = cn[i];
if (n.nodeType == 1) {
var result = getDomById(n,id); if (result) return result;
}
}
return false;
}
Note: The procedure is doing breadth-first search as I was looking for IDs that were pretty high in the hierarchy of trees with many branches.

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.