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.
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.
From this list, components used often in the Mozilla source code include bookmarks, history, mail and news folders, and address books.
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.
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/.
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.
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.
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.
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.
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);
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');
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.
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.
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);
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.
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);
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.
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.
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.
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
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.
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.
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');
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.
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');
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.
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.
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.
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.
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.
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.
[1] | A vector, for those who don't know, is a flexible and more accessible version of the array data structure. |