RDFa is one of the best specifications ever recommended by the W3C. It offers a pragmatic approach to an incredible powerful but intimidating technology like RDF, and it makes it allowing people to build on their experience with previous technologies like microformats.

Last week, I have built hiccup-rdfaa very small Clojure library that makes easy to use RDFa in hiccup templates rendered by a Ring Clojure application, for example, a Compojure web app.

Let’s start with a very minimalistic Compojure application:

(ns hiccuprdfatest.core (:use [compojure core] [compojure response] [ring.adapter jetty] [hiccup.core] [hiccup.page-helpers])) (def *geeks* [{:name "Pablo" :nick "pablete" :tweet-home "http://twitter.com/pablete"} {:name "Mauro" :nick "malditogeek" :tweet-home "http://twitter.com/malditogeek"} {:name "Javier" :nick "jneira" :tweet-home "http://twitter.com/jneira"} {:name "Joel" :nick "joeerl" :tweet-home "http://twitter.com/joeerl"}]) (defn geeks-template ([geeks] (xhtml-tag :en [:head [:title "rdfa test"]] [:body [:h1 "Some geeks"] [:div {:id "geeks"} (map (fn [geek] [:div {:class "geek" :id (:nick geek)} [:p {:class "name"} (:name geek)] [:p {:class "nick"} (:nick geek)] [:a {:class "tweet-home" :href (:tweet-home geek)} (:tweet-home geek)]]) geeks)]]))) (defroutes rdfa-test (GET "/geeks" request (html (geeks-template *geeks*)))) (run-jetty (var rdfa-test) {:port 8081})

This application just exposes a URL showing a listing of people with some links. It uses hiccup to build the markup for the application. The markup used try to be an example of semantic markup. It exposes information about the semantics of the data rather than the visual rendering of the page.

Can we provide a better semantic markup for this trivial sample web app? We could think about adding some kind of microformat, but instead of that, I will show how to use RDFa to accomplish this task. Furthermore, using RDFa will open some interesting possibilities for our sample web application.

The first thing to do is to look for a vocabulary suitable for the description of the semantics of our data. A very good candidate is Friend of a Friend (FOAF) that includes terms for describing people and their relationships. After choosing the vocabulary, we will have to embed this vocabulary in the HTML of our application.

The following example shows how we can modify the previous web application to embed the FOAF semantics in the markup:

(use 'hiccup-rdfa.core) (use 'hiccup-rdfa.vocabularies) (register-vocabulary foaf) (defn rdfa-geeks-template ([geeks] (xhtml-rdfa-tag :en [:head [:title "rdfa test"]] [:body [:h1 "Some geeks"] [:div {:id "geeks"} (map (fn [geek] (foaf-Person (:tweet-home geek) {:id (:nick geek) :class "geek" :tag :div} (foaf-name (:name geek) {:class "name" :tag :p}) (foaf-nick (:nick geek) {:class "nick" :tag :p}) (link-foaf-homepage-to (:tweet-home geek) {:class "tweet-home" :tag :a} (:tweet-home geek)))) geeks)]]))) (defroutes rdfa-test (GET "/geeks" request (html (rdfa-geeks-template *geeks*))))

The rdfa-geeks-template is a variation of the geeks-template function using RDFa. Hiccup-rdfa needs first a description of a vocabulary, it includes already a FOAF vocabulary, so we can use it directly calling to the register-vocabulary function.

After calling to register-vocabulary , all the classes and properties (RDFS and OWL) are transformed into a collection of functions that can be used inside the hiccup template. In this example, as a side effect of the call to register-vocabulary with the FOAF vocabulary description, the functions foaf-Person , foaf-name , foaf-nick and link-foaf-homepage-to used in the template have been generated. HTML attributes can be passed as a parameter to the generated functions as a map. These maps can also include a :tag value with the kind of tag that will be used instead of span that is used by default. Functions link-[property]-to are also generated that can be used to generate links (by default) or anchors to the URL of the resource linked by a RDF property.

The expected output of this template can be seen in a browser:

but we can see how the semantics of the information contained in the page is explicitly stated. Using RDFa is an excellent way of achieving semantic markup.

The use of RDFa has another important implication. Thanks to RDFa a web page can be automatically parsed as data by a HTTP agent supporting RDF, turning a HTML web page into a real “web service”.

We could use a service like the W3C RDFa distiller to feed the source code of the page and extracts the RDF triples embedded into it:

@prefix foaf: . @prefix rdf: . @prefix rdfs: . @prefix xhv: . @prefix xml: . @prefix xsd: . a foaf:Person ; foaf:homepage "http://twitter.com/jneira"@en ; foaf:name "Javier"@en ; foaf:nick "jneira"@en . a foaf:Person ; foaf:homepage "http://twitter.com/joeerl"@en ; foaf:name "Joel"@en ; foaf:nick "joeerl"@en . a foaf:Person ; foaf:homepage "http://twitter.com/malditogeek"@en ; foaf:name "Mauro"@en ; foaf:nick "malditogeek"@en . a foaf:Person ; foaf:homepage "http://twitter.com/pablete"@en ; foaf:name "Pablo"@en ; foaf:nick "pablete"@en .

In the same way, we could use a RDF library, like Plaza for Clojure, to retrieve the data that is contained into the HTML page:

plaza.core=> (use 'plaza.rdf.core) nil plaza.core=> (use 'plaza.rdf.implementations.jena) nil plaza.core=> (init-jena-framework) nil plaza.core=> (def *geeks* (document-to-model "http://localhost:8081/geeks" :html)) #'plaza.core/*geeks* plaza.core=> (alter-root-rdf-ns "http://xmlns.com/foaf/0.1/") "http://xmlns.com/foaf/0.1/" plaza.core=> (use 'plaza.rdf.sparql) nil plaza.core=>(map (fn [[s p o]] {:uri (str s) :name (literal-value o)}) (flatten-1 (model-pattern-apply *geeks* [[?s :name ?o]]))) ({:uri "http://twitter.com/joeerl", :name "Joel"} {:uri "http://twitter.com/jneira", :name "Javier"} {:uri "http://twitter.com/malditogeek", :name "Mauro"} {:uri "http://twitter.com/pablete", :name "Pablo"})

If you find a vocabulary you want to use for the semantic description of your data, you can use it in hiccup-rdfa just importing the vocabulary from the URL with the OWL or RDFS description of the terms. For example, this function call will import the SIOC vocabulary:

(make-vocabulary-from-url "http://rdfs.org/sioc/ns" :xml "http://rdfs.org/sioc/ns#" "sioc")

You can add hiccup-rdfa to your Leiningen project, with the following clojars reference:

[hiccup-rdfa "1.0.0-SNAPSHOT"]