Mozilla DevCenter    
 Published on Mozilla DevCenter (http://www.oreillynet.com/mozilla/)
 See this if you're having trouble printing code examples


XML Transformations with CSS and DOM

by Scott Andrew LePera and Apple Developer Connection
10/15/2002

As the adoption of XHTML continues to expand in Web development circles, the inevitable question arises: Can't we just skip over the browser-specific cruft of HTML and create our documents in pure XML?

XML in the browser has been the subject of many spirited discussions about bleeding-edge Web development. Some feel that XML in place of HTML isn't ready for prime time due to the lack of user agents that can properly parse and render it. Others feel that XML really belongs on the server or used solely as a descriptive framework for data and has no place in the visual world of the Web, which is already adequately served by HTML.

Despite this, the newest generation of browsers possess powerful XML capabilities. The recent releases of Mozilla offer a parser and a rendering engine that support XML technologies such as XML stylesheets, XML namespaces, XLink, SVG, and MathML. Along with its native support for SOAP and forthcoming implementations of WSDL and XSLT, Mozilla is poised to become a power player among XML client software.

Mozilla also permits XML to be rendered in the browser with CSS, and manipulated with the DOM. This is a real boon to those of us eager to experiment with XML transformations (both visual and structural) without having to delve into unfamiliar technologies such as XPath, the verbose traversal language of XSLT. If you're already familiar with CSS and DOM, you're more than halfway to achieving XML transformations in Mozilla.

Related Reading

Dynamic HTML: The Definitive Reference
A Comprehensive Resource for HTML, CSS, DOM & JavaScript
By Danny Goodman

This article demonstrates how to render XML in the browser with a minimum of CSS and JavaScript. The examples that follow were written for and tested with Mozilla 1.0 for Mac OS X and Windows 2000. This article is not a comprehensive tutorial on DOM, CSS, or XML, and a basic understanding of each of these technologies is required.

The Sample XML File

I chose a pared-down RSS file to provide the sample XML for this article. I could have chosen any arbitrary XML document, but RSS serves a particular purpose of syndicating content, often news and Weblog items, and is ideal for demonstrating basic transformations with CSS and DOM. Throughout this article, I'll show how to use CSS to apply formatting style to the RSS elements, and how to use the DOM Level 2 interface to traverse and transform the output. By the end of this article, I'll have created a (admittedly crude) browser-based RSS reader application using the RSS feed itself.

The first order of business is to format the existing XML elements with CSS. As with HTML, this is accomplished with a linked stylesheet.

Applying Style

An XML stylesheet is typically imported via an xml-stylesheet processing instruction in the prologue of the XML document. This is analogous to using the link tag in HTML to import CSS, and the syntax is similar:

<?xml version="1.0"?>
<?xml-stylesheet href="rss.css" type="text/css"?>

Unlike HTML, no assumptions are made about the formatting of XML elements on the part of the processor. A browser understands (either implicitly or otherwise) that an HTML <p> element is a block element, while an <em> is an inline container. Likewise, a browser can safely assume that boldface is the default formatting of text inside an HTML <strong> element. Not so with raw XML—by linking in a CSS document, the basic rules for visually formatting each element and its contents can be supplied to the processor.

I first have to decide which elements to format as block elements. Since all elements are displayed as inline by default, I only need specify the block elements in the stylesheet. I'll start out by defining the display, font, and box properties of the rss, channel, and item elements:

rss
{
    display:block;
    margin:10px;
}

channel
{
    display:block;
    height:300px;
    width:280px;
    border:1px solid #000;
    overflow:auto;
    background-color:#eee;
    font: 12px verdana;
}

item
{
    display: block;
    padding:10px;
    margin-bottom:10px;
    border-top:1px solid #ccc;
    border-bottom:1px solid #ccc;
    background-color:#fff;
}

At this point, I'm not really interested in some of the channel metadata elements, so I'll use display:none to remove them from the visual output. Note that this doesn't remove the elements from the document; if I wanted to do that, I'd use the appropriate DOM method and remove them programmatically. I'll get to that a bit later on. I want to keep the channel's title and description elements, though, so I'll toss them some CSS as well:

channel>title, channel>description
{
    display: block;
    margin-left:10px;
    margin-top:10px;
    background-color:#eee;
    font-weight:bold;
}

channel>title
{
    font-size:16px;
}

channel>description
{
    font-size:10px;
    margin-bottom:10px;
}

item>title
{
    font-weight:bold;
}

item>link, channel>link, channel>language
{
    display: none;
}

To check my formatting style, I fire up Mozilla 1.0 and point it at my RSS document. Here are the results so far:

XML with CSS Applied

As you can see, formatting XML with CSS is as simple as doing the same with HTML. But I have a problem: None of my links are actual links. That is, they don't behave as HTML links when you click on them. I need to find a way to transform the RSS link elements into working links. CSS is ill-suited for providing this kind of functionality, but I do have another tool at my disposal: DOM.

Transformations with DOM

If you're already familiar with using the DOM Level 2 interface to manipulate HTML, this next part should be familiar. Here's the basic approach:

  1. Identify and obtain references to all title and link element pairs in the RSS document.
  2. Pull the text out of each pair.
  3. Create an XHTML link (<a>) with the specified title and URL text.
  4. Replace the title element with the HTML link in the document.

The following code uses the DOM method getElementsByTagName to retrieve a collection of item element references in the XML document. The for loop that follows iterates over the collection and obtains references to the title and link element in each. Finally, the text of the link and title are extracted by first referencing the text node in each, then the nodeValue property of the node:

var allItems = document.getElementsByTagName("item");
for (var i=0;i<allItems.length;i++)
{
    var itemElm = allItems[i];
    var titleElm = itemElm.getElementsByTagName("title").item(0);
    var titleText = titleElm.firstChild.nodeValue;
    var linkElm = itemElm.getElementsByTagName("link").item(0);
    var linkURL = linkElm.firstChild.nodeValue;
}

This provides the needed pieces to build an appropriate HTML link for each RSS item. A bit more code is needed to get everything working, however.

Dealing with Namespaces

The concept of XML namespaces became a W3C recommendation in 1999. They exist to allow XML elements from different schemas to be mixed without colliding. For example, XHTML has a title element, as does RSS. To keep an XML parser from confusing the two, a namespace is declared in the XML document's root element. The namespace consists of a prefix associated with a URI, and is used to distinguish elements and attributes imported from another XML specification.

The following markup fragment demonstrates how to import an XHTML img element into an RSS document:

<?xml version="1.0"?>
<rss version="0.91" xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <channel>
    <title>scottandrew.com</title>
    <link>http://www.scottandrew.com</link>
    <xhtml:img src="/img/photo.jpg" alt="Handsome pic of Scott"/>
  ...
</rss>

The xmlns attribute associates the namespace prefix "xhtml" with the URI. In this way, an XML parser is made aware that the img element exists in a namespace outside the default one. (This is, of course, no guarantee that the parser will know what to do with the img, as not all XML parsers are necessarily browsers.)

When using the DOM interface with an XHTML document, it's assumed that all new elements created programmatically with createElement are in fact XHTML elements. The DOM Level 2 spec provides a different method for creating elements from different namespaces. The createElementNS method accepts two arguments: a string referencing the URI of the namespace and a string representing the element to be created:

var xhtml = "http://www.w3.org/1999/xhtml";
var newLinkElm = document.createElementNS(xhtml,"a");

Since my application is intended to be read by a browser, I can use createElementNS to import an XHTML <a> element into my RSS document. With the information gathered from the earlier DOM operation, I can assign an href attribute to the new <a> element, then create and insert a new text node. Since the href attribute, like the <a> element itself, is imported from XHTML, I need to use the setAttributeNS method. This method operates exactly as the DOM 2 setAttribute method does, and utilizes an additional argument declaring the namespace of the attribute.

Finally, I replace the RSS title element in this item with the XHTML <a> element, using the DOM 2 replaceChild method to do so. Mozilla should now allow the title of the item to behave as an XHTML link. Here's the full code of my node tranversal script:

var xhtml = "http://www.w3.org/1999/xhtml";
var allItems = document.getElementsByTagName("item");
for (var i=0;i<allItems.length;i++)
{
    var itemElm = allItems[i];
    var titleElm = itemElm.getElementsByTagName("title").item(0);
    var titleText = titleElm.firstChild.nodeValue;
    var linkElm = itemElm.getElementsByTagName("link").item(0);
    var linkURL = linkElm.firstChild.nodeValue;

    var newLinkElm = document.createElementNS(xhtml,"a");
    var txtNode = document.createTextNode(titleText);
    newLinkElm.setAttributeNS(xhtml,"href",linkURL);
    newLinkElm.style.display = "block";
    newLinkElm.appendChild(txtNode);
    itemElm.replaceChild(newLinkElm,titleElm);
}

Attaching the Script

To implement this, I need to associate the script with the RSS document so these DOM transformations can take place at runtime. At the time of this writing, I could not find an agreed-upon way to associate an external script file with an XML document (unlike xml-stylesheet, there seems to be no equivalent "xml-script" processing instruction, and it seems doubtful that the W3C will move forward with this approach). There is no "onload" method of an RSS document, so I'll need a different plan of attack.

Using an XML namespace declaration I can import an XHTML script element and insert it as the last element in the RSS document root. Placing the script call at the bottom ensures that the document is loaded into memory before the JavaScript is executed:

<?xml version="1.0"?>
<?xml-stylesheet href="rss.css" type="text/css"?>
<rss version="0.91" xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <channel>
    <title>scottandrew.com</title>
    <link>http://www.scottandrew.com</link>
    <description>DHTML, DOM and JavaScript News</description>
    <language>en-us</language>
    <!-- ITEM elements go here -->
  </channel>
  <xhtml:script type="text/javascript" src="rss.js" />
</rss>

Loading the RSS file in Mozilla now produces the desired result: clickable title links. You can see a working example here, although you'll need Mozilla 1.0 or better to experience it.

An Alternate Approach: XLink

Mozilla supports basic linking via XLink as an alternative to importing XHTML. With XLink, there's no need to create a new element. Instead, the linking functionality comes from attaching XLink attributes to the element in question, thereby making any element a potential link.

To accomplish this, affix the proper XLink attributes to the item's title element with the setAttributeNS method. Here's a modified version of the JavaScript that incorporates XLink in place of XHTML:

var xlink = "http://www.w3.org/1999/xlink";
var allItems = document.getElementsByTagName("item");
for (var i=0;i<allItems.length;i++)
{
    var itemElm = allItems[i];

    var titleElm = itemElm.getElementsByTagName("title").item(0);
    var linkElm = itemElm.getElementsByTagName("link").item(0);
    var linkURL = linkElm.firstChild.nodeValue;

    titleElm.setAttributeNS(xlink,"type","simple");
    titleElm.setAttributeNS(xlink,"show","replace");
    titleElm.setAttributeNS(xlink,"href", linkURL);
}
Learning XML

Related Reading

Learning XML
Guide to Creating Self-Describing Data
By Erik T. Ray

Future Directions

It's been argued that Web developers might be better off sticking to valid HTML 4.01 until user agents are able to parse and render XML properly. Perhaps, though as I've demonstrated, some user agents are already well prepared to deal with XML. The power of CSS and DOM in transforming XML has led many to question why technologies like XSLT are even necessary. I don't make any such claims, but if the capabilities of Mozilla 1.0 are any indication, the future of XML on the Web is within reach, and looks bright indeed.

Further Reading and Resources:

XML in Mozilla: links to resources on the XML capabilities of Mozilla.

XML Namespaces: the W3C recommendation.

XML Namespaces By Example: Tim Bray's gentle introduction to the concept of XML namespaces.

XSL Considered Harmful: read past Michael Leventhal's brusque denunciations of XSL and find great examples of CSS and DOM for XML transformations.

XML Linking Language (XLink) 1.0: the W3C recommendation for XML linking.

Associating Style Sheets with XML documents: the W3C recommendation.

Scott Andrew LePera lives in San Francisco, where he ekes out a schizoid existence as both a web applications developer for KnowNow and a frustrated urban folk singer.


Return to the Mozilla DevCenter.

Copyright © 2009 O'Reilly Media, Inc.