RelaxNG

In the previous section we saw what RNG does for Mozile. This section will go into more detail, particularly about Mozile Editing Schemas (MES).

Semantic Structure

Mozile 0.8 is designed to be an XHTML and XML+CSS editor. At the current time XML editing is not complete, however many of the features required to support XML editing are already in place.

A key difference between a rich-text document and an XML document is that the XML document has more structure. This is often called "semantic" structure, because XML tags carry information about the meaning of their contents. A rich-text file might have a selection of text which is bold and in large font. When a person reads the document she will recognize the selection as a headline, because of its format and position, and she will expect the headline to be a very brief summary of the rest of the document. However, that information about the meaning of the bold text isn't readily available to a computer program. In an equivalent XML document the selection will be marked with a headline tag, and a stylesheet would be used to display it as bold and in large font. This way both the person and the computer can easily pick out the headline.

A computer might not understand the meaning of the headline, not in the way a person does, but it can understand the structure of an XML document if it's properly described. There are several ways to describe XML structure, including Document Type Definitions (DTD) and the XML Schema language. However many people prefer RelaxNG, and this is the method that Mozile supports.

XML stands for eXtensible Markup Language; it's really a set of rules for creating particular markup languages. We usually call a particular XML markup language an "XML dialect". Examples include XHTML, DocBook, MathML, SVG, OpenDocument, etc. A RelaxNG schema describes the structure of the XML dialect. It tells us the elements, attributes, and text nodes which are allowed, and the patterns in which they can occur. One RNG schema describes all the documents that share the same XML dialect.

For an XML dialect with a small number of different tags and very strict rules, the RNG schema will be small. On the other hand, XHTML and DocBook have a large number of different tags, and the tags can be mixed quite freely, so their RNG schemas are large and complex.

Once you have an XML document and an RNG schema, you can check the document against the schema to make sure that it's valid. This is called validation, and Mozile has some support for validating XML documents with RNG schemas. We're working on improving it.

But when you're editing a document with all this rich structure, it's less important to know that the document was valid, and more important to know that document stays valid as you make your changes. This is the main reason that Mozile uses RNG.

Note

The following paragraph explains planned functionality which is not yet complete.

This is what we mean by "structured editing". Using the information from an RNG schema, Mozile is able to present the user with editing options that are appropriate to the structure of the document, and to verify that the changes do not make the structure invalid. For example, inside an XHTML p paragraph you are allowed to insert strong and span tags, but not another p tag, or a script tag or an li tag. Doing so would violate the RNG schema for XHTML, and so Mozile doesn't allow it. Mozile doesn't even present the option.

RelaxNG Objects

Before an RNG schema can be used, Mozile has to parse it. The mozile.useSchema() function starts the parsing operation.

The mozile.rng module contains a number of classes. Every time a new schema is parsed, a new mozile.rng.Schema object is created. It loads the RNG file and then creates a new object for each RNG element it encounters. mozile.rng.Node is the parent class for these objects, and there is a sub-class for each element type available in RNG: mozile.rng.Element, mozile.rng.Text, mozile.rng.Choice, etc. So the parsing operation creates a new hierarchy of JavaScript objects which matches the hierarchy of RNG elements.

Each of the mozile.rng.Node objects has a validate() method, which takes a node from the document and validates it against part of the RNG schema. The validation applies to the target node and all of its descendants. In this way we can validate the whole document or just pieces. Information about the validation operation is passed between objects by a mozile.rng.Validation object. The validation object is returned when the validation operation is complete. It has an isValid Boolean property which indicates whether the validation succeeded or failed. It also has report() and getFirstError() methods which can be used to get information about the validation operation once it is complete.

The mozile.rng.Node objects also provide methods, such as mustContain() and mayContain(), which we can use to determine what kinds of nodes are allowed by the RNG schema.

Of the RNG objects, the mozile.rng.Element object is the most important. Validation, events, and editing commands all focus on the elements of the document.

Mozile Editing Schema

