RDF.rbと空白ノード



RDFデータをRubyで扱うためのライブラリとして，RDF.rbとそのプラグイン群がある．このライブラリで空白ノードを扱う際に注意する点について記載する．

## 結論

* RDF.rbで空白ノードを扱う場合，TurtleやRDF/XMLでシリアライズしない方がよい．
	* 同一のノードIDを与えたはずの空白ノードが，別々のノードとして扱われる
	* その際，ノードIDが正しく記述されない
* N-Triplesでのシリアライズには問題が無いので，一度N-Triplesでファイル出力してから他のツール（Raptorなど）でTurtleやRDF/XMLへ変換するのがよい．
* ファイル出力をせず，文字列から直接Raptorを使用したシリアライズをしたい場合は，rdf-raptorを用いるとよい
	* rdf-raptorをrdf-turtleやrdf-rdfxmlと同時にrequireした場合，先に読み込んだ方の処理系が優先される


## 空白ノードの表現

RDF.rbでは，以下のクラスによってRDFグラフのノードやエッジを表現する

* RDF::URI
	* URIを持つリソースやプロパティは，RDF::URIクラスのインスタンスとして表現する
* RDF::Node
	* URIを持たないリソースは，RDF::Nodeクラスのインスタンスとして表現する
* RDF::Literal
	* リテラルは，RDF::Literalクラスのインスタンスとして表現する

空白ノードは，上記のうちRDF::Nodeクラスのインスタンスとして表現する．Rubyのコードで記述すると以下のようになる．

```
require 'rdf'

# ノードIDが未定の場合
blank_node = RDF::Node.new

# ノードIDが決まっている場合（例: ノードID=1234）
blank_node = RDF::Node.new('1234')
```

ノードIDを指定して空白ノードを作成すると，```_:1234```のようなノードが作成される．ノードIDを指定しない場合，```_:g70161525762180```のようなノードが作成される．

空白ノードをRDF::Statementの主語や目的語にした場合は，以下のように出力される．

```
#<RDF::Statement:0x3ff686826614(<http://example.com/resource/1> <http://purl.org/dc/elements/1.1/creator> _:1234 .)>
```

空白ノードが```_:1234```となっているのが確認できる．


## グラフ中の空白ノード
RDF.rbでは，RDF::Graphクラスのインスタンスとして空のRDFグラフを作成できる．空のグラフに空白ノードを含むRDFトリプルを追加するコードは以下のようになる．

```
require 'rdf'
require 'rdf/vocab'

# 空のグラフの作成
graph = RDF::Graph.new

# 空白ノードを含むトリプルの作成
statement1 = [
  RDF::URI.new('http://example.com/resource/1'),
  RDF::Vocab::DC11.creator,
  RDF::Node.new('1234'),
]
statement2 = [
  RDF::Node.new('1234'),
  RDF::Vocab::FOAF.name,
  RDF::Literal.new('名無しさん'),
]

# トリプルをグラフに追加
graph << statement1
graph << statement2

# グラフの中身の確認
graph.each_statement do |statement|
  p statement
end
```

コードの実行結果は以下のようになる．

```
#<RDF::Statement:0x3fd53504c384(<http://example.com/resource/1> <http://purl.org/dc/elements/1.1/creator> _:1234 .)>
#<RDF::Statement:0x3fd535045c50(_:1234 <http://xmlns.com/foaf/0.1/name> "名無しさん" .)>
```

この出力結果から，```_:1234```という空白ノードを含む2つのトリプルがグラフに格納されていることがわかる．


## シリアライズ後の空白ノード

RDF.rbでは，RDF/XMLやTurtleに対応するためのプラグインとして以下の2種類が挙げられる

* Pure Rubyなプラグイン
	* rdf-turtle，rdf-rdfxmlなど
* Raptor (C言語で記述された外部ライブラリ) を用いるプラグイン
	* rdf-raptor

### rdf-turtleとrdf-rdfxmlを使用した場合

空白ノードを含むRDFグラフをN-Triples，Turtle，RDF/XMLで出力するコードが以下である．

```
require 'rdf'
require 'rdf/vocab'
require 'rdf/turtle'
require 'rdf/rdfxml'

graph = RDF::Graph.new
statement1 = [
  RDF::URI.new('http://example.com/resource/1'),
  RDF::Vocab::DC11.creator,
  RDF::Node.new('1234'),
]
statement2 = [
  RDF::Node.new('1234'),
  RDF::Vocab::FOAF.name,
  RDF::Literal.new('名無しさん'),
]
graph << statement1
graph << statement2

puts "\n\n==== N-Triples ====\n\n"
puts graph.dump(:ntriples)

puts "\n\n==== Turtle ====\n\n"
puts graph.dump(:turtle)

puts "\n\n==== RDF/XML ====\n\n"
puts graph.dump(:rdfxml)
```

