Hacking XUL and WXS-based Transformations
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:
- 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, themenulist
element and thebox
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 XHTMLimg
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 |
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 thename
attribute for the "$i
th"xs:element
which is a great-grandchild of the Schema's rootxs:element
element. - 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 thexsl:for-each
loop.
![]() |
|
Also in XML Q&A |
|
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.