James and I are pleased to announce the first public release of RdfLib and Carp, which are .NET/Mono libraries for fetching, parsing, munging and writing RDF. I first mentioned the existence of these in my talk at FOAF Galway and both are used to run our FriendSpace experiment.
Both libraries are released under a liberal, attribution-only, open source license. We'd love it if you wanted to use either or both in your applications. If you do use them, please let us know - we're keen to help as much as possible.
SemPlan.RdfLib (binaries /
source / docs) provides foundation RDF services for other applications such as parsing and writing RDF. The SemPlan.RdfLib.Core namespace contains interfaces defining fundamental RDF concepts such as UriRef, BlankNode etc. It also contains a Parser interface with a number of associated Factory interfaces to get hold of parsers, resources and statements.
We ship three parsers: XsltParser which is based off of James’ XSL stylesheet RDF parser; DriveParser which is a wrapper around the pure .NET Drive Parser and ICalParser which wraps SemaView's iCal parser. All of these implement the same interface and so are completely interchangeable. It should be very little effort to produce other RDF parser bindings.
The SemPlan.RdfLib.Utility namespace contains some handy implementations of the RdfWriter interface (RdfXmlWriter and NTripleWriter), plus a SimpleModel and SimpleDereferencer, both of which are suitable for quick hacking.
SemPlan.Carp (binaries /
source / docs) uses RdfLib and conceptually sits in a layer above it. Carp stands for Convenient API for RDF Programing and is designed to provide a simple API for programming with RDF without losing the power of the underlying model.
The heart of Carp is the KnowledgeBase. You can include RDF from abitrary locations very simply:
KnowledgeBase knowledge = new KnowledgeBase();
knowledge.include("http://www.semanticplanet.com/index.rdf");
knowledge.include( new StreamReader("./bloggers.rdf"), "");
By default, a KnowledgeBase uses the XsltParser but you can supply any ParserFactory in its constructor to customise that behaviour. In Carp, one of the design principles is to make simple things really simple and hard things possible so most constructors and quite a few methods offer overloaded versions with sensible defaults.
The KnowledgeBase maintains a ResourceDescription for every node in the input documents. A ResourceDescription is a set of properties and values attached to a uriref or a blank node. Each value is a further ResourceDescription with its own properties and values. You can ask any KnowledgeBase for a description of a node like this:
ResourceDescription description = knowledge.getDescriptionOf("http://www.semanticplanet.com/");
Or, you can iterate through all the descriptions:
foreach( ResourceDescription description in knowledge) {
Console.WriteLine( description.getAboutLabel() );
}
The KnowledgeBase also offers some basic, but useful, querying methods such as findByPropertyValue and findByType. You can also write out the KnowledgeBase as RDF by passing an RdfWriter to its write method. Here's a FOAF aggregator:
KnowledgeBase knowledge = new KnowledgeBase();
knowledge.include("http://iandavis.com/foaf.rdf");
knowledge.include("http://www.takepart.com/about/foaf.rdf");
RdfXmlWriter writer = new RdfXmlWriter( new XmlTextWriter( Console.Out ) );
knowledge.write( writer );
The ResourceDescription class is probably the next most important after KnowledgeBase. Once you've got hold of one you can get the values of its properties using the [ ] indexer notation. Property values are always returned as a ResourceDescriptionList which you can enumerate with foreach, index into with [ ] notation or just use the first() method to get the first value. For example:
ResourceDescriptionList topics = doc["http://xmlns.com/foaf/0.1/topic"];
foreach( ResourceDescription topic in topics ) {
Console.WriteLine( topic.getAbout().getLabel() );
}
or, more succinctly,
foreach( ResourceDescription topic in doc["http://xmlns.com/foaf/0.1/topic"] ) {
Console.WriteLine( topic );
}
Calling WriteLine with a ResourceDescription argument like above invokes ToString on the ResourceDescription. This, in turn, returns the label of the node the ResourceDescription is about. The label is either the uri of a uriref, a generated blank node label, or the string value of any literal.
Setting properties is as easy as getting them for a ResourceDescription:
ResourceDescription person = new ResourceDescription();
person["http://xmlns.com/foaf/0.1/name"] += "Ian Davis";
person["http://xmlns.com/foaf/0.1/interest"] += new ResourceDescription( new UriRef("http://example.com"));
Note that in the spirit of RDF we're adding values to a list of values for each of the properties. Copying from one ResourceDescription to another is easy too:
ian["http://xmlns.com/foaf/0.1/interest"] += james["http://xmlns.com/foaf/0.1/interest"];
There's a lot more to core Carp than just these two classes such as investigators that encapsulate algorithms for discovering more RDF about resources, a caching dereferencer with etag and if-modified-since support, and an early implementation of RDF Schema inference rules. However, the I want to finish by describing the SemPlan.Carp.Vocabularies namespace. This, as you might guess, contains classes for using common RDF vocabularies. Carp comes with FOAF, RSS 1.0, RDFS and a partial RdfCal implementation. If you want more, there's an example application in the source distribution called VocabGenerator that can generate C# classes compatible with Carp from RDF Schemas.
To use a Carp vocabulary class, just add using SemPlan.RdfLib.Vocabularies; to your class. Then you can address any FOAF property by its name:
ResourceDescription person = new ResourceDescription();
person[ Foaf.name ] += "Ian Davis";
person[ Foaf.interest ] += new ResourceDescription( new UriRef("http://example.com"));
The vocabulary classes also provide a typed form of ResourceDescription for each class defined in the schema. Each typed ResourceDescription has shortcut properties inferred from the schema that make it even easier to manipulate RDF data:
foreach( Foaf.Person person in james.knows) ) {
Console.WriteLine( person.name + " (" + person.mbox + ")" );
}
Typed ResourceDescription have their own collections, addressed as Agent.Collection and contain implicit casts to and from ordinary ResourceDescriptions and ResourceDescriptionLists. Creating a new typed ResourceDescription creates a pattern which you can use to query the knowledge base for all nodes of that type.
Foaf.Agent pattern = new Foaf.Agent();
foreach (Foaf.Agent agent in pattern.findAllMatching( knowledge ) ) {
... do something with agent
}
I'll leave you for now with one of examples, an RSS aggregator that uses the same format of blogroll as Planet RDF:
KnowledgeBase bloggers = new KnowledgeBase(parserFactory);
bloggers.include(new StreamReader("./bloggers.rdf"), "");
Foaf.Agent pattern = new Foaf.Agent();
foreach (Foaf.Agent agent in pattern.findAllMatching( bloggers ) ) {
Console.WriteLine( agent[Foaf.name] );
foreach (Foaf.Document doc in agent.weblog) {
Console.WriteLine( "Weblog: " + doc );
KnowledgeBase rss = new KnowledgeBase();
rss.investigate( doc , new SeeAlsoPropertyInvestigator());
Rss.Item itemPattern = new Rss.Item();
foreach (Rss.Item item in itemPattern.findAllMatching( rss ) ) {
Console.WriteLine( " - " + item.title );
Console.WriteLine( " " + item.description );
}
}
}
Will you be fixing the broken links to the binaries, etc? You have one of the few RDF library implementations for C#, so it'd be nice to have these updated.
Comment by Bruce Schalau — 17 May 2005 @ 2:49 pm
Whoops, they were left behind when I moved to a different hosting provider yesterday! The links should be fixed now.
Comment by Ian Davis — 17 May 2005 @ 3:01 pm
Could you Update he binaries? because the binaries doesnt contain the MySql classes.
Thanks
Comment by Luiz Silva — 23 May 2005 @ 1:49 pm