Carp / IntroductionToCarp
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 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, 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 );
}
}
}
