W3CNOTE-spice-19980123

Adding Style and Behavior to XML with a dash of Spice

Submitted to W3C on 3 February 1998

This version:
http://www.w3.org/pub/WWW/TR/NOTE-spice-980123.html
Latest version:
http://www.w3.org/pub/WWW/TR/NOTE-spice
Editor:
Robert Stevahn, Hewlett-Packard Co.


Status of this document

This document is a NOTE made available by the World Wide Web Consortium for discussion only. This indicates no endorsement of its content, nor that the Consortium has, is, or will be allocating any resources to the issues addressed by the NOTE. A list of current NOTEs can be found at: http://www.w3.org/TR/.

This document is part of a complete submission to the W3C.

Note: since notes and working drafts are subject to frequent change, you are advised to reference the "latest version" URL above, rather than the URLs for specific working drafts themselves.

Abstract

CSS is proving to be an easy-to-learn, effective approach for styling HTML pages. This NOTE 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 doesn't rely on prior knowledge of the meaning of style properties, or on predefined flow-objects.

To encourage interoperability, URLs are used to name libraries that support a given set of flow objects and style properties. Each library can be implemented in different ways according to the needs of each platform. Flow objects can be implemented in Java, ActiveX or even ECMAScript. CSS flow objects are supported via a standard library.

Spice leverages ongoing W3C work on CSS and DOM for the syntax of style rules, the properties of standard flow objects and the means for accessing the document markup.

1. Background

1.1. Introduction

CSS[1] has proven 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 behavior 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 hard-wired 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 layout 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, e.g., for list bullets or generated text for warnings. You can generate alternate flows for different media, such as for print or 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 behavior, 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 lay out 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 lay out "p" elements. The value "none" is reserved for the cases where you want to suppress the layout 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 laying out 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 htmllite [
<!ELEMENT htmllite - - (h1|p)* >
<!ELEMENT (h1|p) - - (#PCDATA|em|strong)* >
<!ELEMENT (em|strong) - - (#PCDATA)> ]>

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

The corresponding stylesheet

import document, block, inline;

style htmllite
{
    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. Unlike CSS, there is no built-in list of properties. Any identifiers appearing for the first time in style rules are treated as named constants. In the above, this applies to "center" and "bold". Spice defines a number of postfix operators such as "cm" for length units. Other common units are "px" for pixels, "pt" for points and "em" for ems, where one em is the same length as the current font height.

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 one-and-a-half times 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.

Note: uninitialized identifiers such as bold and center are treated as a kind of symbolic value. They can be tested for equality and assigned to variables, but throw an undefined exception in other expressions, e.g., if you try to compare them to numbers. This is an extension to the set of primitive types defined in ECMA 262.

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 example, 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:

    prototype Warning extends block
    {
        function layout(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 "layout" 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.layout(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 laying it out.

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

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

The default behavior maps each element to a single flow object according to the display property, then invokes that object's layout 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 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 layout method for a flow object, you can then direct subsequent processing to use style rules for this mode using the with mode construct, 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 analog for the object classes used in other object-oriented programming languages. Prototypes are more flexible than classes in that you can add new properties and methods at run-time (classes are fixed at compile-time). The prototype construct is used to define a new prototype. For example:

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

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:

    prototype Link extends inline
    {
        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 example, one could exploit 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 XML convention based upon a processing rule:

  <?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 laid out starting with the element that is the root of the tree. The first step is to find matching style rules for the 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 laid out.

3. References

[1] "Cascading Style Sheets, level 1 (CSS)", HRhokon 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&nsp;1-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, 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:://www.w3.org/Submission/1998/02/Spice-and-XSL.html.

[14] "Cascading Style Sheets, level 2 (CSS)", Bert Bos, HRhokon 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.