A RelaxNG schema provides information about the structure of an XML document. We can use this information to figure out which commands should be allowed. But there are many cases when we need more specific information about which editing commands are allowed, and how those commands function.

For this we use another XML dialect called Mozile Editing Schema (MES). RNG files are XML, and they allow "annotations" in the form of XML element from other namespaces. We mix RNG and MES elements in the same file, using different namespaces, so MES can take advantage of the RNG structure.

Note

The MES system is currently still in flux, and some aspects may change.

There are five MES elements:

  • command - describes an editing command. This is the most important MES element.
  • group -groups commands together. Associated with the mozile.edit.CommandGroup class.
  • separator - divides commands.
  • define - used like the RNG define element to group elements for reuse. Must have a name attribute.
  • ref - used like the RNG ref element to refer to definitions. Must have a name attribute.

Mozile's MES system is aware of the rules for RNG define and ref elements, and it will follow RNG references as it searches for command elements. However the RNG parser will ignore all of the MES elements.

When the mozile.useSchema() function is called, the RNG schema will be loaded and parsed. Mozile will create the RNG objects. Then it will run through the list of all the mozile.rng.Elements and for each one of these it will generate the appropriate commands and attach them. The command names must be unique, and Mozile will not generate a new command if the name is already taken.

The command and group tags can have the following attributes. All of them will become properties of the JavaScript Command object which is generated from the MES.

  • name - a unique name which identifies the command or group among all the others. Should only contain alphanumeric characters.
  • label - a short description which will appear on buttons and menu items.
  • priority - a number which allows you to have some commands run before others. The default is 0, with higher numbers meaning higher priority. For example, insertText has priority 10. Negative values can be assigned.
  • image - a path to an icon for the command in the images directory. Should be a 16x16 pixel PNG image.
  • tooltip - a longer description of the command or group.

These attributes apply only to command tags:

  • class - the name of a command class, as described above. When the MES is parsed by Mozile, a new instance of the class will be created. The default is Command.
  • text - text content to be inserted by the Insert command. To specify Unicode characters, use this syntax:  . See the Unicode Charts for the character codes. If an element is given it will be used instead.
  • element - the tag name of an element to be created by Wrap, Insert, or Replace commands. Inside a script tag you can set this.element to an Element object.
  • accel is an "accelerator" or keyboard shortcut combination. In order to work across platforms we usually use the "Command" keyword, which translates to Ctrl on Windows and Linux and on Mac OS X. The sequence should be "Command-Meta-Control-Alt-Shift-Character", where "Character" is a single upper case letter (like "A") or the name of a key (like "Enter"). Key names include: "Backspace", "Tab", "Clear", "Return", "Enter", "Pause", "Escape", "Space", "Page-Up", "Page-Down", "End", "Home", "Left", "Up", "Right", "Down", "Insert", "Delete", and "F1" through "F12". Examples of accelerators include: "Command-U", "Command-Shift-U", "Command-Alt-Shift-U", "Shift-U", "Enter", "Command-Enter", "Shift-Left", etc. You can also specify a space separated list of accelerators, any of which will trigger the command: "Enter Command-Enter Command-Alt-Enter".
  • makesChanges - a "change code" which indicates what kinds of change the command makes. Can be "none" if no changes are made; "state" if only the undo state is changed; "text" if only text node data is changed; or "node" which includes changes to elements and attributes. A "text" change includes a "state" change, and a "node" change includes both of these. The default value is "node".
  • watchesChanges - indicates which change codes the command is sensitive to. The possible values are the same as those for makesChanges. The default is "node", which means that the command will update only when the node changes, and not on a "text" or "state" change. A "text" value means updates on "node" and "text" changes, but not "state" changes. A value of "none" indicates that this command never needs to update. This value is used by the respond() method to determine if a command's GUI representation should be checked for updates.
  • remove - when true the Insert command will perform a remove operation before inserting, removing any contents of the selection. When false it will move the contents of the selection into the inserted element, which is useful for elements like links (a tags). The default is true.
  • nested - when true the Wrap command will create wrappers inside other wrappers if told to do so. When false it will remove the wrapper if called inside another wrapper. The default is false.
  • target - used by the Navigate, Split, Unwrap, Replace, and Style commands to determine which node will be manipulated. Values can be:
    • any - the first node found.
    • text - the first text node found.
    • element - the first element found.
    • block - the first element which counts as a block-level element (usually because it has CSS display: block).
    • localName [tagName] - the first element which has a local name matching the given tagName (matching is done lower case).
    Inside a script tag you can set this.target to a function which returns a node. Any time a target is used a direction can be specified.
  • direction - used wherever target is used, this indicates the direction to use when searching for the target. Can be ancestor (the default), next, previous, or descendant.
  • collapse - used by Navigate to determine whether the selection should be collapsed after it is moved. Can be null (the default), start, or end.
  • copyAttributes - when true the Replace command will copy all of the attributes of the target element into the replacement element. When false it will not. The default is true.
  • className - used t o set the class attribute of a created element.
  • styleName - determines the CSS style to be set for a created element or for the Style command class.
  • styleValue - determines the new CSS value to be set for a created element or for the Style command class.

