このプログラムの挙動はGoogle Chrome 59.0.3071.86で確認を行いました。
例として、odpに登録されている、バス停データを用います。
今回使用するクエリは以下のとおりです。
PREFIX jrrk: <http://purl.org/jrrk#> construct { ?busstop ?p ?o. ?o ?q ?r. } where { { select ?busstop { ?busstop a jrrk:BusStop. } limit 10 } ?busstop ?p ?o. optional { ?o ?q ?r. filter(isBlank(?o)). } }
取得に、Fetch APIを利用します。Fetch APIの詳細については、MDNによるFetch APIの解説をご覧ください。
まず、SPARQLクエリを準備します。
const sparql = ` PREFIX jrrk: <http://purl.org/jrrk#> construct { ?busstop ?p ?o. ?o ?q ?r. } where { { select ?busstop { ?busstop a jrrk:BusStop. } limit 10 } ?busstop ?p ?o. optional { ?o ?q ?r. filter(isBlank(?o)). } } `;
次に、Fetch API用の設定を追加します。
const call_sparql_config = { method: "GET", headers: { "Accept": "application/ld+json" }, mode: "cors", cache: "default", };
この時、headersにAccept: application/ld+json
を指定していることに注意してください。
JSON形式でRDFを表現する JSON-LD で取得を行うには、Acceptヘッダにこのように指定をする必要があります。
以下のコードで、通信を行います。
const odp_endpoint = "https://sparql.odp.jig.jp/data/sparql"; document.addEventListener("DOMContentLoaded", function(e){ main(); }); function main(){ let params = new URLSearchParams(); params.append("query", sparql); fetch(odp_endpoint+"?"+params.toString(), call_sparql_config).then(function(response){ return response.json(); }).then( json => console.log(json)); }
URLSearchParams
は、クエリストリングを生成するAPIです。詳細はMDNの解説ページをご覧ください。
上記のmain
関数は、odpエンドポイントから、サンプルクエリを発行し、結果をコンソールにJSON形式で受信するものです。バス停を1件だけ取得するようにした結果は以下の通りです。
{ "@graph" : [ { "@id" : "_:b0", "identifier" : "2", "label" : { "@language" : "ja", "@value" : "京福バス池田線 下り" } }, { "@id" : "_:b1", "表記" : { "@language" : "ja", "@value" : "稲荷" }, "ic:表記" : { "@language" : "ja", "@value" : "稲荷" } }, { "@id" : "http://odp.jig.jp/rdf/jp/fukui/imadate/ikeda/741#2/%E4%BA%AC%E7%A6%8F%E3%83%90%E3%82%B9%E6%B1%A0%E7%94%B0%E7%B7%9A%E3%80%80%E4%B8%8B%E3%82%8A/%E7%A8%B2%E8%8D%B7/35.885572/136.343482/", "@type" : "jrrk:BusStop", "名称" : "_:b1", "地理座標" : "http://odp.jig.jp/res/geopoint/+35.885571/+136.343475/", "ic:名称" : { "@id" : "_:b1" }, "ic:地理座標" : { "@id" : "http://odp.jig.jp/res/geopoint/+35.885571/+136.343475/" }, "http://odp.jig.jp/odp/1.0#hasRoute" : { "@id" : "_:b0" }, "hasRoute" : "_:b0", "label" : { "@language" : "ja", "@value" : "稲荷" }, "lat" : "35.885572", "long" : "136.343482" } ], "@context" : { "hasRoute" : { "@id" : "http://purl.org/jrrk#hasRoute", "@type" : "@id" }, "地理座標" : { "@id" : "http://imi.go.jp/ns/core/rdf#地理座標", "@type" : "@id" }, "label" : "http://www.w3.org/2000/01/rdf-schema#label", "lat" : { "@id" : "http://www.w3.org/2003/01/geo/wgs84_pos#lat", "@type" : "http://www.w3.org/2001/XMLSchema#float" }, "long" : { "@id" : "http://www.w3.org/2003/01/geo/wgs84_pos#long", "@type" : "http://www.w3.org/2001/XMLSchema#float" }, "名称" : { "@id" : "http://imi.go.jp/ns/core/rdf#名称", "@type" : "@id" }, "表記" : "http://imi.go.jp/ns/core/rdf#表記", "identifier" : "http://purl.org/dc/terms/identifier", "schema" : "http://schema.org/", "cc" : "http://creativecommons.org/ns#", "icnew" : "http://imi.go.jp/ns/core/rdf#", "jrrk" : "http://purl.org/jrrk#", "uncefactISO4217" : "urn:un:unece:uncefact:codelist:standard:ISO:ISO3AlphaCurrencyCode:2012-08-31#", "dct" : "http://purl.org/dc/terms/", "owl" : "http://www.w3.org/2002/07/owl#", "xsd" : "http://www.w3.org/2001/XMLSchema#", "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", "ic" : "http://imi.ipa.go.jp/ns/core/rdf#", "foaf" : "http://xmlns.com/foaf/0.1/" } }
この結果は、@graph
と@context
の2つによって成り立っております。@graph
は実際に取得したトリプルです。@context
はそれぞれのプロパティがどのIRIを意味するか、型は何の型か、といった情報が含まれております。詳細は JSON-LDの仕様をご確認ください。
JSON-LDには@context
の内容を全て@graph
に埋め込むようにしたExpanded書式、逆に@context
に可能な限り情報を入れるようにし、@graph
が最小になるようにしたCompacted書式等があります。
上記の書式は、主語1つにつき配列1要素を割り当てるようにしたFlattened書式です。
これはそれぞれの主語に対して処理を行うときに便利な書式です。
帰ってくる書式はサーバの実装により異なるため、統一して扱えるようにjsonld.jsなどで事前に書式を変えておくと良いでしょう。
odpのサーバでは、デフォルトでFlattened
形式を取得してくれるようなので、このまま進めます。
@graph
内から、型がjrrk:BusStop
であるトリプルの抽出を行います。@context
の中から、jrrk:BusStop
がどのようなプロパティで入っているかを求めます。@context
内には省略名がフルパスの参照であるケースと、短縮IRIのプリフィックスの参照であるケースがあります。
例えば、
{ "hasRoute" : { "@id" : "http://purl.org/jrrk#hasRoute" } }
上記の要素が@context
内にあった場合、結果内のhasRoute
はhttp://purl.org/jrrk#hasRoute
を示します。
{ "jrrk" : "http://purl.org/jrrk#" }
上記の要素が@context
内にあった場合、結果内のjrrk:XXX
はhttp://purl.org/jrrk#XXX
を示します。
戦略として、まず絶対パスが参照されているケースを求め、次に相対参照されているケースを求めます。もしなければ、@graph
内に絶対パスで指定されています。
@context
では、キーに対して純粋にIRIの文字列が格納されているか、@id
要素にIRIが格納されています。相対参照の場合は、直接文字列が指定されています。
以下の関数によって、使われている値を検索することが可能です。
function find_key(context, full_iri){ // 短縮IRIによる探索準備 let splited = /^(.*[/#])([^/#]*)$/.exec(full_iri); let prefix_iri = splited[1]; let suffix = splited[2]; let shorten_result = null; let full_result = null; // フルIRIによる探索 for (i in context) { let compare_target = typeof(context[i]) === "string" ? context[i] : context[i]["@id"]; if( compare_target === full_iri ){ // fullは一意 full_result=i; break; } if( compare_target === prefix_iri ){ shorten_result = i + ":" + suffix; } } if (full_result !== null ){ return full_result; } if (shorten_result !== null){ return shorten_result; } return full_iri; }
準備が整ったので、抜き出しを行います。結果json
に対して、@graph
からjrrk:BusStop
型のトリプルの抽出を行います。
let busstops_graphs = json["@graph"].filter(triple => triple["@type"] === find_key(json, "http://purl.org/jrrk#BusStop"));
今回は、IRIと名前の表示を行います。名前はhttp://imi.go.jp/ns/core/rdf#名称/http://imi.go.jp/ns/core/rdf#表記
を辿り、日本語のものを表示します。
トリプルから、IRIと名前を持つプロパティへの加工を行います。
let Name = find_key(json["@context"], "http://imi.go.jp/ns/core/rdf#名称"); let Transcribe = find_key(json["@context"], "http://imi.go.jp/ns/core/rdf#表記"); let busstops = busstop_graphs.map(triples => { let names = json["@graph"].filter(t => t["@id"] === triples[Name]); let name = undefined; if (names.length > 0) { name = names[0][Transcribe]["@value"]; } return { iri: triples["@id"], name: name, }; });
最初の2行は、@context
によるキーを求めています。
3行目から、各トリプルに対し、名前を抽出、オブジェクトへの変換処理を行っています。
4行目で名前を持つグラフの抜き出しを行います。名前は複数持っている可能性もありますが、
今回は簡略化のため単一の名前を持っているものとします。
7行目で、実際の表示するための名前の抜き出しを行います。
多言語化されたものを含めても、名前が単一であれば、以下のようなオブジェクトが格納されます。
もし名前が複数あったのであれば、以下のオブジェクトが配列形式で格納されます。
{ "@value": "名前", "@lang": "ja" }
@value
は、そのままトリプルに対するリテラルの値を指します。@lang
は、それが何語で示すかを表しています。
今回は単一であることがわかっているので、そのまま格納されているオブジェクトの@value
を参照します。
これにて、IRIと名前が格納されたオブジェクトを作成することができました。
今回は言語が日本語のものしかありませんでしたが、データが多言語対応したときのために、アプリ側も多言語化できるようにしましょう。
もし多言語化されていた場合は、以下のような形式で格納されています。
[ { "@value": "名前", "@lang": "ja" }, { "@value": "Name", "@lang": "en" } ]
ja
, en
の順に優先して表示することを考えます。はじめに、優先順位のテーブルを用意します。
そこから、フィルタ・並び替えを行います。
let langPriority = { "ja": 0, "en": 1, }; name_values = names[0][Transcribe]; if(Array.isArray(name_values) && name_values.length > 0) { name = name_values .filter(value => allowLang.indexOf(value["@lang"]) >= 0) .sort( (a, b) => langPriority[a["@lang"]] - langPriority[b["@lang"]])[0]["@value"]; } else { name = name_values["@value"]; }
上記のコードを応用すれば、国際化に対応したアプリの作成が容易になります。
このように、RDFをJSONで表した形式であるJSON-LDを加工すると、SPARQLの結果や、元々のRDFデータから柔軟にデータを取り出すことができます。