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.

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.

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.

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.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);

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.

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:

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.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.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.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.

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.

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.

Notes

[1]

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