Chapter 10. RDF, RDF Tools, and the Content Model-P4
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.nsIRDFRemoteDa
taSource);
datasource.Init(' />;
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.nsIRDFPurgeabl
eDataSource);
rootSubject = RDF.GetResource('urn:root');
predicate =
RDF.GetResource(' />rs');
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.nsIRDFContainerUti
ls);
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.GetLit
eral('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.nsIRDFXMLParse
r);
uri = ios.newURI("
null);
// Entire RDF File stored in a string
rdfString =
'<rdf:RDF xmlns:rdf= />rdf-syntax-ns#' +
'xmlns:b=" +
'<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.nsI
RDFXMLSink);
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.nsIRDFXMLSeria
lizer);
s.init(datasource);
output = new Object( );
output.write = new function(buf,count)
{
alert(buf); // Show the serialized syntax
return count;
}
s.QueryInterface(Components.interfaces.nsIRDFXMLSou
rce).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
[1]
A vector, for those who don't know, is a flexible and more accessible
version of the array data structure.
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 ="
xmlns:xul=" />per/there.is.only.xul">
<binding id="people">
<implementation>
<constructor>
<![CDATA[
// Read the Names into an Array
names =
document.getAnonymousNodes(this)[0].getAttribute('n
ames');
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.nsIRDFDataSour
ce);
// 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(' />,
people,true);
// Initialize Methods needed for Containers
rdfc = Components
.classes['@mozilla.org/rdf/container-
utils;1']
.getService(Components.interfaces.nsIRDFContainerUt
ils);
// 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('
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="
object="?people"/>
<xul:member container="?people"
child="?person"/>
<xul:triple subject="?person"
predicate="
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>