© Hewlett Packard 1998. All Rights Reserved.

Adding Style and Behaviour to XML Pages with a dash of Spice

Dave Raggett <dsr@w3.org>

Abstract

CSS is proving to be an easy to learn, effective approach for styling HTML pages. This paper shows how with a few simple extensions ECMAScript becomes a powerful and easy to learn way to style XML pages using extensible CSS rules together with scripted flow objects. The approach avoids any prior knowledge of the meaning of style properties, and there are no predefined flow-objects. Instead these are written in ECMAScript or imported as down-loadable Java classes and Active-X controls. This gives authors the freedom to create extensible Web applications without the need to wait for committees to agree on extensions to CSS or HTML. HP has submitted Spice to the World Wide Web Consortium, see

http://www.w3.org/TR/1998/NOTE-spice-19980123.html

This version incorporates a number of syntax changes inspired by work on refining the details of Spice and discussions at ECMA. For instance, the use of 'class' rather than 'prototype' and the use of 'slot' for defining class properties.

1. Background

1.1. Introduction

CSS[1] has proved to be highly effective and easy to learn for simple applications. Spice is designed to meet the needs of more complex applications for richer style sheets that offer capabilities such as:

Spice is an extended version of the ECMAScript language as defined by ECMA 262[2], more commonly known as JavaScript[3]. Spice is designed to make it simple to apply style and behaviour to XML[4] documents, but can also be applied to HTML[5]. Spice builds upon familiarity with CSS. It uses the same syntax for representing style rules and allows you to cascade style information, including imported CSS style sheets.

You are probably asking why not just use CSS? The answer is simple: Spice gives you much greater flexibility in adding style and behavior. Unlike CSS it doesn't make hardwired assumptions about what things you can render. Instead these are defined by downloadable flow objects that implement things like paragraphs, text emphasis, hypertext links etc. To get you started, CSS flow objects are made available as standard. When these aren't up to the job, you can write your own flow objects in Spice, Java[6] or ActiveX[7].

If you want to add a few extensions to common style sheets and flow objects, you can write a small style sheet for your extensions and import the common definitions. When the latter are cached or preloaded, only your extensions will need to be downloaded, speeding document display.

1.2. Relationship to DSSSL

DSSSL[8] is the Document Style Semantics and Specification Language and is meant to work with SGML[9], the Standard Generalized Markup Language. DSSSL was developed by ISO over a period of many years. It is a very powerful but hard to learn language for styling SGML documents. DSSSL exploits the Scheme[10] language to define rules for traversing document markup to generate a sequence of flow objects. DSSSL defines a large set of flow objects for printed documents.

