RDF Containers

This section will describe how to query and manipulate RDF containers.

RDF Container Interfaces

Since RDF containers, that is, the Seq, Bag and Alt types, are often manipulated, Mozilla provides some additional methods to handle these types. These methods are handled by two interfaces, nsIRDFContainer and nsIRDFContainerUtils. It is important to note that these interfaces are only convenience methods that wrap the datasource methods already explained in previous sections. It is possible to perform all these operations without using the container classes at all. This might be useful to do some more specific things with containers. For most purposes, however, the containers provide a handy way to manipulate RDF containers. That RDF containers are just wrappers around the datasource methods mean that all datasources support containers, although not all datasources will use them for anything. Any RDF observers attached to the datasource will receive notifications about the underlying changes that the container makes.

The nsIRDFContainer interface is used to hold an RDF container. You can use this interface to query, add and remove the children of the container. This interface is useful since the indexing of the children is handled for you. To create one, use the following code:

var container = Components.classes["@mozilla.org/rdf/container;1"].
                  createInstance(Components.interfaces.nsIRDFContainer);

The code above will create an unitialized RDF container. Initializing it is described below.

The RDF container (nsIRDFContainer) component should be created as an instance with createInstance, not as a service. There are a number of sources and examples that incorrectly use getService instead.

The nsIRDFContainerUtils interface has some convenient utility methods for creating containers and checking to see whether a resource is a container or not. This object is a service, so it should be created with getService.

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

Querying a Container

There are two ways to initialize an RDF container object. First, is to call the Init method of the nsIRDFContainer interface. This method takes a resource and initializes the container using that resource. In this case, the resource needs to already be a container. If a resource is not a container, the Init method will throw an exception.

var folderRes = rdfService.GetResource("http://www.example.com/folder/simonesbirthday");
var container = Components.classes["@mozilla.org/rdf/container;1"].
                  createInstance(Components.interfaces.nsIRDFContainer);
try {
  container.Init(photosDS, folderRes);
}
catch (ex){}

In this example, the container is initialized to a given resource. The two arguments to the Init method are the datasource and the resource respectively. We need to wrap the call in a try-catch block in case the resource isn't a RDF container. If you are sure that it will be, you don't need to perform this check.

The second way to initialize an RDF container object is to create a new one. This method will turn an existing resource into a container. You should use this method when creating new containers. This involves the use of three methods in the nsIRDFContainerUtils interface, MakeSeq, MakeBag, and MakeAlt. Which one to use depends on which type of container you want to create. For example, the MakeSeq method will make a resource into a Seq. Remember, that RDF containers are just wrappers around other datasource methods. It's possible to use datasource methods to make a resource into an RDF container.

var folderRes = rdfService.GetResource("http://www.example.com/folder/simonesbirthday");
var rdfContainerUtils = Components.classes["@mozilla.org/rdf/container-utils;1"].
                          createInstance(Components.interfaces.nsIRDFContainerUtils);
var container = rdfContainerUtils.MakeSeq(photosDS, folderRes);

The MakeSeq method takes the datasource and resource as arguments as with the RDF container's Init method. This method returns a new container object already initialized to the proper values. If the resource is already a container, the three Make methods just return the existing container. They don't recreate it nor switch from one type of container to another. This means that it's possible to create and get existing containers using only the Make methods.

You can check if a resource is a container or not by using the Is methods of the nsIRDFContainerUtils interface. Specifically, IsSeq checks if a resource is a Seq, IsBag checks if a resource is a Bag, IsAlt checks if a resource is a Alt, and IsContainer checks if a resource is any kind of container. All four methods return true or false.

You can find out what resources are children of a container by using the GetElements method of a container. Like other RDF query methods, it returns an enumeration which can be used to iterate over the children in the container. They will be returned in order, although for a Bag, this order isn't intended to be significant.

