In the previous section there was a short discussion of what RNG does for Mozile. This section will go into more detail, particularly about Mozile Editing Schemas (MES).
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.
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.
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.
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.
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.Element
s 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
). 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).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
.styleName
- used by the Style
command to determine the CSS style to be set.styleValue
- used by the Style
command to determine the new value of the CSS style.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. element
attribute or the script
tag instead.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> <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. If your document is an HTML document, there may be problems mixing elements with different namespaces. If your document is an XHTML or XML document then this will work, however beware of browser issues for displaying XHTML and XML documents.