A DSSSL rule for an H1 element:

    (element H1
        (make paragraph
             font-family-name: "Times New Roman"
             font-weight: 'bold
             font-size: 20pt
             quadding: 'center))

By contrast the Cascading Style Sheet (CSS) language is much easier to learn. You specify simple rules that specify one or more style properties such as font size, or color. Each rule has a selector specifying a pattern for matching to elements in the document markup.

A CSS rule for an H1 element:

    H1 {
        font-family: "Times New Roman";
        font-weight: bold;
        font-style: italic;
        font-size: 20pt;
        text-align: center;
    }

A powerful feature in CSS is the ability to cascade rules defined in different style sheets. This makes it easy to extend existing style sheets by importing them and adding one or two new rules. It also allows you to split up your style rules to take advantage of the ability to inherit style properties from enclosing elements.

Here H1 elements will inherit the font family from the rule for the enclosing BODY element.

    BODY {
        font-family: "Times New Roman";
        font-size: 12pt;
        text-align: left;
    }

    H1 {
        font-size: 20pt;
        font-weight: bold;
        text-align: center;
    }        

1.3. So what is Spice?

Spice is an amalgam of ideas from DSSSL, CSS and JavaScript. JavaScript is a popular scripting language for Web pages. Spice extends JavaScript to add cascading style rules using the same syntax as CSS. The rules further name the flow object class to be used to format each element.

Flow objects can be written in Spice or imported from classes implemented in Java or as ActiveX controls. Each flow object has a format method that is used to format its contents. The method is passed an element in the document markup, the style properties specified by the cascading style rules and the current flow.

You can use flow objects to generate additional flow objects, for example for list bullets or generated text for warnings. You can generate alternate flows for different media, such as for speech synthesis, or for alternative presentations, such as a table of contents generated from the document headings. Flow objects can exploit the full capabilities of the W3C Document Object Model[11] for traversing the document markup.

To further control the behaviour, you can write event handlers to script flow objects, whether or not these are implemented in Spice or in Java, etc. This gives you the ability to dynamically alter the document after it has been loaded.

1.4. Comparison with XSL

XSL[12] is a proposal for a style sheet langage defined in XML. It uses an entirely new syntax for style rules and flow object constructors. By contrast, Spice uses CSS syntax for style rules and properties, leveraging people's investment in CSS. A detailed comparison between Spice and XSL is given in [13].

2. Style Rules and Flow Objects

2.1. Introduction to Style Rules

Style rules are used to assign style properties to each element in the document. Here is a rule that could be used for an HTML P element:

    style p
    {
        fontSize: 12pt;
        display: block
    }

This rule specifies two properties: fontSize and display. The latter names a flow object to be used to format the element and can be defined in Spice or Java or even as an ActiveX control.

The display property is used to name a flow object that can be used to format "p" elements. The value "none" is reserved for the cases where you want to suppress the formatting of an element and its descendants, e.g. to suppress an element called "answer":

    style answer
    {
        display: none
    }

If necessary, you can change the name of the property used to name the flow object. This feature is mainly relevant when rendering to other media, for which the term "display" is inappropriate.

Flow objects are used for formatting different parts of a document and generally consist of things like blocks, list items, inline emphasis, tables etc. Properties like fontSize are passed to flow objects to control the formatting.

The import statement can be used to import style sheets which then form part of the cascade. More details on this statement are given in a later section. You can even import CSS style sheets:

    import "funstyle.css";

Unlike CSS, Spice doesn't allow the use of hyphens within property names. This avoids confusion with the infix operator for minus. When CSS style sheets are imported into Spice, a name like font-style is automatically mapped to fontStyle.

Another difference is to avoid problems with white space. You must use '*' when you want a selector to match any element. For instance:

    *[href]

matches any element with an attribute "href". Apart from this, the selector syntax is the same as for CSS2[14]. For example:

style ul p
{
    fontFamily: "Comic Sans MS"
}

Matches all P elements that are descendants of a UL element.

2.2. Spice Tutorial

Spice makes it simple to create style sheets for documents. A small subset of HTML is used below to illustrate this. (adapted from Paul Prescod's DSSSL tutorial[15])

Small but complete document and DTD

<!DOCTYPE tinyhtml [
<!ELEMENT tinyhtml - - (h1|p)* >
<!ELEMENT (h1|p) - - (#PCDATA|em|strong)* >
<!ELEMENT (em|strong) - - (#PCDATA)> ]>

<tinyhtml>
<h1>This is a heading</h1>
<p>This is text</p>
<p>This is <em>bold</em></p>
<p>This is <strong>outrageous</strong></p>
</tinyhtml>

The corresponding stylesheet

import document, block, inline;

style tinyhtml
{
    fontFamily: "Arial";
    fontSize: 12pt;
    display: document
}

style h1
{
    fontWeight: bold;
    fontSize: 1.5em;
    textAlign: center;
    display: block
}

style p
{
    textAlign: left;
    display: block
}

style em
{
    fontStyle: italic;
    display: inline
}

style strong
{
    fontWeight: bold;
    display: inline
}

This style sheet uses three kinds of flow objects: document, block and inline. These are imported from external definitions. This example uses standard flow objects defined for use with CSS, but you can also import flow objects from code written in Spice, Java or ActiveX. H1 and P elements are formatted using CSS blocks. These are inserted in sequence into the document flow object created for the toplevel HTMLLITE element. In a similar manner the inline flow objects for EM and STRONG elements are inserted into the blocks used for the parent elements.

Style properties are passed to flow objects to control the formatting. Property values are symbolic expressions whose evaluation is left to flow objects to deal with. Simple property values are identifiers such as bold or center or string literals such as "Times New Roman". Compound property values may include both comma and space separated items. You can also use operators like + and / for expressions, and functions such as rgb(128, 128, 255).

Spice defines a number of units such as "cm" for lengths, for instance 12cm. Other common units are "px" for pixels, "pt" for points and "em" for em's where one em is the same length as the current font height. Less common are "s" for seconds and "Hz" for frequency. "%" is used for percentages, e.g. for expressing the font size relative to that used by the parent flow object.

2.3. Inheritance

Flow objects inherit style properties from their parent flow object. This allows the font family to be specified once at the top level. The font size for H1 elements is set to 1.5em, which is twice the height of the current font size, as specified for the parent flow object. This yields a font size of 18pt. P elements are rendered in the font size for the document flow object, i.e. 12pt. The same goes for the flow objects for emphasis that are used to flow EM and STRONG elements.

2.4. Definitions and Expressions

Let's expand the HTML subset to include several levels of headings. We could simply paste copies of the H1 rule and modify that, but to allow the styles of all headings to be changed in a single place, we can use shared definitions for property values as follows:

headingFont = "Times New Roman";
headingWeight = bold;
headingPosture = italic;

style h1
{
    fontFamily: headingFont;
    fontWeight: headingWeight;
    fontStyle: headingPosture;
    fontSize: 1.5em;
    textAlign: center;
    display: block
}

style h2
{
    fontFamily: headingFont;
    fontWeight: headingWeight;
    fontStyle: headingPosture;
    fontSize: 1.2em;
    textAlign: left;
    display: block
}

Property values can include expressions, for instance to compute the font size:

headingFontSize = 20pt;

style h2
{
    fontFamily: headingFont;
    fontWeight: headingWeight;
    fontStyle: headingPosture;
    fontSize: 0.7 * headingFontSize;
    textAlign: left;
    display: block
}

2.5. Taking control with custom flows

By default, each element is formatted by a single flow object which is inserted in sequence into its parent flow object. This works well when the structure of the flow objects mirrors the structure of the elements in the markup.

Spice allows you to override the default processing by creating new classes of flow objects. These can be defined in Java or in Spice itself. A simple example is to insert some leading text for an element serving as a warning:

Spice allows you to define flow objects with the full power of a scripting language. The following example defines a new flow object derived from the standard flow object block. It creates a block with a solid border and inserts the text string "Warning!" in the start of its contents:

    class Warning extends block
    {
        method format(element)
        {
            this.style.borderStyle = solid;
            this.append(new Text("Warning!"));
            ProcessChildren(element, this);
        }
    }

    style warning
    {
        fontSize: 14pt;
        display: Warning
    }

Flow objects form a directed acyclic graph. The "flow" property specifies the parent flow, while the "children" property specifies the sequence of flow objects acting as children. The "format" method is passed the element in the document parse tree that the flow object is being applied to. The "append" method is used to append a new child to the sequence of children.

ProcessChildren is a built-in method that iterates through the children of an element in the document parse tree, creating the flow objects named by their display properties and appending them to the current flow. It is defined as follows:

    function ProcessChildren(element, flow)
    {
        var child, subflow;

        for (child in element.children)
        {
            if (child.display != null)
            {
                subflow = new child.display;
                flow.append(subflow);
                subflow.element = child;
                subflow.format(element);
            }
            else // assume node is Text
            {
                flow.append(child);
            }
        }
    }

This is the definition of the default methods for appending a child to a parent flow, and for formatting it.

    function Flow_append(node)
    {
        var size = this.children.length;
        this.children[size] = node;
        node.flow = this;
    }

    function Flow_format(element)
    {
        ProcessChildren(element, this);
    }

The default behaviour maps each element to a single flow object according to the display property then invokes that object's format method. This is repeated for each of the element's children.

2.6. Out of Sequence Processing

Normally the flow objects are generated in the same sequence as the elements in the document. On occasion, there is the need to do process something out of sequence.

An example is the aural rendering of table cells. HTML 4.0 provides the means to associate each cell with one or more header cells. In the simplest case, you render the pertinent headers before the contents of each data cell.

The first step is to use the headers attribute on the data cell to get the list of ID values for each of the headers. The next is to iterate through the list, getting each header in turn and processing it in the context of the current flow. Finally, the content of the current element is processed:

    var hdrefs = GetAttribute(element, "headers");

    for (hdref in hdrefs)
    {
        header = GetElementByID(element, hdref);
        ProcessChildren(header, this);      
    }
 
    ProcessChildren(element, this);

Other functions allow you to traverse the document markup tree using the full capabilities of the document object model defined by W3C.

A more sophistocated approach is to process the headers in a different mode. Modes are explained in the next section.

2.7. Using modes for alternate renderings

Suppose you want to construct a table of contents for the document that is to appear in a side panel. The table of contents might consist of the headings from all of the sections in the document. If you use the style rules for the main part of the document, the headings will appear too large in the table of contents.

The solution is to define a new set of rules within a mode declaration.

    mode toc
    {
        style *
        {
            display: none
        }

        style h1
        {
            level: 1
            display: toc_entry
        }

        style h2
        {
            level: 2
            display: toc_entry
        }
    }

In the format method for a flow object, you can then direct subsequent processing to use style rules for this mode using the with mode construct, as for example:

    with mode toc
    {
        ProcessChildren(element, toc);
    }

The contents of the element are then processed using style rules defined for the mode "toc". Note that style rules cascade independently for each mode.

If you don't need to define you own flow objects, you can use the with mode directive in style rules to specify the mode to be used to process a named flow object, e.g.

    style #toc
    {
        display: block with mode toc
    }

You can specify that an element is to be processed in several modes as follows:

    style body
    {
        display: block with mode toc, block with mode regular
    }

The default mode is named regular.

Note: an open question is whether mode definitions can be nested. Another is whether to replace"with mode" by "using".

2.8. Media dependent style sheets

Suppose you want to write a style sheet that will render some parts of a document to a display and other parts to a speech synthesiser. The media declaration allows you to specify which media a group of rules can be used for:

    media aural
    {
        style body
        {
            volume: medium;
            voiceFamily: male;
            speak: normal
        }

        style abbr
        {
            volume: medium;
            voiceFamily: female;
            speak: spellOut
        }
    }

You can then direct subsequent processing to use style rules for this media using the with media construct, as for example:

    with media aural
    {
        ProcessChildren(element, tape);
    }

The contents of the element are then processed using style rules defined for the media "aural". A comma separated list of media can be given following the media keyword.

2.9. Flow Objects, Events and Graphics

Spice introduces some short cuts for defining protoypes, which are ECMAScript's version of the object classes used in regular object oriented programming languages. This together with a few other features allows Spice to be compiled efficiently. Here is an example of a new class extending an imported class "block":

    class Warning extends block
    {
        method format(element)
        {
            this.style.borderStyle = solid;
            this.append(new Text("Warning!"));
            ProcessChildren(element, this);
        }
    }

This example is roughly equivalent to:

        function Warning()
        {
            this.name = "Warning";
            this.flow = null;
            this.children = new Array;
        }

        function Warning_layout(element)
        {
            this.style.borderStyle = solid;
            this.append(new Text("Warning!"));
            ProcessChildren(element, this);
        }

        new Warning;  // make sure prototype is created
        Warning.prototype.append = block.prototype.append;
        Warning.prototype.layout = Warning_layout;

Prototypes make it easy to define event handlers using the when statement:

    class Link extends inline
    {
        slot href = "http://www.w3.org/";

        ...

        when onmousedown
        {
            document.load(this.href);
        }
    }

This defines a flow object prototype for hypertext links, derived from the prototype for emphasis. Clicking on a link causes it to load the URL given in its href property. You can use the prototype construct to extend flow objects defined in other languages. This allows you to specify event handlers for such objects.

The addition of a standard graphics library would allow flow objects to be written in Spice that render themselves as text and graphics. For instance exploiting graphical effects for decorating text, something that is currently solved using bitmapped graphics. Flow objects written in Java can exploit AWT for this purpose.

2.10. Libraries

Interoperability across vendors and platforms is crucial to the Web as it greatly increases the number of people who can read each document. This in turn encourages the creation of content, and helps to explain why the Web has grown so dramatically.

Spice style sheets support interoperability by decoupling flow objects from their implementations. A Spice library (aka spice rack) names a set of flow objects with support for particular style properties. Each library is identified by a URL.

Libraries are decoupled from their implementations. This makes it practical to provide different implementations of a library for each platform. Each flow object has a name such as "paragraph" that is local in scope to the library in which it is defined. The import statement is used to import flow objects from libraries, e.g.

    import document, block, inline from "http://www.w3.org/Style/std.lib";

Here 'document' and 'inline' etc. are names of flow objects from the (hypothetical) library identified by the URL <http://www.w3.org/Style/std.lib>.

The implements statement is used to specify implementations for particular libraries, e.g.

    "css.spice" implements "http://www.w3.org/Style/std.lib" on "Spice";
    "css.jar" implements "http://www.w3.org/Style/std.lib" on "Java";
    "css.cab" implements "http://www.w3.org/Style/std.lib" on "ActiveX/win32";

Here "css.jar" and "css.cab" are relative URLs, defined as relative to the URL for the current style sheet. You can also use absolute URLs. The on keyword precedes a string naming the platform that this implementation applies to.

In the absence of a matching implements statement, the import statement expects to get the implementation from the URL specified by the from keyword. If that too is missing, you can simply list the URLs for the files you want to import, for instance:

    import "housestyle.css";    // imports a CSS style sheet
    import "koolbits.spice";    // imports a Spice style sheet

2.11. How to specify the style sheet

If the user asks the browser for mydoc.xml, this is downloaded and parsed to build a tree-like representation of the markup. The Spice script specifying the style can be specified using the accepted XML convention. For instance:/p>

  <?xml-stylesheet href="docstyle.spice" type="text/spice" ?>

In an HTML document you can use the LINK element, e.g.

  <link rel="stylesheet" type="text/spice" href="docstyle.spice">

The document is formatted starting with the element that is the root of the tree. The first step is to find matching style rules for the root element. Style rules cascade in the same way as for CSS. Spice uses the CSS2 definitions for the precedence of matching style rules. The display property names the flow object to be used to lay out the root element, and in turn its children. The process collects the matching styles for each element, creates a flow object to lay it out, and so on until the whole of the document has been layed out.

3. References

[1] "Cascading Style Sheets, level 1 (CSS)", Håkon Wium Lie, Bert Bos, W3C, December 1996. see http://www.w3.org/TR/REC-css1

[2] "ECMA-262", ECMAScript: A general purpose cross-platform programming language, ECMA June 1997, http://www.ecma.ch/ or helpdesk@ecma.ch

[3] "JavaScript The Definitive Guide", David Flanagan, O'Reilly & Asscoiates, Inc., Sebastopol CA, 1997, ISBN 1-56592-234-4

[4] "Extensible Markup Language (XML)", Tim Bray, Jean Paoli, C.M. Sperberg-McQueen, W3C, November 1997, see http://www.w3.org/TR/WD-xml

[5] "HTML", W3C Recommendation for HTML 4.0, December 1997, http://www.w3.org/TR. For an easy to read user guide see, "Raggett on HTML 4", Dave Raggett, Jenny Lam, Ian Alexander & Michael Kmiec, "Addison Wesley Longman Reading Mass., 1997. ISBN 0-201-17805-2

[6] "The Java Language Specification", James Gosling, Bill Joy, & Guy Steele, "Addison Wesley Longman Reading Mass., 1996. ISBN 0-201-63451-1

[7] "Designing and Using Activex Controls", Tom Armstrong, M &mp T Books, 1997. ISBN&nsp1-558-51503-8

[8] "ISO/IEC 10179:1996 Document Style Semantics and Specification Language (DSSSL)", A good place to start is at: http://www.jclark.com/dsssl/

[9] "ISO 8879. Information Processing -- Text and Office Systems - Standard Generalized Markup Language (SGML)", 1986. Available from http://www.iso.ch/cate/d16387.html. Newcomers to SGML are recommended to read "A Gentle Introduction to SGML", at http://www-tei.uic.edu/orgs/tei/sgml/teip3sg/SG.htm

[10] "The Seasoned Schemer", Daniel P. Friedman, Matthias Felleisen, Duane Bibby, MIT Press, 1996, ISBN 0-262-56100-X

[11] "Document Object Model Specification", Lauren Wood, Jared Sorensen, Steve Byrne, Robert S. Sutor, W3C, October 1997. see http://www.w3.org/TR/WD-DOM

[12] "A Proposal for XSL", Sharon Adler et al, August 1997. see http://www.w3.org/TR/NOTE-XSL-970910

[13] "A comparison of Spice and XSL" which can be found at: http://http://www.w3.org/People/Raggett/spice-and-xsl.html.

[14] "Cascading Style Sheets, level 2 (CSS)", Bert Bos, Håkon Wium Lie, Chris Lilley, Ian Jacobs, W3C, November 1997. see http://www.w3.org/TR/WD-css2

[15] "Paul Prescod's DSSSL tutorial" which can be found at: http://itrc.uwaterloo.ca/~papresco/dsssl/tutorial.html.

Dave Raggett <dsr@w3.org> is an engineer at Hewlett Packard's UK Laboratories, and works on assignment to the World Wide Web Consortium, where he is the W3C lead for HTML.