var ratingProp = rdfService.GetResource("http://www.example.com/rdfns/rating");
var threeProp = rdfService.GetLiteral("3");
var children = container.GetElements();
while (children.hasMoreElements()){
  var child = children.getNext();
  if (child instanceof Components.interfaces.nsIRDFResource){
    photosDS.Assert(child, ratingProp, threeProp, true);
  }
}

This code iterates through all of the children of some container. For each child, it adds a triple setting its rating to 3.

An additional method of a container is GetCount, which may be used to get the number of children in the container without having to iterate over a container. Actually, this isn't quite true. It actually returns the index of the last child in the container. Remember that not all indices need to be used in a container and some may be used multiple times. If you just want to check if a container has any children, use the IsEmpty method of the nsIRDFContainerUtils interface. This method will return true or false.

You may wish to retrieve a specific child of a container, identified by its index. Recall from the section of the RDF model that RDF containers reference children using triples with predicates like _1, _2, and so on. This makes it fairly easy to retrieve a specific child just by using GetTarget without using the container classes. In fact, the container classes don't contain a method such as GetChild to retrieve children.

var kidsRes = rdfService.GetResource("http://www.xulplanet.com/rdf/people/KarensKids");
var twoRes = rdfService.GetResource("http://www.w3.org/1999/02/22-rdf-syntax-ns#_2");
var child = datasource.GetTarget(kidsRes, twoRes, true);

The example above may be used to retrieve the second child of a container. Using the Karen example from previous sections, it will return Karen's second child. This technique isn't any different from retrieving any other property of a resource. The nsIRDFContainerUtils interface does provide us a convenience method for creating indexing resources though in the form of the IndexToOrdinalResource method. For example, we could retrieve the 'two' resource using the following instead:

var twoRes = rdfContainerUtils.IndexToOrdinalResource(2);

This may make the code more readable. There is also an similar OrdinalResourceToIndex method for going the other way and retrieving the integer index from a resource. Naturally this method will fail on non-ordinal resources. You can check if a resource is an ordinal resource with the IsOrdinalProperty method. Note that indices in the RDF API always begin with one, not zero.

You can determine the index of a child within a container by using the container's IndexOf method. This method will return the integer index of the child within the container. If the child isn't in the container, the method returns -1. This means you can also use this method to check if a child exists in the parent. There is similar method indexOf method in the nsIRDFContainerUtils interface which does the same thing except you don't need to create an RDF container object first. Note the difference in case between the two forms. The following example determines the position of Sandra within Karen's list of children.

var kidsRes = rdfService.GetResource("http://www.xulplanet.com/rdf/people/KarensKids");
var sandraRes = rdfService.GetResource("http://www.xulplanet.com/rdf/people/Sandra");
var idx = rdfContainerUtils.indexOf(datasource,kidsRes,sandraRes);

You may wish to determine what the parent of a child is, or determine the container which a resource is inside. The RDF container classes don't provide a method to do this. One possible way to determine the container for a child is as follows:

var rdfContainerUtils = Components.classes["@mozilla.org/rdf/container-utils;1"].
                          getService(Components.interfaces.nsIRDFContainerUtils);
var sandraRes = rdfService.GetResource("http://www.xulplanet.com/rdf/people/Sandra");
var parent = null;
var arcsIn = datasource.ArcLabelsIn(sandraRes);
while (arcsIn.hasMoreElements()){
  var arc = arcsIn.getNext();
  if (arc instanceof Components.interfaces.nsIRDFResource){
    if (rdfContainerUtils.IsOrdinalProperty(arc)){
      parent = datasource.GetSource(arc, sandraRes, true);
      break;
    }
  }
}

This code iterates over all of the predicates (arcs) pointing into the 'Sandra' resource. This list of predicates may include a number of things. If the predicate is an ordinal resource, however, we know that it is a child of some other resource. We can use the GetSource method to determine the parent. Remember that it is possible for a resource to be in several containers at once. This example assumes that the child will only have one parent. If you want to find all of the parents, you will need to use GetSources instead and build up a list.