command elements can also have child elements. Both of these are optional, and no more than one of each should be included.

  • element - the first child element inside the element tag will be used as a template. Commands like Wrap and Insert will clone this template and insert the clone into the document. Wrap will then append other elements as children of the clone. If you set the import attribute to true Mozile will copy the element into the current namespace -- this is important when dealing with HTML elements.
  • script - can contain JavaScript code which will be evaluated inside the command object after it has been created and initialized. This allows you to customize the behaviour of the built-in Mozile command classes. Be careful of JavaScript scope rules, however. The this keyword will refer to the command object. We recommend including your JavaScript code as a CDATA section, as in the example below; this will avoid problems with the <, >, and & characters.

Creating your own commands is easy. Here are some examples.

Example 2.3. Creating a command

Here is a simple command that will wrap a selection in a span tag and use CSS to underline the text.

<m:command name="underline" class="Wrap" accel="Command-U"
  element="span" styleName="text-decoration" styleValue="underline"
  label="Underline" image="silk/text_underline" tooltip="Underline text"/>

No JavaScript is needed, however we are limited to using a single CSS style.


Example 2.4. Creating a command using script

This command will underline selected text and make it red.

<m:command name="underline" class="Wrap" accel="Command-U"
  label="Underline" image="silk/text_underline" tooltip="Underline text">
  <m:script>
    <![CDATA[
    this.element = mozile.dom.createElement("span");
    mozile.dom.setStyle(this.element, "text-decoration", "underline");
    mozile.dom.setStyle(this.element, "color", "red");
    ]]>
  </m:script>
</m:command>

The command has the unique name "underline". It uses the mozile.edit.Wrap class to do most of the work. You can use the keyboard shortcut "Command-U" to trigger the command; on Windows and Linux this means Ctrl+U, and on Mac OS X it means +U. The icon for the button will be images/silk/text_underline.png, the label will be "Underline", and the tooltip will be "Underline text", if these are displayed by the GUI. The script tag says to create a new span element and set the "text-decoration" style to "underline".


Example 2.5. Creating a command using element

The same thing can be done using the element tag, with a few reservations.

<m:command name="underline" class="Wrap" accel="Command-U"
  label="Underline" image="silk/text_underline" tooltip="Underline text">
  <m:element import="true">
    <span xmlns="http://www.w3.org/1999/xhtml" style="text-decoration: underline; color: red"/>
  </m:element>
</m:command>

No JavaScript is needed. However, note that the span element has the attribute xmlns="http://www.w3.org/1999/xhtml", which indicates that it is an XHTML element. However, since the import attribute is set Mozile will import a copy of the element into the current document and namespace, whatever that might be. When dealing with HTML document import is important! For XHTML and XML documents you may want to mix namespaces.