Chapter 10. RDF, RDF Tools, and the Content Model

Chapter 9 introduced the Resource Description Framework (RDF) as the basis for building display data in the interface, where XUL templates take RDF-based data and transform it into regular widgets. But RDF is used in many other more subtle ways in Mozilla. In fact, it is the technology Mozilla uses for much of its own internal data handling and manipulation.

RDF is, as its name suggests, a framework for integrating many types of data that go into the browser, including bookmarks, mail messages, user profiles, IRC channels, new Mozilla applications, and your collection of sidebar tabs. All these items are sets of data that RDF represents and incorporates into the browser consistently. RDF is used prolifically in Mozilla, which is why this chapter is so dense.

This chapter introduces RDF, provides some detail about how Mozilla uses RDF for its own purposes, and describes the RDF tools that are available on the Mozilla platform. The chapter includes information on special JavaScript libraries that make RDF processing much easier, and on the use of RDF in manifests to represent JAR file contents and cross-platform installation archives to Mozilla.

Once you understand the concepts in this chapter, you can make better use of data and metadata in your own application development.

10.1. RDF Basics

RDF has two parts: the RDF Data Model and the RDF Syntax (or Grammar). The RDF Data Model is a graph with nodes and arcs, much like other data graphs. More specifically, it's a labeled-directed graph. All nodes and arcs have some type of label (i.e., an identifier) on them, and arcs point only in one direction.

The RDF Syntax determines how the RDF Data Model is represented, typically as a special kind of XML. Most XML specifications define data in a tree-like model, such as XUL and XBL. But the RDF Data Model cannot be represented in a true tree-like structure, so the RDF/XML syntax includes properties that allow you to represent the same data in more than one way: elements can appear in different orders but mean the same thing, the same data can be represented as a child element or as a parent attribute, and data have indirect meanings. The meaning is not inherent in the structure of the RDF/XML itself; only the relationships are inherent. Thus, an RDF processor must make sense of the represented RDF data. Fortunately, an excellent RDF processor is integrated into Mozilla.

10.1.1. RDF Data Model

Three different types of RDF objects are the basis for all other RDF concepts: resources, properties, and statements. Resources are any type of data described by RDF. Just as an English sentence is comprised of subjects and objects, the resources described in RDF are typically subjects and objects of RDF statements. Consider this example:

Eric wrote a book.

Eric is the subject of this statement, and would probably be an RDF resource in an RDF statement. A book, the object, might also be a resource because it represents something about which we might want to say more in RDF -- for example, the book is a computer book or the book sells for twenty dollars. A property is a characteristic of a resource and might have a relationship to other resources. In the example, the book was written by Eric. In the context of RDF, wrote is a property of the Eric resource. An RDF statement is a resource, a property, and another resource grouped together. Our example, made into an RDF statement, might look like this:

(Eric) wrote (a book)

Joining RDF statements makes an entire RDF graph.

We are describing the RDF data model here, not the RDF syntax. The RDF syntax uses XML to describe RDF statements and the relationship of resources.

As mentioned in the introduction, the RDF content model is a labeled-directed graph, which means that all relationships expressed in the graph are unidirectional, as displayed in Figure 10-1.

Figure 10-1. Simple labeled-directed graph

images

A resource can contain either a URI or a literal. The root resource might have a URI, for example, from which all other resources in the graph descend. The RDF processor continues from the root resource along its properties to other resources in the graph until it runs out of properties to traverse. RDF processing terminates at a literal, which is just what it sounds like: something that stands only for itself, generally represented by a string (e.g., “book,” if there were no more information about the book in the graph). A literal resource contains only non-RDF data. A literal is a terminal point in the RDF graph.

For a resource to be labeled, it must be addressed through a universal resource identifier (URI). This address must be a unique string that designates what the resource is. In practice, most resources don't have identifiers because they are not nodes on the RDF graph that are meant to be accessed through a URI. Figure 10-2 is a modified version of Figure 10-1 that shows Eric as a resource identifier and book as a literal.

Figure 10-2. Resource to literal relationship

images

Resources can have any number of properties, which themselves differ. In Figure 10-2, wrote is a property of Eric. However, resources can also have multiple properties, as shown in Figure 10-3.

Figure 10-3. RDF Graph with five nodes

images

The RDF graph in Figure 10-3 has five nodes, two resources, and three literals. If this graph were represented in XML, it would probably have three different XML namespaces inside of it: RDF/XML, a book XML specification, and a computer XML specification. In English, the graph in Figure 10-3 might be expressed as follows:

Eric wrote a book of unknown information. Eric's computer is 700 MHz and has an Athlon CPU.

Note that if Eric wrote a poem and a book, it would be possible to have two wrote properties for the same resource. Using the same property to point to separate resources is confusing, however. Instead, RDF containers (see the section Section 10.1.2.2, later in this chapter) are the best way to organize data that would otherwise need a single property to branch in this way.

10.1.1.1. RDF URIs relating to namespaces

The URIs used in RDF can be part of the element namespace. (See Section 2.2.3 and in Section 7.1.3 for more information about XML namespaces.) This use is especially true for properties. Some namespaces can be created from previous examples:

xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:book="http://www.oreilly.com/rdf#"
xmlns:comp="my.computer.hardware#"

When you use namespaces, the graph looks much different, as shown in Figure 10-4.

Figure 10-4. Namespaces applied to Figure 10-3

images

The resource identifier is often displayed in a URL format too, but it shouldn't use the same namespace URL as the RDF/XML file. The URL typically tries to describe a unique object, such as http://my.jar-of-flies.com.

10.1.1.2. RDF triples: subject, predicate, and object

A triple is a type of RDF statement. While an RDF statement can be a loose collection of resources, properties, and literals, a triple typically defines a tighter relationship between such elements.

The first part of a triple is the subject. This part is the resource described by the triple. The second part of the triple is the predicate. This part is a subject's property, a thing that joins it with something else. The third part is the object, which is either a resource or a literal.

RDF triples are significant because their stricter semantics guarantee the relationship between parts. A triple is a more formal version of the RDF statement, which is used more broadly. In Figure 10-4, all statements are formally subject > predicate > object, so those statements are triples.

10.1.1.3. RDF data model terminology

When reading RDF specifications, documentation, examples, and other related material on the Internet, you can encounter a dizzying array of terms that mean the same thing. Table 10-1 should help clarify these different terms. The italicized versions of the synonyms all do not technically mean the same thing, but are loose synonyms whose meanings depend on the context in which they are used.

Table 10-1. Synonyms in RDF

Common term Synonyms
Resource Subject, object
Resource identifier Name, (resource) URI, ID, identifier, URL, label
Properties Attributes
Statement Triple, tuple, binding, assertion
Subject Source, resource, node, root
Predicate Arc, (statement) URI, property, atom
Object Value, resource, node, literal

10.1.2. RDF Syntax

Mozilla uses XML to represent RDF data. In 1999, the W3C defined the RDF/XML specification syntax to make it the most common way RDF is used. The RDF/XML format is sometimes called the RDF serialization syntax because it allows RDF models to be sent easily from one computer application to another in a common XML format.

When an application reads an RDF file, the Mozilla RDF processor builds a graphical interpretation in-memory. In this section, you learn how to build an RDF file from scratch and see what the graph looks like after running through Mozilla's RDF processor.

RDF:RDF is a common namespace representation of RDF/XML data and is the one most frequently used in Mozilla files. However, it can be hard to read, so this chapter uses rdf:RDF. The W3C also used rdf:RDF in the RDF recommendation document.

10.1.2.1. Examining a simple RDF file

We begin with an example of an RDF file whose basic layout and simple syntax can be a model for the more advanced data introduced later. The RDF file shown in Example 10-1 is a list of three types of “flies,” with the context of those “flies” inside a “jar.” Example 10-1 also contains a namespace that defines these types of flies and shows the rdf and fly XML intertwined.

Example 10-1. Simple RDF file with "fly" namespace
<?xml version="1.0"?>
  <rdf:RDF
     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     xmlns:fly="http://xfly.mozdev.org/fly-rdf#">
    <rdf:Description about="urn:root">
      <fly:types>
        <rdf:Bag>
          <rdf:li>
            <rdf:Description fly:name="Horse"/>
          </rdf:li>
          <rdf:li>
            <rdf:Description fly:name="House"/>
          </rdf:li>
          <rdf:li>
            <rdf:Description fly:name="Fruit"/>
          </rdf:li>
        </rdf:Bag>
      </fly:types>
    </rdf:Description>
  </rdf:RDF>

<rdf:Description> is the tag used to outline a resource. Example 10-1 shows how the about attribute references the resource identifier and makes this resource unique in the document. Two resources cannot have the same about value in a document, just as tags cannot share an id in an XML document. Both attributes guarantee the unique nature of each element and relationship.

