Menu

Hacking XUL and WXS-based Transformations

November 27, 2002

John E. Simpson

Q: How do I build a new skin for the Mozilla browser?

I am designing a new Mozilla browser for a university project, with the intention of making it more accessible for older people to browse the web. Could you give me a hand in the design of Mozilla skins?

A: On the face of it, this seems a bit far afield for a column dedicated to answering questions about XML. Not really, though.

The reason is that the user interface of Mozilla (and Mozilla-based browsers -- such as recent versions of Netscape) is controlled by what you might think of as XML-based "driver files". Specifically, the vocabulary used is the XML-based User interface Language or XUL (pronounced "zool"). The XUL code is contained in files with a .xul extension and is referenced in URIs by way of the Mozilla-specific chrome:// scheme. (Why "chrome"? Think of the shiny-metal trim on a 1950s-era automobile. It's the glittery facade behind which the actual work takes place. All the chrome in a given Mozilla instance makes up the "skin".) The specific form of a XUL URI is

chrome://component/content/filename

where component is one of several standard chunks of a browser's functionality and filename, obviously, is the name of the .xul file. For instance,

chrome://communicator/content/newbrowser.xul

This says that the chrome for the browser window itself (that's the communicator piece) resides in a file named newbrowser.xul.

A typical XUL document consists of the standard XML declaration, a link to a CSS stylesheet, and a root window element. Within the latter might be a mixture of other XUL elements representing various user-interface widgets (pushbuttons, progress meters, textboxes, and so on) and plain old XHTML elements.

As an example, consider this very simple XUL document:

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window
id="xul-demo"
title="An Inert XUL Demonstration"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
<vbox id="mainbox">
<menulist id="bookinfo-menu">
<menupopup>
<menuitem label="Title"/>
<menuitem label="Author"/>
<menuitem label="ISBN"/>
</menupopup>
</menulist>
<box>
<html:img src="xpathpointer.s.gif" />
</box>
</vbox>
</window>

A few notes on the above:

  • The style in which text and other widgets will be displayed is determined by the CSS stylesheet named in the xml-stylesheet PI. Since no specific stylesheet is named here, the styles will come from a default stylesheet supplied with Mozilla itself.
  • The root window element contains two namespace declarations:
    • The default namespace (for elements with no namespace prefix at all) is associated with XUL itself.
    • The html: namespace prefix will be used for elements in the XHTML vocabulary.
  • The content is arranged in a vertical box (the vbox element) -- that is, the menulist element and the box element (both of which, per the default namespace specification, are in the XUL namespace) are placed one above the other.
  • The menulist element and its descendants define a drop-down select list of three choices: the strings "Title," "Author," and "ISBN."
  • The box element contains a single descendant, an XHTML img element which, of course, displays the specified image.

When this file is opened in Mozilla using the standard file:// scheme, you can get a sense of how the XUL document affects what the user sees (and can interact with):

Mozilla's-eye view of an XUL document
Mozilla's-eye view of an XUL document

This brief overview barely hints at the range of things you can accomplish with XUL, all the way up to completely remaking the browser window itself. The best on-line reference for learning XUL is XULPlanet's XUL Tutorial. It's comprehensive and clearly written, so much so that it's hard to imagine someone's getting through the entire tutorial and remaining confused. If you prefer to take your lessons in hardcopy form, check out O'Reilly's recently published Creating Applications with Mozilla, by David Boswell, Brian King, Ian Oeschger, Pete Collins, and Eric Murphy. XUL is the subject of just one of 12 chapters, so you'll come away with a lot more than just the skill to design your own chrome.

Q: Can I transform an arbitrary document into a specific vocabulary defined by W3C XML Schema (WXS)?

Assume the following problem:

  • There is an XML file.
  • There is an WXS schema.
  • I don't want to validate the XML data, but I would like to transform it to a form established by the schema.

For example, the WXS to drive the transformation might include the following:

<xs:element name="note">
<xs:complexType>
<xs:sequence>
<xs:element name="a" type="xs:string"/>
<xs:element name="b" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>

That is, the note element has two children, named a and b, in that order.

An XML file input to the transformation might look like this:

<?xml version="1.0"?>
<apple>
<something>Hello</something>
<from>Joe</from>
</apple>

I would like some kind of automatic transformation which

  • checks if the schema and the data may fit (with element names replaced) at all, and
  • makes the transformation.

The result would look like this:

<?xml version="1.0"?>
<note>
<a>Hello</a>
<b>Joe</b>
</note>

Can you help me out?

A: Your specification of the automatic transformation's first step is deceptively simple: may the schema and the source tree fit, at all? What exactly does "may" mean in this context or "fit," for that matter? It's almost impossible to code a generic transformation of any given source tree to a particular result tree, unless you can somehow codify exactly what the conditions are which determine what a "fit" is.

That said, let me take a crack at your problem; maybe it will help get you moving. This assumes that the schema above resides in a file named note.xsd, and that the source tree will "fit" if its root element has exactly the same number of children as does the root element defined by the WXS; we don't care what these child elements' names are (in either the source tree or the document whose structure is defined by the schema).

We'll start out by building the basic framework of a stylesheet, storing the contents of the schema's root element in a global variable, and overriding the built-in template rule for text nodes:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="schema" select="document('note.xsd')/." />
<xsl:template match="text()" />

<!-- ... -->

</xsl:stylesheet>

(Why override the built-in rule text-node template rule? Because otherwise text nodes will get copied straight to the result tree, which is not what we want at all.)

Now let's set up a couple of other global variables. One will hold the name of the result tree's root element, as defined by the schema, and one will hold the number of that root element's children. (You don't have to use variables; I like to, when possible, because (a) a variable's name is often easier to understand than the XPath expression which provides its value, and (b) repeated references to a given complex expression are easier to code.) These variable declarations might look something like this:

<xsl:variable name="xsd_root" select="$schema/xs:element/@name" />
<xsl:variable name="xsd_root_children"
select="count($schema/xs:element/xs:complexType/xs:sequence/xs:element)" />

Notice that the $xsd_root variable gets its value from the name attribute of the schema's uppermost xs:element element. (The schema you provided, as you said, is incomplete; it requires at least a higher-level xs:schema element to comply with the XML Schema 1.0 Recommendation. Any adjustments to the structure of your schema would have to be matched by adjustments to this stylesheet.)

So far, so good. But then $xsd_root_children gets its value by counting (hold your breath) the "great-grandchildren" of this xs:element element. Therefore, this will work only if (say) the schema is structured exactly like the sample one you've provided: a root xs:element with a single xs:complexType child with a single xs:sequence child with any number of xs:element children. (There are two such "great-grandchildren" in your sample schema.)

Now we can proceed to process the source tree. We need only a single template rule, matching the source tree's root node:

<xsl:template match="/">
<xsl:choose>
<xsl:when test="count(*/*) != $xsd_root_children"/>
<xsl:otherwise>
<xsl:element name="{$xsd_root}">
<xsl:for-each select="*/*">
<xsl:variable name="i" select="position()"/>
<xsl:variable
name="xsd_curr_elem"
select="$schema/xs:element/xs:complexType/xs:sequence/xs:element[$i]/@name"/>
<xsl:element name="{$xsd_curr_elem}"><xsl:value-of select="."/></xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

(As with the previously discussed variable declarations, this template rule comes with built-in assumptions. The assumption is that the source tree consists of a single root element, with some number of children. Again, if your source tree is structured in some other way, you need to make corresponding changes in this template rule.)

The template rule first tests to ensure that the number of children of the source tree's root element matches the value of the global $xsd_root_children variable. If not -- that's the empty xsl:when variable -- the stylesheet does nothing. (Depending on your XSLT processor, you might consider wrapping an xsl:message element in this xsl:when for notifying the user when there's a mismatch in the two structures.) In terms of your question, the empty xsl:when handles the "What if the structure of the source tree doesn't match the structure of a document defined by the schema" issue.

Otherwise, the template rule instantiates in the result tree a root element whose name matches the value of the previously declared $xsd_root variable. Then, for each child of the source tree's root element:

  • Declare a variable, $i, which simply saves the position of the current child within its node-set.
  • Declare another variable, $xsd_curr_elem. The value of this variable is a string -- the value of the name attribute for the "$ith" xs:element which is a great-grandchild of the Schema's root xs:element element.
  • Also in XML Q&A

    From English to Dutch?

    Trickledown Namespaces?

    From XML to SMIL

    From One String to Many

    Getting in Touch with XML Contacts

  • Instantiate in the result tree an element whose name is the value of the $xsd_curr_elem variable. Within this element, include a text node whose value is that of the node currently being processed by the xsl:for-each loop.

I should emphasize that this is a fragile sort of processing. Although it's "generic" in the sense that it doesn't require any knowledge of the names of the elements in the two documents (source tree and WXS-defined), it's entirely dependent on specific, corresponding structures of the two documents. Still, maybe the above will give you some ideas for solving your problem in a more general way.