]> Beyond CC's, Selecting HTML Client-side

Documenta Rudolphina

Selecting browser specific elements with XSLT

Manfred Staudinger, Vienna (April 2009)

By adding an XSL stylesheet to your XHTML you may choose a token to select a node or a group of nodes to be included or excluded for some browsers. The selection process runs client side but is not dependent on JavaScript, as it takes place before JavaScript even starts.

This is a mayor rewrite from the previous version (which is still accessible here). In addition to the xsl:system-property function and certain XSLT implementation differences, the User-Agent string is now used. This enables us to treat all browsers equally with positive or negative selection possible (no usage of CC's anymore) and to get a more detailed breakdown for the browsers.

Purpose

The aim is to replace Microsofts proprietary Conditional Comments by a new concept based on XSLT, which is adaptable and applies to all browsers evenly. The current webpage is an implementation of that concept to show its flexibility and robustness in a demo.

The key to this concept is the definition of a token. For example assume you are interested in a certain feature implemented in Gecko (the rendering engine for Firefox and Co.). Then you can identify a Gecko browser having that feature by its release date or its version number and add your token my-feature to the built-in list. If this very same feature is implemented in Webkit and in Opera too, simply add the same token at the appropriate level. With this you have build two groups of browsers for which you may select HTML elements by specifying my-feature or not my-feature.

Tokens could be chosen to represent:

It is no problem to have many tokens available, but it might be a problem to use 10 and more alternatives for one HTML element (no problem for a demo, I suppose).

Basic Document

The server should send the document with a Content-Type: application/xml header. The encoding may be specifiied on that header too, or at the document level with an XML declaration. In case of IE 6 this would have caused the browser to switch into quircks mode, but because we are feeding this tag soup browser with XHTML, its is in quircks mode anyway (same for IE 7) and the XML declaration does no harm.

To start the transformation, we use a processing instruction. This way the XSLT engiine will act as a "preprocessor" as the transformation will take place before the rendering (layout) engine runs, which would obviously not be the case if we would start it by JavaScript.

For the selection process we use two custom attributes dr:select and dr:select-one. They are in a foreign namespace and require a namspace declaration on the <html> element. By putting it all together, we have

<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet href="cond-elements.xsl" type="text/xsl"?>
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:dr="http://documenta.rudolphina.org/">

Selection Process

When the browser calls such a web-page, the XSL transformation generates a list of one or more tokens. The selection process is triggered by the dr:select attribute which contains another list of tokens. When the XSLT processor encounters an element with such an attribute, the list of generated tokens is matched against the list of tokens found in the attribute. If a match is found, the elemet and its content are included, otherwise ignored.

The attribute causes a positive selection and we may call the list of tokens a positive selection list. If we prefix the list with not it will become a negative selection list, with exactly the opposite effect: in case of a match, the element and its content will be ignored.

All of the above holds also for the dr:select-one attribute with one exeption: it effects only the element on which it is specified. Of course it may be combined with dr:select.

First Example

Assume we want to have one <a> element in two flavours, one for IE 6 and IE 7 and another one for all the others browsers. The list of tokens we have choosen to generate for IE 6 is simply "IE 6, IE" and for IE 7 accordingly "IE 7, IE". Now we can write:

<a dr:select="IE 6, IE 7" href="http://example.com" target="_blank">link text</a>
<a dr:select="not IE 6, IE 7" href="http://example.com">link text</a>

As it happens, IE 6 and IE 7 need a new window when linking to the W3C Validator, otherwise IE would not send a referer header. You can see this example realized at the next section.

But couldn't we have done this with "Conditional Comments"? Not exactly the same, but here is a good enough solution that will also pass the validator:

<!--[if lte IE 7]>
   <a href="http://example.com" target="_blank">link text</a>
<![endif]-->
<!--[if IE]><![if gt IE 7]><![endif]-->
   <a href="http://example.com">link text</a>
<!--[if IE]><![endif]><![endif]-->

But even it validates, it does not look very convincing and indeed has some limitations:

(If you would like to know how these CC's can be created with XSLT, have a look at my article MSIE Conditional Comments and XSLT).

Valid XHTML 1.1 Valid XHTML 1.1

Validation

Validation currently can only be done on what is used as the input to the XSLT engine instead of its output. For example, all occurrences of the dr:select attribute are deleted in the course of the transformation and the browsers rendering engine will find nothing left behind in the foreign namespace. But in this and similar cases, where most of the document is copied over by the identity transformation, validating the input is still valuable. With that in mind, there were two problems to solve:

Now put it together and this is the DOCTYPE in use here:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
	"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
	<!ATTLIST style dr:select CDATA #IMPLIED>
	<!ATTLIST a dr:select CDATA #IMPLIED>
	<!ENTITY % xhtml-prefw-redecl.mod "" >
	<!ENTITY % xhtml-postfw-redecl.mod "" >]>

If you load this page with MS Internet Explorer, you will notice an extended load time, because IE fetches the DTD from the internet. It does not make any sense other than delivering one bold message: MS hates both, validation and XHTML! Up to now I can see only two possibilities:

Note: I would expect readers of this page to rarly use IE anyway!

Second Example

Here the selection is done on the style elements for internal style sheets, which all define the color of the square but have different tokens in theire dr:select attributes. To the right you can see the User-Agent string and first attemps to extract the browser and the rendering engine correctly.

Below are the colors for the different browser categories and the tokens that would be generated for each (characters in parenthesis are not part of the tokens).

IE 6, IE
IE 7, IE
IE 8, IE
Netscape 7, Netscape
Netscape 8, Netscape
Netscape 9, Netscape, Navigator
Firefox 1 (1.0+), FF 1, Firefox, Gecko 1.7, Camino
Firefox 2 (2.0+), FF 2, Firefox 1.5 (1.5.0+), FF 1.5, Firefox, Gecko 1.8, Epiphany, Flock, Galeon, Iceape, Iceweasel, Kazehakase, K-Meleon, SeaMonkey
Firefox 3, FF 3, Firefox, Gecko 1.9, Epiphany, Flock, Galeon, Iceweasel, Kazehakase, Minefield, SeaMonkey, Shiretoko, Songbird
Safari 1 (1.3+), Safari
Safari 2 (2.1+), Safari
Safari 3 (3.1+), Safari
Safari 4, Safari
Chrome 1, Chrome
Chrome 2, Chrome
Opera 9.5, Opera
Opera 9.6, Opera
Opera 10, Opera
Unknown browser
Error: no XSL-transformation!

Programs used for this Demo

The stylesheet invoked via the PI, b-cond-elements.xsl is rather short, with demo specific code only, and imports the main stylesheet cond-elements.xsl which

To get the UA string, I use the document() function to call a small PHP program which sends back a XML document containing the UA string.

<?php
header('Content-Type: application/xml;charset=UTF-8');
echo '<doc>' . $_SERVER['HTTP_USER_AGENT'] . '</doc>';
return true;
?>

Note: Opera 9 has problems here: prior to 9.50 the document() function is not defined and transformation does not run. Opera 10 has solved this problem.