<rdf:Description about="http://my.jar-of-flies.com">
    <fly:types>
      <rdf:Bag>

http://my.jar-of-flies.com, is the subject shown in the previous code snippet. My jar of flies is a resource definition and defines only what flies are inside of the statement. The predicate, which addresses a property in the resource, is defined by the tag <types> (of the http://xfly.mozdev.org/fly-rdf# namespace).

The final part of the statement, the object, is the actual data of the predicate and a container of type bag. The container is an RDF resource that “holds,” or points to, a collection of other resources. In the next section, container types are discussed in depth. Figure 10-5 illustrates how the triple originates from the root subject and includes the container object.

Figure 10-5. The first statement of the graph, with labeled parts

images

In this case, an RDF statement is extracted from the example, but no useful data is reached. Little can be done with an empty RDF container, and two more steps are needed to reach literals that contain names of the flies.

10.1.2.2. RDF containers

Containers are a list of resources or literals. They are a form of RDF resource. There are three different container types: bag, sequence, and alternative. Bag is an unordered list of items, whereas sequence is an ordered list of items. They both allow duplicate values. Alternative is a list of values that could replace a particular property in a resource. Sequence is the most popular container for use in Mozilla applications because it frequently uses ordered lists of data. A container's graphical definition is an entire separate statement about its type and the items it contains. In Figure 10-6, you can see the type of the container defined in the RDF statement with the property rdf:type. The remaining properties are the container's items.

Figure 10-6. The second statement of the graph, with labeled parts

images

Once the container is defined, you can examine its collection of elements. At this point in the RDF code, direct comparisons can again be made from the code to the graph:

<rdf:Bag>
    <rdf:li>
      <rdf:Description ...

Here, the <rdf:li> tag is similar to the <li> tag in HTML, which stands for “list item.” Moving from code to graph, the new representation is shown in Figure 10-6.

In Figure 10-6, the subject is the instance of the container. This statement does not begin from rdf:Bag because that resource is only a type definition. The actual items in the container originate from the instance created in memory by any RDF processor, including Mozilla's.

Mozilla's RDF processor fills in the rdf:*(1) of the resource identifier in Figure 10-6 with a hashed value. The same is true for the container's resource identifier. The actual values come out as something like rdf:#$0mhkm1, though the values change each time the RDF document is loaded.

Objects inside of the container have properties identified automatically as rdf:_1, rdf:_2, etc., as defined by the RDF model specification. However, RDF applications such as Mozilla may use different identifiers to differentiate list objects.

10.1.2.3. Literals

The final statement in Example 10-1 allows the predicate to reach the text data, the literal “horse” shown in Figure 10-7. Note that the about reference on the Description is fictitious RDF, but it demonstrates the difference between a resource and a literal.

<rdf:Description about="rdf:*(1)" fly:name="Horse"/>

Figure 10-7. The third statement of the graph, with labeled parts

images

The previous RDF code for the literal is syntactic shorthand. Using this type of shortcut can make RDF much easier to read. The previous code snippet is the same as the longer and more cumbersome one shown here:

<rdf:Description about="rdf:*(1)">
    <fly:name>Horse</fly:name>
  </rdf:Description>

The shorthand version of this statement can be useful when you have a lot of data or when you want to use one syntax to show all relationships in the graph.

10.1.2.4. The RDF syntax and RDF graphs

Figure 10-8 shows the entire RDF graph for the RDF file in Example 10-1. This graph was compiled by combining the concepts you've seen in Figures 10-5 through 10-7.

As you can see, the statements fit together quite nicely. Four resources originate from the container, and one is the container type definition. The other two properties are numbered according to their order in the RDF file.

Figure 10-8. The full graph

images

10.1.3. Building an RDF File from Scratch

Now that you understand the basic principles of a simple RDF file, this section steps through the creation of an RDF file from information found in regular text:

There is a jar with the name urn:root. Inside of it there are two types of flies listed as House and Horse.

There are three Horse flies. The Face Fly, coded in green, is officially identified as “musca autumnalis”. The Stable Fly, coded in black, has the identification “stomoxys_calcitrans.” The red-coded Horn Fly, located in Kansas, is identified as “haematobia_irritans.”

There are also three house flies. “musca_domestica,” coded in brown, has the name “Common House Fly.” A gray fly named “Carrion Fly” has the ID “sarcophagid” and is found globally. Finally, The “Office Fly,” coded with white, is prevalent in the Bay Area.

You can use the techniques described here to model the data you want in your application: spreadsheet-like rosters of people, family trees, or catalogs of books or other items.

10.1.3.1. Identify namespaces

The new RDF file will have three namespaces including the RDF namespace. The result is two different data types that are connected in an RDF graph. For the sake of the example, one namespace is not in the standard URL format. Here is how the RDF file namespaces are set up:

<?xml version="1.0"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:fly="http://xfly.mozdev.org/fly-rdf#"
         xmlns:location="fly-location#">
</rdf:RDF>

10.1.3.2. Root resource

This file's root resource is an urn:root, which is the conventional name for root nodes in Mozilla's RDF files. When rendering RDF files, defining a root node for processing the document can be useful -- especially when building templates. This root node can be entered as the first item in the file:

<?xml version="1.0"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:fly="http://xfly.mozdev.org/fly-rdf#"
         xmlns:location="fly-location#">
  <rdf:Description about="urn:root">
  </rdf:Description>
</rdf:RDF>

10.1.3.3. Root sequence

Next, a generic tag needs to be used to specify a sequence of “fly” data. As in Example 10-2, <fly:list> is used as a list of fly types. This tag is a generic name because of the way XUL templates process lists of RDF data. If a list of data has sublists, as in the following examples, then they must use the same tag name to recurse correctly for the data they contain.

Example 10-2 represents all the information given in the first paragraph of the text example: “There is a jar set up with the name urn:root. Inside of it there are two types of flies, listed as House and Horse.”

Example 10-2. RDF root sequence
<?xml version="1.0"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:fly="http://xfly.mozdev.org/fly-rdf#"
         xmlns:location="fly-location#">
  <rdf:Description about="urn:root">
    <fly:list>
      <rdf:Seq>
        <rdf:li>
          <rdf:Description ID="House" fly:label="House"/>
        </rdf:li>
        <rdf:li>
          <rdf:Description ID="Horse" fly:label="Horse"/>
        </rdf:li>
      </rdf:Seq>
</fly:list>
  </rdf:Description>
</rdf:RDF>

An RDF sequence resides with its list of resources inside <fly:list>. Here, shorthand RDF specifies a label with the fly:label attribute. The ID attribute within this sequence is actually a pointer to the main definition of the resource described by an about attribute of the same value. The about attribute includes a # in its identifier, much like HTML anchors use <a href="#frag"> to refer to <a name="frag">. For example, ID="Horse" points to about="#Horse" elsewhere in the file, allowing you to add to the description of any element with new properties and resources.

10.1.3.4. Secondary sequences and literals

The Horse and House resources need to be defined next. Example 10-3 shows the creation of Horse from the second paragraph. The process for creating House is almost identical.

Example 10-3. The Horse sequence
<rdf:Description about="#Horse">
    <fly:list>
      <rdf:Seq>
        <rdf:li>
          <rdf:Description about="musca_autumnalis"
                           fly:label="Face fly"
                           fly:color="green"/>
        </rdf:li>
        <rdf:li>
          <rdf:Description about="stomoxys_calcitrans"
                           fly:label="Stable Fly"
                           fly:color="black"/>
        </rdf:li>
        <rdf:li>
          <rdf:Description about="haematobia_irritans"
                           fly:label="Horn Fly"
                           fly:color="red"
                           location:location="Kansas"/>
        </rdf:li>
      </rdf:Seq>
    </fly:list>
  </rdf:Description>

Here the shorthand RDF definition continues to use only the attributes. Again, a <fly:list> is defined and the items inside it are listed. The listed values have multiple attribute values, all of which are RDF literals. In longhand with RDF showing all literals, the last item would be written out as follows:

<rdf:li>
   <rdf:Description about="haematobia_irritans ">
       <fly:label>Horn Fly</fly:label>
       <fly:color>red</fly:color>
       <location:location>Kansas</location:location>
   </rdf:Description>
</rdf:li>

The two different namespace literals are both resource attributes. haematobia_irritans is used as the resource identifier because it is a unique value among all data.

Laying out the data in the same pattern gives you the final, full RDF file in Example 10-4.

