python rdflibを使う

firefoxのaddonを書くとき、install.rdfを書くことになるのですが、そのチェックをrdflibでやってみることに。

addonのxpiに入れるinstall.rdfは以下のような感じです。

<?xml version="1.0"?>
<rdf:RDF
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:em="http://www.mozilla.org/2004/em-rdf#">
  <rdf:Description
     rdf:about="urn:mozilla:install-manifest"
     em:id="${guid}"
     em:name="${name}"
     em:version="${version}"
     em:description="${description}"
     em:creator="${creator}"
     em:homepageURL="${home}"
     em:updateURL="${home}update.rdf"
     >
    <em:targetApplication rdf:nodeID="firefoxInfo" />
  </rdf:Description>
  <rdf:Description
     rdf:nodeID="firefoxInfo"
     em:id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"
     em:minVersion="3.0"
     em:maxVersion="3.*"
     />
</rdf:RDF>

読み込み&検索


前記index.rdfを読み込んで、情報を取り出すコードです。

import rdflib

# load xml
graph = rdflib.ConjunctiveGraph()
graph.parse("install.rdf")

# query
em = rdflib.Namespace("http://www.mozilla.org/2004/em-rdf#")
creators = graph.triples((None, em["creator"], None))
for target, prop, value in creators:
    print unicode(target)
    print unicode(value)
    pass

# query: navigate
app = [v for r, p, v in 
       graph.triples((None, em.targetApplication, None))][0]
max = [v for r, p, v in 
       graph.triples((app, em.maxVersion, None))][0]
print unicode(max)

# query: sparql 
max = [max for max, in graph.query(
    """
    select ?max where {
      ?a em:targetApplication ?b . 
      ?b em:maxVersion ?max . 
    }""", 
    initNs={"em": em})][0]
print unicode(max)

RDFのデータは、単純にはresource, property, valueのトリプレット(プログラミング風に書けば、resource[property] = value)をカラムとしたデータベースになってます。

ユーザーが操作するのは基本、Graphオブジェクトだけです。

検索は、triplesメソッドで、このトリプレットタプル(resource, property, value)のうち探したい部分をNoneにしたタプルを引数で渡すことで、Noneでない部分が一致したトリプレットタプルを返すgeneratorを返します。

namespaceはdict風に使うことも、property風に使うことも可能です。ただし、namespaceオブジェクトはunicodeクラスのサブクラスでもあるため、文字列のメソッド名(index、count, formatなど)とかぶる場合はproperty風には使えません。

parseメソッドでsparqlというSQL風言語でも問い合わせ可能です。その場合は、関連を手繰るような条件付けもできます。

作成&表示

前記index.rdfと同じものを作成するコードです。

import rdflib
from rdflib import Literal as L

from rdflib import RDF
em = rdflib.Namespace("http://www.mozilla.org/2004/em-rdf#")

# build new graph
graph = rdflib.ConjunctiveGraph()
graph.namespace_manager.bind("em", em)

manifest = rdflib.URIRef("urn:mozilla:install-manifest")
graph.add((manifest, em.id, L("${guid}")))
graph.add((manifest, em.name, L("${name}")))
graph.add((manifest, em.version, L("${version}")))
graph.add((manifest, em.description, L("${description}")))
graph.add((manifest, em.creator, L("${creator}")))
graph.add((manifest, em.homepageURL, L("${home}")))
graph.add((manifest, em.updateURL, L("${home}update.rdf")))

firefox = rdflib.BNode("firefoxInfo")
graph.add((manifest, em.targetApplication, firefox))
graph.add((firefox, em.id, L("{ec8030f7-c20a-464f-9b0e-13a3a9e97384}")))
graph.add((firefox, em.minVersion, L("3.0")))
graph.add((firefox, em.maxVersion, L("3.*")))

# to xml
print graph.serialize()

ちなみに、namespace_managerでプレフィックス指定を追加しなかったり、BNodeでid名を省略したりすると、適当な文字列が割り振られます。

BNodeはそのままではDescriptionになります。Seqなどは、BNodeに対して、RDF.typeにRDF.Seqを指定することになるようです。

# make seq
seq = rdflib.BNode()
graph.add((seq, RDF.type, RDF.Seq))
rdf = rdflib.Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#")
graph.add((seq, rdf._1, item1))
graph.add((seq, rdf._2, item2))

# get li items in seq: it cannot find by g.triples((seq, RDF.li, None))
items = [item for item in graph.seq(seq)]

なぜ、RDF.liでないかは、以下の文書を読めばわかるでしょう。


一旦RDFを手書きし、parseしてserializeしてみると、どういう構造かわかるかもしれません。