Modifying a Container

Adding and removing children from a container is simple. There are several methods of the nsIRDFContainer interface which can be used to add and remove children.

To add a child to a container, use the AppendElement method. This will append the child to the end of the list of children of the parent. The ordinal index will be one greater than the current highest ordinal, so you don't have to calculate it yourself. This method takes one argument, the child to add. Here is an example:

var kidsRes = rdfService.GetResource("http://www.xulplanet.com/rdf/people/KarensKids");
var christaRes = rdfService.GetResource("http://www.xulplanet.com/rdf/people/Christa");
var container = Components.classes["@mozilla.org/rdf/container;1"].
                  createInstance(Components.interfaces.nsIRDFContainer);
try {
  container.Init(datasource, kidsRes);
  container.AppendElement(christaRes);
}
catch (ex){}

The RDF container modification methods do not check to see whether the child is already in the container or not. That means that you can add a child multiple times. The InsertElementAt method may be used to insert a child at a specific index.

container.InsertElementAt(christaRes,2,true);

This method takes three arguments. The first argument is the child resource to add. The second is the integer position to place the child. The third argument indicates whether to renumber the indices of the other children to accommodate for the new child. Recall that the indices are just predicate resources with a numbering convention and that there may be several children with the same index. If you pass true for the third argument, the remaining children in the list will be renumbered to accommodate the new child. In the example above, the new child will be added at the second position. The child already at the second position will have its index adjusted to three, the third child will be moved to the fourth position, and so forth. If this last argument is false, the indices are not renumbered. This would mean that there would be two children at the second index in the example above, assuming that one existed already.

The renumbering process is able to retain the case where several of the indices being renumbered already have several children with that index. For instance, if there were three child resources at index 3, all three would be moved to index 4. Those at index 4 would be moved to index 5, and so forth. If the datasource implements the nsIRDFPropagatableDataSource interface, the change notifications are disabled while the renumbering is performed so as to avoid lots of spurious notifications. Only the assertion caused by the resource being inserted will be sent to the observers.

To remove a child, use either the RemoveElement or RemoveElementAt methods. The former will remove a child given its resource, while the latter will remove a child given its index.

container.RemoveElement(christaRes,true);
container.RemoveElementAt(2,true);

Assuming that the resource was inserted as in the earlier example, both of these statements will do the same thing. The first removes the child given its resource. This method will determine the index itself. The second statement will remove an element at a specific index. Unlike the RemoveElement method, the RemoveElementAt method will return the removed element as an RDF node. If there are multiple children at the index, the RemoveElementAt method will only remove one of them.

Both remove methods also take a second argument which indicates whether to renumber the other children after removing the child, and this works similarly to the InsertElementAt method.

Add a note User Contributed Notes
March 11, 2005, 7:07 pm hamoth at hotmail dot com
It should be noted in the example above, that using the "hasMoreElements" enumerator returns a static figure that is not updated if the actions in your loop alter the number of children that a given container has.

For eaxmple:

var i=0;
var children = container.GetElements();
while (children.hasMoreElements()){
var child = children.getNext();
if (child instanceof Components.interfaces.nsIRDFResource){
container.RemoveElement( child , true )
}
i++; // use this to count number of times this runs.
}

The example above will only run half the number of expected times. SO if a container has 4 children, "var i" will equal 2. When destroying child elements with the hasMoreElements function, be sure to use the following instead:


var i=0;
var children = container.GetElements();
while (children.hasMoreElements()){
var child = children.getNext();
if (child instanceof Components.interfaces.nsIRDFResource){
container.RemoveElement( child , false)
}
i++; // use this to count number of times this runs.
}

Supplying a "false" value of the truth argument in the RemoveElement command ensures that the data does not reorder itself while you are changing it. In the latter example, "var i" will come out at exactly the count of your child elements prior to your deleting them.

Copyright © 1999 - 2005 XULPlanet.com