Example 10-4. Entire RDF file
<?xml version="1.0"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:fly="http://xfly.mozdev.org/fly-rdf#"
         xmlns:location="fly-location#">
  <rdf:Description about="urn:root">
    <fly:list>
      <rdf:Seq>
        <rdf:li>
          <rdf:Description ID="House" fly:label="House"/>
        </rdf:li>
        <rdf:li>
          <rdf:Description ID="Horse" fly:label="Horse"/>
        </rdf:li>
      </rdf:Seq>
    </fly:list>
  </rdf:Description>
  <rdf:Description about="#Horse">
    <fly:list>
      <rdf:Seq>
        <rdf:li>
          <rdf:Description about="musca_autumnalis"
                           fly:label="Face fly"
                           fly:color="green"/>
        </rdf:li>
        <rdf:li>
          <rdf:Description about="stomoxys_calcitrans"
                           fly:label="Stable Fly"
                           fly:color="black"/>
        </rdf:li>
        <rdf:li>
          <rdf:Description about="haematobia_irritans"
                           fly:label="Horn Fly"
                           fly:color="red"
                           location:location="Kansas"/>
        </rdf:li>
      </rdf:Seq>
    </fly:list>
  </rdf:Description>
  <rdf:Description about="#House">
    <fly:list>
      <rdf:Seq>
        <rdf:li>
          <rdf:Description about="musca_domestica"
                           fly:label="Common House Fly"
                           fly:color="brown"/>
        </rdf:li>
        <rdf:li>
          <rdf:Description about="sarcophagid"
                           fly:label="Carrion Fly"
                           fly:color="gray"
                           location:location="Worldwide"/>
        </rdf:li>
        <rdf:li>
          <rdf:Description about="musca_oficio"
                           fly:label="Office Fly"
                           fly:color="white"
                           location:location="California, Bay Area"/>
        </rdf:li>
      </rdf:Seq>
    </fly:list>
  </rdf:Description>
</rdf:RDF>

Example 10-4 shows the RDF data used in several template examples in Chapter 9. Example 9-4 includes the 10-4.rdf datasource, as do many of those templates. You can copy the data out of Example 10-4 and into a file of the same name to use as a datasource.

10.2. The Mozilla Content Model

One theme of this book -- and a general goal of the Mozilla development environment -- is that developers can create real applications using many of the same technologies they use to create a web page. The Gecko rendering engine, sitting at the heart of Mozilla and happily rendering web content, XML files, XUL interfaces, and whatever else they can support, is what makes this type of development possible. But how does Gecko know what to render and how? How can RDF data be handed over so that Gecko knows how to draw it?

When a browser uses the same engine to draw everything -- its own interface as well as the various kinds of content it supports -- that engine treats everything as content. Gecko needs a way to understand all the various parts of the Mozilla browser itself -- such as the sidebar, the toolbars, and the mail folders and mail messages -- as resources it can render and display in the Mozilla chrome. This approach to the Mozilla application interface is called the content model.

In Mozilla's content model, XUL documents and other interface resources are transformed into RDF when they are read. Each chunk of content is represented as a separate RDF datasource (see the next section, Section 10.2.1, for more information) and is then fed to the XUL Content Builder and rendered as the actual bits on the screen, as Figure 10-9 shows.

Figure 10-9. Diagram of Mozilla's content model

images

As you can see in Figure 10-9, the content model can be complex. The XUL documents in Figure 10-9 are files such as navigator.xul, which defines the main browser window's basic layout; the RDF documents include files like help-toc.rdf, which defines the Mozilla Help viewer's table of contents. The list of mail folders and accounts shown in Example 10-5 are part of the built-in data that Mozilla renders into browser content.

Whatever the source, the content model gets everything processed in-memory as RDF so that any data can be combined and formatted into XUL or other interface code. All sources of RDF data are called datasources.

10.2.1. Datasources

A datasource is a collection of related, typically homogenous, RDF statements. A datasource may be a single RDF file like localstore.rdf, a combination of files, or RDF structures that exist only in memory (as discussed later).

In Mozilla, datasources represent the messages in your email inbox, your bookmarks, the packages you installed, your browser history, and other sets of data. Datasources can be combined easily (or "composed," which is where the term "composite datasource" comes from).

10.2.1.1. A datasource example: mailboxes

Several datasources describe all the folders and messages in Mozilla's email. A root datasource called msgaccounts describes which mail servers and accounts are present. Separate datasources then represent each account separately. These datasources are composed to create the entire email storage system. The higher levels of this content structure look like Example 10-5.

Example 10-5. Content model of email datasources
msgaccounts:/
+-- http://home.netscape.com/NC-rdf#child -->
    imap://oeschger@imap.netscape.com
    |    +-- http://home.netscape.com/NC-rdf#IsServer --> "true"
    |    +-- http://home.netscape.com/NC-rdf#child -->
    |        imap://oeschger@imap.netscape.com/INBOX
    |    +-- http://home.netscape.com/NC-rdf#TotalMessages --> "4"
    |    +-- http://home.netscape.com/NC-rdf#IsServer --> "false"
    |    +-- http://home.netscape.com/NC-rdf#MessageChild -->
    |        imap_message://oeschger@imap.netscape.com/INBOX#1
    |    +-- http://home.netscape.com/NC-rdf#MessageChild -->
    |        imap_message://oeschger@imap.netscape.com/INBOX#2
    |    +-- http://home.netscape.com/NC-rdf#MessageChild -->
    |    etc...
    |
+-- http://home.netscape.com/NC-rdf#child -->
    mailbox://oeschger@pop.netscape.com
    |    +-- http://home.netscape.com/NC-rdf#IsServer --> "true"
    |    +-- http://home.netscape.com/NC-rdf#child -->
    |        mailbox://oeschger@pop.oeschger.com/INBOX
    |    +-- http://home.netscape.com/NC-rdf#TotalMessages --> "2"
    |    etc...

Each direct child of the root msgaccounts:/ is a mail server. This portion of the graph shows two Mozilla email accounts that are the primary children: imap://oeschger@imap.netscape.com and mailbox://oeschger@pop.netscape.com. These two accounts are entirely different datasources that can exist on their own. The content model for email actually extends much lower than what is represented in this outline. It uses RDF to represent the data all the way into the actual message lists.

10.2.1.2. Types of datasources

As you may have already inferred, email accounts are not actually RDF files. Mozilla provides a custom RDF map of all email accounts and messages and the content model represents the accounts and their relationships to one another as RDF so they can be integrated and rendered properly. The interface to this custom mail RDF map makes it possible to display a list of messages and mailboxes in a <tree> template.

Another example of a datasource, the in-memory-datasource, doesn't come from an actual RDF file. When an in-memory datasource is created, it doesn't contain data. However, data can be inserted into it and stored in memory until the datasource is destroyed. In-memory datasources frequently represent ephemeral data like search results. Other basic datasource types are described in Table 10-2.

Table 10-2. Types of datasources

Type Description
Local datasource A local datasource is an RDF graph contained in an RDF/XML file on a local disk. All RDF files in the chrome registry (e.g., all-packages.rdf in the chrome directory, which keeps track packages installed in Mozilla) are local datasources.
Remote datasource RDF can be accessed locally or remotely. A remote datasource is an RDF/XML file stored on a server and accessed with a URL.
In-memory datasource An in-memory datasource exists only in memory during a Mozilla session. In-memory datasources are built with assertions, statements that build an in-memory data model by adding resources, properties, and value to those.
Built-in datasource These unique, prefabricated datasources represent something used often in Mozilla, such as a built-in filesystem datasource and a history datasource.
Composite datasource A composite datasource may be a combination of any of the datasources previously listed. RDF allows you to merge different graphs.

10.3. RDF Components and Interfaces

Once you are comfortable using XUL templates to display RDF data (see Chapter 9), you should explore the various ways to create and change that data. In Mozilla, data is generally RDF, since all data in Mozilla is either represented formally in RDF or passed through the RDF-based content model for display. Use the tools described in this section to manipulate RDF and the data it represents.

Mozilla has a great set of interfaces for creating, manipulating, and managing RDF, and it also provides ready-made RDF components that represent datasources used in Mozilla. Think of RDF interfaces as ways to manipulate RDF directly and of RDF components as sets of the interfaces already associated with a particular kind of data, such as bookmarks. Interfaces tend to deal with the RDF model itself, without regard to the kinds of data being handled, while RDF components give you control over specific Mozilla data. See the next two sections for more information on RDF interfaces and components.

10.3.1. What Is an RDF Component?

An RDF component may implement any number of the general RDF interfaces described here, in addition to special interfaces for accessing and controlling the data the datasource represents. For example, @mozilla.org/rdf/datasource;1?name=internetsearch is an RDF component used to control Mozilla's internet searching facility. In Mozilla, a component can act as a library of code specific to a given set of data or domain. The internetsearch component is instantiated and used to recall text entered in a previous search:

