XML.com 
 Published on XML.com http://www.xml.com/pub/a/2002/11/27/qa.html
See this if you're having trouble printing code examples

 

Hacking XUL and WXS-based Transformations
By John E. Simpson
November 27, 2002

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:

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:

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

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:

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.

XML.com Copyright © 1998-2006 O'Reilly Media, Inc.