このコードを実行すると，以下のように表示される．

```
==== N-Triples ====

<http://example.com/resource/1> <http://purl.org/dc/elements/1.1/creator> _:1234 .
_:1234 <http://xmlns.com/foaf/0.1/name> "名無しさん" .


==== Turtle ====


<http://example.com/resource/1> <http://purl.org/dc/elements/1.1/creator> [] .

 [ <http://xmlns.com/foaf/0.1/name> "名無しさん"] .


==== RDF/XML ====

<?xml version='1.0' encoding='utf-8' ?>
<rdf:RDF xmlns:ns0='http://purl.org/dc/elements/1.1/' xmlns:ns1='http://xmlns.com/foaf/0.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
  <rdf:Description rdf:about='http://example.com/resource/1'>
    <ns0:creator rdf:nodeID='1234' />
  </rdf:Description>
  <rdf:Description>
    <ns1:name>名無しさん</ns1:name>
  </rdf:Description>
</rdf:RDF>
```

N-Triplesは```_:1234```という空白ノードが正しく出力されているが，Turtleでは1つ目のトリプルの目的語と2つ目のトリプルの主語が別々の空白ノードとして扱われている．RDF/XMLも同様で，1つ目のトリプルの目的語は```rdf:nodeID='1234'```として正しく出力されているが，2つ目のトリプルにノードIDが与えられていない．


### rdf-raptorを使用した場合

空白ノードを含むRDFグラフをN-Triples，Turtle，RDF/XMLで出力するコードが以下である．

```
require 'rdf'
require 'rdf/vocab'
require 'rdf/raptor'

graph = RDF::Graph.new
statement1 = [
  RDF::URI.new('http://example.com/resource/1'),
  RDF::Vocab::DC11.creator,
  RDF::Node.new('1234'),
]
statement2 = [
  RDF::Node.new('1234'),
  RDF::Vocab::FOAF.name,
  RDF::Literal.new('名無しさん'),
]
graph << statement1
graph << statement2

puts "\n\n==== N-Triples ====\n\n"
puts graph.dump(:ntriples)

puts "\n\n==== Turtle ====\n\n"
puts graph.dump(:turtle)

puts "\n\n==== RDF/XML ====\n\n"
puts graph.dump(:rdfxml)
```

require以外は，rdf-turtleやrdf-rdfxmlを使用した場合と全く同じである．ちなみに，rdf-turtleやrdf-rdfxmlをrdf-raptorと一緒にrequireした場合は，先に読み込まれた方がシリアライズに使用される．

このコードを実行すると，以下のように表示される．
なお，事前にraptorのバージョン2.0.15をインストールしている．

```
==== N-Triples ====

<http://example.com/resource/1> <http://purl.org/dc/elements/1.1/creator> _:1234 .
_:1234 <http://xmlns.com/foaf/0.1/name> "\u540D\u7121\u3057\u3055\u3093" .


==== Turtle ====

@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

<http://example.com/resource/1>
    <http://purl.org/dc/elements/1.1/creator> [
        <http://xmlns.com/foaf/0.1/name> "名無しさん"
    ] .



==== RDF/XML ====

<?xml version="1.0" encoding="utf-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
  <rdf:Description rdf:about="http://example.com/resource/1">
    <ns0:creator xmlns:ns0="http://purl.org/dc/elements/1.1/" rdf:nodeID="1234"/>
  </rdf:Description>
  <rdf:Description rdf:nodeID="1234">
    <ns0:name xmlns:ns0="http://xmlns.com/foaf/0.1/">名無しさん</ns0:name>
  </rdf:Description>
</rdf:RDF>
```

rdf-turtleやrdf-rdfxmlを使用した場合とは異なり，同一の空白ノードが正しく処理されていることがわかる．

以上から，RDF.rbで空白ノードを含むTurtleファイルやRDF/XMLファイルを出力したい場合，Raptorをインストールできる環境ならばrdf-raptorを使うとよい．Raptorをインストールできないなら，一度N-Triplesで出力してから他のツールでTurtleやRDF/XMLに変換するのがよい．