var searchDS = Components.classes["@mozilla.org/rdf/datasource;1?name=internetsearch"]
      .getService(Components.interfaces.nsIInternetSearchService);

searchDS.RememberLastSearchText(escapedSearchStr);

This RDF component implements an interface called nsIInternetSearchService, which is selected from the component and used to call the RememberLastSearchText method. Although you can also use the getService method to get one of a component's RDF interfaces (e.g., by using getService(Components.interfaces.nsIRDFDataSource)), doing so is seldom necessary in practice. RDF components are tailored to the datasources they represent and usually provide all the access you need to access that data directly. Example 10-6 lists RDF components in Mozilla.

Example 10-6. RDF-specific components built into Mozilla
@mozilla.org/rdf/container;1
@mozilla.org/rdf/content-sink;1
@mozilla.org/rdf/datasource;1?name=addresscard
@mozilla.org/rdf/datasource;1?name=addressdirectory
@mozilla.org/rdf/datasource;1?name=bookmarks
@mozilla.org/rdf/datasource;1?name=charset-menu
@mozilla.org/rdf/datasource;1?name=composite-datasource
@mozilla.org/rdf/datasource;1?name=files
@mozilla.org/rdf/datasource;1?name=history
@mozilla.org/rdf/datasource;1?name=httpindex
@mozilla.org/rdf/datasource;1?name=in-memory-datasource
@mozilla.org/rdf/datasource;1?name=internetsearch
@mozilla.org/rdf/datasource;1?name=ispdefaults
@mozilla.org/rdf/datasource;1?name=local-store
@mozilla.org/rdf/datasource;1?name=localsearch
@mozilla.org/rdf/datasource;1?name=mailnewsfolders
@mozilla.org/rdf/datasource;1?name=msgaccountmanager
@mozilla.org/rdf/datasource;1?name=msgfilters
@mozilla.org/rdf/datasource;1?name=msgnotifications
@mozilla.org/rdf/datasource;1?name=smtp
@mozilla.org/rdf/datasource;1?name=subscribe
@mozilla.org/rdf/datasource;1?name=window-mediator
@mozilla.org/rdf/datasource;1?name=xml-datasource
@mozilla.org/rdf/delegate-factory;1?key=filter&scheme=imap
@mozilla.org/rdf/delegate-factory;1?key=filter&scheme=mailbox
@mozilla.org/rdf/delegate-factory;1?key=filter&scheme=news
@mozilla.org/rdf/delegate-factory;1?key=smtpserver&scheme=smtp
@mozilla.org/rdf/rdf-service;1
@mozilla.org/rdf/resource-factory;1
@mozilla.org/rdf/resource-factory;1?name=abdirectory
@mozilla.org/rdf/resource-factory;1?name=abmdbcard
@mozilla.org/rdf/resource-factory;1?name=abmdbdirectory
@mozilla.org/rdf/resource-factory;1?name=imap
@mozilla.org/rdf/resource-factory;1?name=mailbox
@mozilla.org/rdf/resource-factory;1?name=news
@mozilla.org/rdf/xml-parser;1
@mozilla.org/rdf/xml-serializer;1

From this list, components used often in the Mozilla source code include bookmarks, history, mail and news folders, and address books.

Special URIs

Mozilla's built-in datasource components have special URIs for access. Here is the format used to determine the URI from the component reference:

Component:

@mozilla.org/rdf/datasource;1?name=SomeName

Datasource URI:

rdf:SomeName

The URI, such as rdf:someName, is also accessible as a datasource property:

foo-ds.URI

10.3.2. What Are RDF Interfaces?

RDF interfaces are interfaces in Mozilla designed to manipulate RDF structures and data. They typically deal with RDF generally, rather than specific sets of data (as in the case of components). A common use for an RDF interface in JavaScript, shown in Example 10-7, is to use nsIRDFService to retrieve or assert the root node of an RDF datasource.

Example 10-7. Creating a root node
// get the nsIRDFService interface and assign it to RDF
RDF = Components.classes['@mozilla.org/rdf/rdf-service;1'].
      getService(Components.interfaces.nsIRDFService);
// call the GetResource method from the interface
rootResource = RDF.GetResource('urn:root');

Like all Mozilla interfaces, RDF interfaces (shown in Table 10-3) are defined in IDL and can be accessed through XPCOM. The examples in this section use JavaScript and XPConnect to access the components for simplicity, but you can also use these interfaces with C++, as they are often in the actual Mozilla source code. Most interfaces deal with datasources, which drive the use of RDF in Mozilla.

Table 10-3. Mozilla's built-in RDF interfaces

RDF interface Description
nsIRDFService Mostly used for retrieving datasources, resources, and literals. It also registers and unregisters datasources and resources.
nsIRDFCompositeDataSource Allows the addition and removal of a datasource from a composite datasource (which may be empty).
nsIRDFDataSource, nsIRDFPurgeableDataSource, nsIRDFRemoteDataSource Mostly used for adding, removing, and changing triples in a datasource. It provides the means to change the graph.
nsIRDFNode, nsIRDFResource, nsIRDFLiteral Provide an equality function. Values for resources and literals can be retrieved. Objects of these types are retrieved from nsIRDFService.
nsIRDFContainer Provides vector-like access to an RDF container's elements.
nsIRDFContainerUtils Provides container creation and other container-related functions.
nsIRDFObserver Fires events when data is changed in a datasource.
nsIRDFXMLParser, nsIRDFXMLSerializer, nsIRDFXMLSink, nsIRDFXMLSource Used for working with RDF/XML. Functions are provided for parsing files and serializing content.

The sheer variety of RDF interfaces may seem overwhelming, but all interfaces serve different purposes and are often used in conjunction with one another. In your particular application space, you may find yourself using some subsets of these interfaces constantly and others not at all. This section describes some of the most commonly used functions. You can look up all of interfaces in their entirety at http://lxr.mozilla.org/seamonkey/source/rdf/base/idl/.

10.3.3. nsIRDFService

If you will do any sort of RDF processing, you need to use the nsIRDFService interface. It provides the basics for working with datasources, resources, and literals, and is useful when you process RDF data. nsIRDFService can be initialized by using the getService method of the rdf-service class:

RDF = Components.classes['@mozilla.org/rdf/rdf-service;1']
      getService(Components.interfaces.nsIRDFService);

Once the service is available, it's ready to go to work. Even though no datasource is created yet (in this particular example), the RDF service can still get resources and literals, as shown in the next section.

10.3.3.1. Getting a resource

Once a resource is created (e.g., with the identifier urn:root in Example 10-7), it needs to be added to a datasource:

rootResource = RDF.GetResource('urn:root');

When a resource is already registered under the given identifier (see Section 10.3.3.4, later in this chapter for more information about RDF registration), then GetResource returns that resource.

10.3.3.2. Getting an anonymous resource

Anonymous resources are resources with no resource identifier. Here is the creation of a new anonymous resource and a test of its anonymity:

anonResource = RDF.GetAnonymousResource( );
// This would be true. Checking is not necessary, just here for example.
isAnon = RDF.isAnonymousResource(anonResource);

Typically, these resources are turned into containers, as shown in the next section. Anonymous resources exist when names are not needed and a simple reference to that resource is all that is required.

10.3.3.3. Getting a literal

The GetLiteral function returns the given name in the format of a literal, which you can then use to assert into an RDF graph as a resource.

myName = RDF.GetLiteral('Eric');

Variations on this function are GetIntLiteral and GetDateLiteral.

10.3.3.4. Registering and unregistering datasources

If you create a Mozilla application that uses the same datasource or RDF resources in different ways, you may want to register the datasource with Mozilla. When you register a datasource, you register it as a component in Mozilla (see Section 8.1.6 for more information on Mozilla's component model), which means it can be accessed and used as easily as any other XPCOM component, and from anywhere in Mozilla.

To register a datasource, call the RegisterDatasource method of the RDF Service. In this example, the datasource already exists and is assigned to a variable named myDatasource:

RDF.RegisterDataSource(myDatasource, false);

In this case, myDatasource is the datasource name, and the false parameter specifies that this datasource is not replacing a datasource with the same name. Once a datasource is registered with the component manager in this way, it can be retrieved by name and associated with another instance:

secondDatasource = anotherRDF.GetDataSource("My Datasource");

To unregister a datasource from the RDF Service, pass the datasource into the UnRegisterDataSource function:

RDF.UnRegisterDataSource(myDatasource);

Once it's unregistered, a datasource is no longer available to other instances of the RDF Service. Registered resources work the same way as datasources in the RDF Service: if a resource is registered with the RDF Service, then it is available in every instance of RDF Service. To get two different instances of the same registered datasource and unregister its use:

newResource = RDF.GetResource('my.resource');
RDF.RegisterResource(newResource,false);
notNewResource = RDF.GetResource('my.resource');
RDF.UnRegisterResource(notNewResource);

If you register resources and datasources, be sure to use the overwrite Boolean variable on RegisterDataSource and RegisterResource to avoid overwriting existing datasources.

10.3.3.5. Getting a remote datasource

Finally, nsIRDFService provides a useful method that loads a datasource from a remote server, which is a process that occurs asynchronously. Compared to forthcoming discussions about datasource loading, GetDataSource is a real shortcut:

remoteDatasource = RDF.GetDataSource('http://books.mozdev.org/file.rdf');

Remember that RDF files requested in this way must be set with the text/rdf MIME type on the web server to load properly.

10.3.4. nsIRDFCompositeDataSource

When you work with multiple datasources, you can make things easier by grouping them, which nsIRDFCompositeDataSource allows you to do. This functionality aggregates data in a number of Mozilla's applications. To get this interface, invoke:

composite_datasource
   = '@mozilla.org/rdf/datasource;1?name=composite-datasource';
compDataSource = Components.classes[composite_datasource]
   getService(Components.interfaces.nsIRDFCompositeDataSource);

Once you have the interface, adding and removing datasources from the composite is easy. You can also enumerate the datasources by using the getNext method. Example 10-8 demonstrates how to add, remove, and cycle through datasources.

Example 10-8. Manipulating datasources
compDataSource.AddDataSource(datasource1);
compDataSource.AddDataSource(datasource2);
compDataSource.AddDataSource(datasource3);
compDataSource.RemoveDataSource(datasource1);
allDataSources = compDataSource.GetDataSources( );
datasource2 = allDataSources.getNext( );
datasource2.QueryInterface(Components.interfaces.nsIRDFDataSource);
datasource3 = allDataSources.getNext( );
datasource3.QueryInterface(Components.interfaces.nsIRDFDataSource);

In Example 10-8, allDataSources is an nsISimpleEnumerator returned by the GetDataSources method on the composite datasource. datasource1 is removed from the composite, and then the remaining datasources are cycled through. This step provides a way to iterate through a collection of datasources. nsIRDFCompositeDatasource also inherits the many functions of nsIRDFDataSource; refer to the section Section 10.3.5 for more information.

10.3.5. nsIRDFDataSource

The nsIRDFDataSource interface is large, with twenty functions and one attribute (URI), so it's one of the most common interfaces used to manipulate RDF data. nsIRDFDataSource contains all the components in Example 10-6 with “datasource” in their contract IDs, along with other common components:

@mozilla.org/browser/bookmarks-service;1
@mozilla.org/related-links-handler;1
@mozilla.org/browser/localsearch-service;1
@mozilla.org/registry-viewer;1
@mozilla.org/browser/global-history;1

The nsIRDFDataSource interface is meant to handle some of the core interaction with the datasource. APIs such as URI, GetTarget, Assert, and Change are helpful for working on the RDF graph itself. For example, the @mozilla.org/rdf/datasource;1?name=in-memory-datasource RDF component demonstrates the use of the nsIRDFDataSource interface. When this component is created, it's a blank datasource in memory, into which objects are inserted, changed, and removed. You can access the nsIRDFDataSource interface from the RDF component by first constructing an RDF graph in the in-memory datasource:

mem = '@mozilla.org/rdf/datasource;1?name=in-memory-datasource';
datasource = Components.classes[mem].
             createInstance(Components.interfaces.nsIRDFDataSource);

Of the twenty functions (found at http://lxr.mozilla.org/seamonkey/source/rdf/base/idl/nsIRDFDataSource.idl) in this interface, we show only a handful here:

  • Assertion and removal
  • Changing values
  • Moving triples
  • HasAssertion
  • GetTarget
  • GetSource

The main purpose of the nsIRDFDatasource interface is to work with RDF triples inside a datasource, allowing you to change that datasource's RDF graph.

10.3.5.1. Assertion and removal

Recall from the Section 10.1.1.2 section, earlier in this chapter, that triples are RDF statements in which the relationship between the subject, predicate, and object is more strictly defined. In the interface code, a triple's elements are all typically defined as resources rather than plain URIs, which means they can be asserted into a datasource in the particular sequence that makes them meaningful as parts of a triple:

rootSubject = RDF.GetResource('urn:root');
predicate = RDF.GetResource('http://books.mozdev.org/rdf#chapters');
object = RDF.GetResource('Chapter1');
datasource.Assert(rootSubject,predicate,object,true);

Once you assert the statement's elements into the datasource in this way, the datasource contains the triple. The truth value parameter in the last slot indicates that the given node is “locked” and thus cannot be overwritten.

Removing a triple from the datasource is as easy as adding it. If you try to remove a triple that doesn't exist, your request is ignored and no error messages are raised. To unassert a triple in the datasource, use:

rootSubject = RDF.GetResource('urn:root');
predicate = RDF.GetResource('http://books.mozdev.org/rdf#chapters');
object = RDF.GetResource('Chapter8');
datasource.Unassert(rootSubject,predicate,object);

10.3.5.2. Changing values

Changing values in a datasource is also very easy. Assert and change a literal in the datasource as follows:

subject = RDF.GetResource('Chapter1');
predicate = RDF.GetResource('http://books.mozdev.org/rdf#title');
object = RDF.GetLiteral('Mozilla as a Platform');
datasource.Assert(subject,predicate,object,true);
newObject = RDF.GetLiteral('Mozilla is a cool Platform!');
datasource.Change(subject,predicate,newObject,);

If working with triples seems hard in the template generation, their use in these examples -- where adding to and changing the parts is so easy -- may make things clearer.

10.3.5.3. Moving triples

Moving a triple in a datasource also requires some simple code. This example moves the asserted triple in the previous section:

newSubject = RDF.GetResource('Chapter99');
// Moving from Chapter1 to Chapter99
datasource.Move(subject,newSubject,predicate,object);

10.3.5.4. HasAssertion

This next example checks if the previous statement still exists in the datasource.

datasource.HasAssertion(newSubject,predicate,object,true);

This function is useful when you create new statements and resources and want to make sure you are not overwriting pre-existing resources.

10.3.5.5. GetTarget

The GetTarget method returns the resource's property value (i.e., the object). Given the RDF statement “(Eric) wrote (a book),” for example, the GetTarget method would input “Eric” and “wrote” and get back the object “a book.” Once again, the example code is based on the previous examples:

object = datasource.GetTarget(newSubject,predicate,true);
objects = datasource.GetTargets(rootSubject,predicate,true);
// objects is an nsIEnumeration of the object and its properties

In addition to GetTarget, as seen above, a GetTargets function returns an object and its properties in an enumeration. This function can be very handy for quick access to resources with fewer function calls.

10.3.5.6. GetSource

GetSource is the inverse of GetTarget. Whereas GetTarget returns an object, GetSource returns the subject attached to an object. Given the RDF statement “(Eric) wrote (a book)” again, in other words, the GetSource method would input “wrote” and “a book” and get back the statement subject “Eric.”

subject = datasource.GetSource(object,predicate,true);
subjects = datasource.GetSources(object,predicate,true);
// subjects is an nsIEnumeration of the subject and its properties

When you create RDF statements with assertions or work with in-memory datasources, it is often difficult to remember the shape of the graph, which statements exist about which resources, or which objects are attached to which subjects. These “getter” methods can help you verify the shape of your graph.

10.3.6. nsIRDFRemoteDataSource

The Section 10.3.3 section (earlier in this chapter) showed how to load a datasource from a remote server simply. If you want control over that datasource, you can manage it by using the nsIRDFRemoteDatasource to set up a remote datasource:

xml = '@mozilla.org/rdf/datasource;1?name=xml-datasource';
datasource = Components.classes[xml].
             createInstance(Components.interfaces.nsIRDFRemoteDataSource);
datasource.Init('http://books.mozdev.org/file.rdf');
datasource.Refresh(false);

In this example, the Init and Refresh methods control the datasource on the server. In addition to these methods, you can call the Flush method to flush the data that's been changed and reload, or you can check whether the datasource is loaded by using the loaded property:

if (datasource.loaded) {
  // Do something
}

Built-in datasources that implement nsIRDFRemoteDataSource (and other necessary interfaces) and do their own data handling include:

@mozilla.org/rdf/datasource;1?name=history
@mozilla.org/browser/bookmarks-service;1
@mozilla.org/autocompleteSession;1?type=history
@mozilla.org/browser/global-history;1
@mozilla.org/rdf/datasource;1?name=bookmarks

10.3.7. nsIRDFPurgeableDataSource

Using the nsIRDFPurgeableDatasource interface allows you to delete a whole section of an existing in-memory datasource in one fell swoop. This means that all relatives -- all statements derived from that node -- are removed. When you work with large in-memory datasources (such as email systems), the using interface can manipulate the data efficiently. The Sweep( ) method can delete a section that is marked in the datasource.

datasource.
   QueryInterface(Components.interfaces.nsIRDFPurgeableDataSource);
rootSubject = RDF.GetResource('urn:root');
predicate = RDF.GetResource('http://books.mozdev.org/rdf#chapters');
object = RDF.GetResource('Chapter1');
datasource.Mark(rootSubject,predicate,object,true);
datasource.Sweep( );

In this instance, a statement about a chapter in a book is marked and then removed from the datasource. You can also mark more than one node before sweeping.

10.3.8. nsIRDFNode, nsIRDFResource, and nsIRDFLiteral

These types of objects come from only a few different places. Here are all the functions that can return the resource of a literal:

nsIRDFService.GetResource
nsIRDFService.GetAnonymousResource
nsIRDFService.GetLiteral
nsIRDFDataSource.GetSource
nsIRDFDataSource.GetTarget

nsIRDFNode is the parent of nsIRDFResource and nsIRDFLiteral. It is not used often because it's sole function is to test equality:

isEqual = resource1.EqualsNode(resource2);

The other two interfaces inherit this function automatically. EqualsNode tests the equivalency of two resources, which can be useful when you try to put together different statements (e.g., “Eric wrote a book” and “[This] book is about XML”) and want to verify that a resource like “book” is the same in both cases.

10.3.8.1. nsIRDFResource

Like nsIRDFNode, nsIRDFResource is a minimalist interface. Here are the functions and the property available in a resource from the nsIRDFResource interface:

resource = RDF.GetAnonymousResource( );
// get the resource value, something like 'rdf:#$44RG7'
resourceIdentifierString = resource.Value;
// compare the resource to an identifier
isTrue = resourceEqualsString(resourceIdentifierString);
// Give the resource a real name.
resource.Init('Eric');

10.3.8.2. nsIRDFLiteral

A literal's value can be read but not written. To change the value of a literal, make a new literal and set it properly:

aValue = literal.Value;

Note that aValue could be a string or an integer in this case. The base type conversion, based on the data's format, is done automatically.

10.3.9. nsIRDFContainerUtils

This interface facilitates the creation of containers and provides other container-related functions. It provides functions that make and work with a sequence, bag, and alternative. (The functions work the same way for all types of containers, so only sequence is covered here.) To create an instance of nsIRDFContainerUtils, use the following:

containerUtils = Components.classes['@mozilla.org/rdf/container-utils;1'].
                                 getService(Components.interfaces.nsIRDFContainerUtils);

Once you create an anonymous resource, you can create a sequence from it. Then you can test the type of the container and see whether it's empty:

// create an anonymous resource
anonResource = RDF.GetAnonymousResource( );
// create a sequence from that resource
aSequence = containerUtils.MakeSeq(datasource,anonResource);
// test the resource
// (all of these are true)
isContainer = containerUtils.isContainer(datasource,anonResource);
isSequence = containerUtils.isSequence(datasource,anonResource);
isEmpty = containerUtils.isEmpty(datasource,anonResource);

Note that the sequence object is not passed into the functions performing the test in the previous example; the resource containing the sequence is passed in. Although aSequence and anonResource are basically the same resource, their data types are different. isContainer, isSequence, and isEmpty can be used more easily with other RDF functions when a resource is used as a parameter:

object = datasource.GetTarget(subject,predicate,true);
if(RDF.isAnonymousResource(object))
{
  isSeq = containerUtils.IsSeq(datasource,object);
}

The RDF container utilities also provide an indexing function. indexOf is useful for checking if an element exists in a container resource:

indexNumber =
   containerUtils.indexOf(datasource,object,RDF.GetLiteral('Eric'));
if(index != -1)
   alert('Eric exists in this container');

10.3.10. nsIRDFContainer

This interface provides vector-like access to an RDF container's elements.[1] The nsIRDFContainer interface allows you to add, look up, and remove elements from a container once you create it.

10.3.10.1. Adding an element to a container

You can add an element to a container in two ways. You can append it to the end of the list with Append or insert it at a specific place in the container:

newLiteral = RDF.GetLiteral('Ian');
aSequence.AppendElement(newLiteral);
// or
aSequence.InsertElementAt(newLiteral,3,true);

The second attribute in InsertElementAt is where the element should be placed. The third attribute specifies that the list can be reordered. This method is useful for working with ordered containers such as sequences. If this locking parameter is set to false and an element already exists at that location, then the existing element is overwritten.

10.3.10.2. Removing an element from a container

Removing an element from a container works much the same as adding one. The difference is that a reordering attribute is included on RemoveElement. If this attribute is set to false, you may have holes in the container, which can create problems when enumerating or indexing elements within.

newLiteral = RDF.GetLiteral('Ian');
aSequence.RemoveElement(newLiteral,true);
// or
aSequence.RemoveElementAt(newLiteral,3,true);

If you use the indexOf property of nsIRDFContainer, you can also use GetCount to learn how many elements are in the container. The count starts at 0 when the container is initialized:

numberOfElements = aSequence.GetCount( );

Once you have the sequence, the datasource and resource the sequence resides in can be retrieved. In effect, these properties look outward instead of toward the data:

seqDatasource = aSequence.DataSource;
seqResource = aSequence.Resource;

Like many methods in the RDF interfaces, this one allows you to traverse and retrieve any part of the RDF graph.

10.3.11. nsIRDFXML Interfaces

The RDF/XML interfaces are covered only briefly here. Besides being abstract and confusing, these interfaces require a lot of error handling to work correctly. Fortunately, a library on mozdev.org called JSLib handles RDF file access. The JSLib XML library does the dirty work in a friendly manner. See the section Section 10.5, later in this chapter, for more information.

10.3.11.1. nsIRDFXMLParser and nsIRDFXMLSink

nsIRDFXML is the raw RDF/XML parser of Mozilla. Used by Mozilla, its main purpose is to parse an RDF file asynchronously as a stream listener. Though this subject is beyond the scope of this book, the interface provides something interesting and useful. The parseString function allows you to feed nsIRDFXMLParser a string and have it parse that data as RDF and put it into a datasource, as Example 10-9 demonstrates.

Example 10-9. Parse an RDF/XML string into a datasource
RDF = Components.classes['@mozilla.org/rdf/rdf-service;1'].
        getService(Components.interfaces.nsIRDFService);
// Used to create a URI below
ios = Components.classes["@mozilla.org/network/io-service;1"].
      getService(Components.interfaces.nsIIOService);
xmlParser = '@mozilla.org/rdf/xml-parser;1';
parser = Components.classes[xmlParser].
         createInstance(Components.interfaces.nsIRDFXMLParser);
uri = ios.newURI("http://books.mozdev.org/rdf#", null);
// Entire RDF File stored in a string
rdfString =
  '<rdf:RDF xmlns:rdf=http://www.w3.org/1999/02/22-rdf-syntax-ns#' +
  'xmlns:b="http://books.mozdev.org/rdf#">' +
  '<rdf:Description about="urn:root">' + // Rest of file ...
parser.parseString(datasource,uri,rdfString);
// Parsed string data now resides in the datasource

The RDF/XML data that was in the string is a part of the datasource and ready for use (just like any other RDF data in a datasource). The uri acts as a base reference for the RDF in case of relative links.

nsIRDFXMLParser uses nsIRDFXMLSink for event handling. The interfaces are totally separate, but behind the scenes, they work together with the incoming data. Example 10-10 shows how a series of events is created in an object and then used to handle parser events.

Example 10-10. Setup nsIRDFXMLSink with event handlers
var Observer = {
   onBeginLoad: function(aSink)
   {
     alert("Beginning to load the RDF/XML...");
   },
   onInterrupt: function(aSink) {},
   onResume: function(aSink) {},
   onEndLoad: function(aSink)
   {
     doneLoading( ); // A function that does something with the datasource
   },
  onError: function(aSink, aStatus, aErrorMsg)
  {
    alert("Error: " + aErrorMsg);
  }
};

Once the event handlers are set up, you can use nsIRDFXMLSink:

sink = datasource.QueryInterface(Components.interfaces.nsIRDFXMLSink);
sink.addXMLSinkObserver(observer);

The events are then triggered automatically when the datasource is loaded up with data, allowing you to create handlers that manipulate the data as it appears.

10.3.11.2. nsIRDFXMLSerializer and nsIRDFXMLSource

These two interfaces are meant to work together. nsIRDFXMLSerializer lets you init a datasource into the xml-serializer module that outputs RDF. However, nsIRDFXMLSource actually contains the Serialize function. Here's how to serialize a datasource into an alert:

serializer = '@mozilla.org/rdf/xml-serializer;1';
s = Components.classes[serializer].
createInstance(Components.interfaces.nsIRDFXMLSerializer);
s.init(datasource);
output = new Object( );
output.write = new function(buf,count)
{
  alert(buf); // Show the serialized syntax
  return count;
}
   s.QueryInterface(Components.interfaces.nsIRDFXMLSource).Serialize(output);

As in the previous example with nsIRDFXMLParser, Example 10-10 does not use RDF data from a file. The serialized data is passed directly to an alert, which then displays the generated RDF.

Notes

10.4. Template Dynamics

Once you learn how to create templates and modify datasources, the ultimate in template mastery is to apply datasources to a template dynamically.

This process is done through the database property of a XUL element that contains a template. The object returned by this property has only two methods, AddDataSource and RemoveDataSource. A separate builder.rebuild function is also available for refreshing the template's display, but you probably won't need it once the template automatically updates itself. The addition and removal of a datasource to a <tree> template is demonstrated here:

tree = document.getElementById('tree-template');
tree.database.AddDataSource(someDatasource);
// tree will now update its display to show contents
tree.database.RemoveDataSource(someDatasource);
// tree will now be empty
// Optional, use only when tree is not updating for some reason
tree.builder.rebuild( );

You can add and remove any datasource as long as the template actually matches the data inside it. Also, multiple datasources can be applied to the same template with no problems, which allows you to aggregate data from different places, such as contact data, work information, and computer hardware information (e.g., "Eric uses a Compaq with the serial number 1223456-1091 to write his book and he sits on the fourth floor of the Acme Building, which is the Bay Area branch of Acme Enterprises.)

10.4.1. Template Dynamics in XBL

Putting templates inside XBL can be a useful organizational scheme. Here is a basic implementation of a widget that creates a list of people based on names listed in an attribute:

<people names="Brian King,Eric Murphy,Ian Oeschger,Pete Collins,David Boswell"/>

Obviously, the comma is used as the delimiter for this list. The constructor element in Example 10-11 uses JavaScript to break up this string.

Example 10-11. Binding with in-memory datasource and <listbox> template
<?xml version="1.0"?>
<bindings xmlns ="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <binding id="people">
    <implementation>
      <constructor>
      <![CDATA[
        // Read the Names into an Array
        names = document.getAnonymousNodes(this)[0].getAttribute('names');
        names = new String(names);
namesArray= names.split(',');
        // Initialize the RDF Service
        rdf = Components
             .classes['@mozilla.org/rdf/rdf-service;1']
             .getService(Components.interfaces.nsIRDFService);
        // Initialize a Datasource in Memory
             inMemory = '@mozilla.org/rdf/datasource;1?name=in-memory-datasource';
        datasource = Components.classes[inMemory].
           createInstance(Components.interfaces.nsIRDFDataSource);
        // Create the Root Node and an Anonymous Resource to Start With
        root   = rdf.GetResource('urn:root');
        people = rdf.GetAnonymousResource( );
        // Insert the People resource into the RDF graph
        datasource.Assert
          (root,
           rdf.GetResource('http://www.mozdev.org/rdf#people'),
           people,true);
        // Initialize Methods needed for Containers
        rdfc = Components
              .classes['@mozilla.org/rdf/container-utils;1']
              .getService(Components.interfaces.nsIRDFContainerUtils);
        // For the People resource, make a Sequence of people
        peopleSequence = rdfc.MakeSeq(datasource, people);
        for(i=0;i<namesArray.length;i++)
        {
          // Create a Person, with a Unique Number, for example
          person = rdf.GetResource(i);
          // Insert the Person's name into the RDF graph underneath number
          datasource.Assert
            (person,
             rdf.GetResource('http://www.mozdev.org/rdf#name'),
             rdf.GetLiteral(namesArray[i]),true);
          peopleSequence.AppendElement(person);
        }
        list = document.getAnonymousNodes(this)[1];
        list.database.AddDataSource(datasource);
      ]]>
      </constructor>
    </implementation>
    <content>
      <xul:box id="names" inherits="names" flex="0"/>
      <xul:listbox datasources="rdf:null" ref="urn:root" flex="1">
        <xul:template>
          <xul:rule>
            <xul:conditions>
              <xul:content uri="?uri"/>
              <xul:triple subject="?uri"
                       predicate="http://www.mozdev.org/rdf#people"
                         object="?people"/>
              <xul:member container="?people" child="?person"/>
              <xul:triple subject="?person"
                       predicate="http://www.mozdev.org/rdf#name"
                         object="?name"/>
            </xul:conditions>
            <xul:action>
              <xul:listitem uri="?person">
                <xul:listcell>
                  <xul:description value="?person "/>
                  <xul:description value="?name"/>
                </xul:listcell>
              </xul:listitem>
            </xul:action>
          </xul:rule>
        </xul:template>
</xul>
    </content>
  </binding>
</bindings>

In Example 10-11, everything you need to display a datasource dynamically is present. The only difference between this dynamically generated version and a static RDF-based template is the datasources="rdf:null", which specifies that the template does not refer to an actual datasource. Data that is edited, rearranged, or changed in a different way is often displayed dynamically in the UI with templates in this manner.

10.5. JSLib RDF Files

Working with actual RDF files is not easy. However, JSLib (http://jslib.mozdev.org) provides an RDF file library that can help you develop an RDF-based application. The library provides many types of error checking, as well as a friendly abstraction away from the RDF/XML interfaces of Mozilla (see Section 10.3.11, later in this chapter). Example 10-12 shows some common uses of the RDFFile class in JSLib. This functionality can be used in situations in which you have data in RDF that you want to pull out “manually” and use piece by piece (rather than as a whole datasource in a template).

Example 10-12. Creating and modifying an RDF file using JSLib
var rdfFileURL = 'chrome://jarfly/content/jar.rdf';
var gTreeBody = null;
var gListbox = null;
var gRDF = null;
function onload( )
{
  fileUtils = new FileUtils( );
  path = fileUtils.chrome_to_path(rdfFileURL);
  if(navigator.platform == "Win32") {
    path = path.replace(/\//g,"\\");
    // Only needed on Windows, until JSLib is fixed
  }
  gRDF = new RDFFile(path,'jar:flies','http://mozdev.org/fly-rdf#');
  gTreeBody = document.getElementById('tb');
  gTreeBody.database.AddDataSource(gRDF.dsource);
  gListbox  = document.getElementById('list');
  gListbox.database.AddDataSource(gRDF.dsource);
  rebuildLists( );
}
function rebuildLists( )
{
  gTreeBody.builder.rebuild( );
  gListbox.builder.rebuild( );
}
function update( )
{
  name      = document.getElementById('nameField').value;
  color     = document.getElementById('colorField').value;
  quantity  = document.getElementById('quantityField').value;
  seqNumber = -1;
  del       = false;
  replace   = false;
  if(document.getElementById('delete').checked)
    del = true;
  if(document.getElementById('replace').checked)
    replace = true;
  var seqLength = 0;
  if(gRDF.doesSeqExist('types'))
{
    seqLength = gRDF.getSeqSubNodes('types').length;
    //if(del)gRDF.removeSeq('types',false);
  }
  else
    gRDF.addSeq('types');
  for(i=0;i<seqLength;i++)
  {
    tempItem = 'types:_' + (i+1);
    if(gRDF.getAttribute(tempItem,'name')==name)
      seqNumber = gRDF.getAttribute(tempItem,'number');
  }
  if(seqNumber == -1)
  {
    item = 'types:_' + (seqLength+1);
    gRDF.setAttribute(item,'name',name);
    gRDF.setAttribute(item,'number',seqLength+1);
  }
  else
  {
    item = 'types:_' + seqNumber;
    gRDF.setAttribute(item,'number',seqNumber);
  }
  if(color!='')
    gRDF.setAttribute(item,'color',color);
  if(quantity!='')
  {
    gRDF.setAttribute(item,'quantity',quantity);
    gRDF.setAttribute(item,'dead',calcDead(quantity,replace));
  }
  if(!del)
    gRDF.addNode(item);
  else
    gRDF.removeNode(item);
  gRDF.flush( );
  onload( );
}
function calcDead(quantity,replace)
{
  if(!replace)
  {
    v = parseInt( (quantity * Math.random( )) * 0.13 );
    return (v.toString( ));
  }
  else
    return 0;
}
function changeC(color)
{
  document.getElementById('colorField').value=color;
}
function changeQ(quantity)
{
  document.getElementById('quantityField').value=quantity;
}

This example contains a datasource that represents a collection of flies. These flies are built up dynamically with JavaScript objects from the RDF library, which represent the datasource itself (gRDF = new RDFFile), methods that view and update the data (if(gRDF.getAttribute(tempItem,'name')==name), and utilities that make work with RDF files easier (path = fileUtils.chrome_to_path(rdfFileURL)).

Example 10-13 initializes and updates a file after it changes.

Example 10-13. Initialization
var rdfFileURL = 'chrome://jarfly/content/jar.rdf';
var gTreeBody = null;
var gListbox = null;
var gRDF = null;
function onload( )
{
  fileUtils = new FileUtils( );
  path = fileUtils.chrome_to_path(rdfFileURL);
  if(navigator.platform == "Win32") {
    path = path.replace(/\//g,"\\");
    // Only needed on Windows, until JSLib is fixed
  }
  gRDF = new RDFFile(path,'jar:flies','http://mozdev.org/fly-rdf#');

In Example 10-13, the file URL is set to an RDF file in the chrome area. Note that both a <tree> and a <listbox>, which display the same data in different ways, will be updated with the same datasource. The onload function is called after the main XUL document is loaded. A class called FileUtils is initialized, which will create a path to the RDF file. If the file doesn't already exist, JSLib automatically creates it.

Finally, the RDFFile is created by using the path and a root resource identifier, and the "xFly" namespace is used for the data references. Example 10-14 shows that the RDF file is ready to have its data added and deleted.

Example 10-14. Data updating
function update( )
{
  ...
  var seqLength = 0;
  if(gRDF.doesSeqExist('types'))
  {
    seqLength = gRDF.getSeqSubNodes('types').length;
  }
  else
    gRDF.addSeq('types');
  for(i=0;i<seqLength;i++)
  {
    tempItem = 'types:_' + (i+1);
    if(gRDF.getAttribute(tempItem,'name')==name)
      seqNumber = gRDF.getAttribute(tempItem,'number');
  }
  if(seqNumber == -1)
  {
    item = 'types:_' + (seqLength+1);
    gRDF.setAttribute(item,'name',name);
    gRDF.setAttribute(item,'number',seqLength+1);
  }
  else
  {
    item = 'types:_' + seqNumber;
    gRDF.setAttribute(item,'number',seqNumber);
  }
  if(color!='')
    gRDF.setAttribute(item,'color',color);
  if(quantity!='')
  {
    gRDF.setAttribute(item,'quantity',quantity);
    gRDF.setAttribute(item,'dead',calcDead(quantity,replace));
  }
if(!del)
    gRDF.addNode(item);
  else
    gRDF.removeNode(item);
  gRDF.flush( );
  onload( );

Example 10-14 contains a modified version of the update function. First, the function checks to see if a sequence called types is in the RDF file. If not, it creates one. Next, it appends an item to the sequence using type:_+(seqLength+1). The same type of container setup was described in the section Section 10.3.10, earlier in this chapter.

The update function then adds the color, quantity, and “dead” properties of that new item in the sequence. Next, it ensures that you actually want to add the item to the RDF file and flushes it out if not. It then recalls the onload function to update the template display.

These are the basics of using RDFFile. As you can see, using JSLib for RDF is often much easier than trying to implement a similar setup on your own. More information about RDFFile and the other JSLib libraries can be found at http://jslib.mozdev.org/.

10.6. Manifests

The package descriptions, generally called manifests, use RDF to describe new packages and files to Mozilla. They can be added seamlessly because RDF provides a platform-like environment that facilitates the installation and use of new Mozilla software.

All packages, including the ones that come preinstalled with Mozilla (such as the browser, the MailNews component, and the en-US language pack), have manifests describing them in terms of their relation to other packages. The manifests are typically files called contents.rdf, but they may also be called manifest.rdf. Example 10-15 presents a contents.rdf file that describes a new skin for Mozilla.

Example 10-15. Skin manifest
<?xml version="1.0"?>
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns:chrome="http://www.mozilla.org/rdf/chrome#">
<!-- List all the skins being supplied by this theme -->
<RDF:Seq about="urn:mozilla:skin:root">
  <RDF:li resource="urn:mozilla:skin:modern/1.0" />
</RDF:Seq>
<!-- Modern Information -->
<RDF:Description about="urn:mozilla:skin:modern/1.0"
  chrome:displayName="Modern"
  chrome:author="themes@mozilla.org"
  chrome:name="themes@mozilla.org/modern/1.0">
<chrome:packages>
  <RDF:Seq about="urn:mozilla:skin:modern/1.0:packages">
    <--RDF:li resource="urn:mozilla:skin:modern/1.0:aim"/ -->
    <RDF:li resource="urn:mozilla:skin:modern/1.0:communicator"/>
    <RDF:li resource="urn:mozilla:skin:modern/1.0:editor"/>
    <RDF:li resource="urn:mozilla:skin:modern/1.0:global"/>
    <RDF:li resource="urn:mozilla:skin:modern/1.0:messenger"/>
    <RDF:li resource="urn:mozilla:skin:modern/1.0:navigator"/>
  </RDF:Seq>
</chrome:packages>
</RDF:Description>
</RDF:RDF>

As you can see, the manifest is divided up into sections. After the preamble, where the XML processing instruction and the namespace declarations are made, an RDF sequence lists all the themes defined or supplemented (since you can create a package updated for only one Mozilla component, such as the browser) by this package. This section contains only one RDF:li -- the modern theme.

The next section gives more information on the theme, such as the author, the theme name, and a description. The chrome:packages structure that completes the manifest describes the packages to which this theme should be applied. All major components of the Netscape browser are listed in this example -- including the AIM client that is not a part of Mozilla -- but is skinned by themes such as Modern.

10.6.1. RDF and Dynamic Overlays

Manifests can also add new menu items to existing Mozilla menus. When you add a new package to Mozilla, you should make it accessible from within the browser application, where users can access it easily. This is where RDF and dynamic overlays come in.

The RDF you provide in your package makes it possible for the chrome registry, discussed in Chapter 6, to find, understand, and register your new files. Packages must be registered if they are to be skinned, localized, or accessed using the special tools Mozilla provides (e.g., the chrome URL or XPConnect to the XPCOM libraries). If you do not register your package by providing the necessary RDF manifests, it cannot be accessed except as a disparate collection of files in the browser's main content window, which is not what you want.

You can add overlays in Mozilla in two ways: import them explicitly by using an overlay processing instruction at the top of the XUL file into which items in the overlay file are to be “composed,” or use RDF to register and load overlay files at runtime. This latter method will be used here to add an “xFly” item to the Tools menu of the Mozilla suite of applications.

Example 10-16 shows the contents.rdf manifest format that alerts Mozilla of the presence of an overlay, its target in the Mozilla application, and the package of which it is a part.

Example 10-16. Overlay for a sample application menu
<?xml version="1.0"?>
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:chrome="http://www.mozilla.org/rdf/chrome#">
  <RDF:Seq about="urn:mozilla:package:root">
    <RDF:li resource="urn:mozilla:package:help"/>
  </RDF:Seq>
  <RDF:Description about="urn:mozilla:package:help"
        chrome:displayName="xFly Application"
        chrome:author="xfly.mozdev.org"
        chrome:name="xfly">
  </RDF:Description>
  <!-- Declare overlay points used in this package -->
  <RDF:Seq about="urn:mozilla:overlays">
    <RDF:li resource="chrome://communicator/content/tasksOverlay.xul" />
  </RDF:Seq>
  <RDF:Seq about="chrome://communicator/content/tasksOverlay.xul">
    <RDF:li>chrome://xfly/content/xflyOverlay.xul</RDF:li>
  </RDF:Seq>
</RDF:RDF>

The manifest in Example 10-16 names the file xflyOverlay.xul as an overlay. Then it names tasksOverlay.xul as the base file into which the contents are placed. In this case, the overlays can overlay other overlay files arbitrarily. An overlay can define new content anywhere in the application. Overlays are often responsible for putting new items in menus. As long as the target and overlay ids match, any two RDF datasources are merged. You can try this example by putting a single new menu item in an overlay structure like the one shown in Example 10-17. Save it as xflyOverlay.xul in the xfly content subdirectory and use the manifest information in Example 10-16 as part of the packaging process described in Chapter 6.

Example 10-17. Overlay for an xFly menu item in the browser
<?xml version="1.0"?>
<overlay id="xflyMenuID"
        xmlns:html="http://www.w3.org/1999/xhtml"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <menupopup id="tools_menu">
    <menuitem label="xfly xml editor"
        oncommand="toOpenWindowByType('mozilla:xfly, 'chrome://xfly/content/');" />

</menupopup>

</overlay>

The menupopup in Mozilla with the ID “tools_menu” gets a new menu item when this overlay is processed and its content included.

[1] A vector, for those who don't know, is a flexible and more accessible version of the array data structure.

Get Creating Applications with Mozilla now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.