Shapes Constraint Language (SHACL)


この記事は，以下の文書に書かれた内容を自分なりにまとめたものです．

Shapes Constraint Language (SHACL)
W3C First Public Working Draft 08 October 2015
http://www.w3.org/TR/2015/WD-shacl-20151008/

原文とほぼ同じ流れ，同じexampleを用いていますが， **翻訳ではありません**．筆者がSHACLをどう理解したかをまとめたメモです．分野に関する知識の不足，あるいは英語の読解能力不足などに起因する間違いがあると思います．間違いを発見したら，コメント等で指摘をいただけると幸いです．

2015/11/18時点で，2.2節までの内容をまとめています．2.3以降のConstraintに関する話は，今後この記事に追加する形でまとめる予定です．

## 概要

SHACLは，RDFグラフの制約（RDFグラフ中の各ノードがどのようなプロパティを持ちうるか，など）を記述するための言語である．SHACLを使うと，例えば，

* foaf:Personクラスのインスタンスはfoaf:mboxを一つだけ持ち，その値はIRIである
* ex:bug1というインスタンスは最低一つ以上のex:reportedByを持ち，その全ての値はfoaf:Personクラスのインスタンスである

という制約を表現できる．


### Data GraphとShapes Graph

RDFグラフがSHACLで記述された制約に従っているかを検証する処理系のことを，ここではSHACLプロセッサと呼ぶ．プロセッサは，以下の2種類のグラフを入力として受け取り，その検証結果を出力する.

* Data Graph = 検証の対象
	* 検証を行いたいデータを含むグラフ
* Shapes Graph = 検証に用いる制約
	* SHACLで記述されたShape（制約の範囲と内容．詳しくは後述）の定義や，その他Data Graphの検証に有用な情報を含むグラフ



Data GraphとShapes Graphの例を示す．IssueとUserに関する情報を持つData Graphに対し，以下の制約を与えたいとする．

* 全てのIssueは「未割り当て」または「割り当て済み」のステータスを持つ
* 全てのIssueは一つのReporterを持つ
* 全てのReporterは一つの名前と一つ以上のメールアドレスを持ち，メールアドレスの値はIRIである

このとき，Data GraphとShapes Graphは例えば以下のように記述できる．

``` DataGraph
inst:Issue1
	a ex:Issue ;
	ex:state ex:unassigned ;
	ex:reportedBy inst:User2 .

inst:User2
	a foaf:Person ;
	foaf:name "Bob Smith" ;
	foaf:mbox <mailto:bob@example.org> ;
	foaf:mbox <mailto:rs@example.org> .

inst:Issue3
	a ex:Issue ;
	ex:state ex:unsinged ;
	ex:reportedBy inst:User4 .

inst:User4
	a foaf:Person ;
	foaf:name "Bob Smith", "Robert Smith" ;
	foaf:mbox <mailto:bob@example.org> ;
	foaf:mbox <mailto:rs@example.org> .
```

``` ShapesGraph
ex:IssueShape
	a sh:Shape ;
	sh:scopeClass ex:Issue;
	sh:property [
		sh:predicate ex:state ;
		sh:allowedValues (ex:unassigned ex:assigned) ;
		sh:minCount 1 ;
		sh:maxCount 1 ;
	] ;
	sh:property [
		sh:predicate ex:reportedBy ;
		sh:valueShape ex:UserShape ;
		sh:minCount 1 ;
		sh:maxCount 1 ;
	] .

ex:UserShape
	a sh:Shape ;
	sh:property [
		sh:predicate foaf:name ;
		sh:datatype xsd:string ;
		sh:minCount 1 ;
		sh:maxCount 1 ;
	] ;
	sh:property [
		sh:predicate foaf:mbox ;
		sh:nodeKind sh:IRI ;
		sh:minCount 1 ;
	] .
```

SHACLプロセッサは，IssueShapeを使ってIssue1とIssue3を，UserShapeを使ってUser2とUser4の検証を行う．検証の順番は，

1. IssueShapeで指定されているscopeClassのインスタンスであるIssue1を検証する
2. 1の検証中にsh:valueShapeでUserShapeが呼び出されるとUserShapeでUser2を検証する
3. 1と2の結果から，Issue1とUser2は制約を満たしていると判定する
4. Issue3の検証とその途中でUser4の検証を行う
5. Issue3はex:stateの値が指定されたリスト以外のものであるため制約違反，User4はfoaf:nameの値が一つではないため制約違反と判定する

というものである．UserShapeにスコープが設定されていないので，他のShapes Graphから呼び出されるまでは，Userインスタンスの検証が始まることは無い．


## Shapes

SHACLでは，グラフの検証に用いる制約をShapeと呼ぶ．各Shapeは，

* 制約の及ぶ範囲（Focus Nodes）を指定するScopeとFilter
* 制約の内容を定義するConstraint

で構成されている．SHACLプロセッサは，

1. Data GraphとShapes Graphを受け取る
2. Scopeによって，検証の対象となるFocus Nodesを限定する
3. Filterによって，Focus Nodesをさらに絞り込む
4. 絞り込まれたFocus Nodesに対して，Constraintsによる検証を行う

という処理を行う．原文のFig.1が分かりやすい．


## Scope

Scopeには，Individual Scopes，Class-based Scopes，General Scopesの3種類がある．これらのScopeは，それぞれFocus Nodesの指定方法が異なっている．
一つのShapeに対して複数のScopeが定義されている場合，SHACLの処理系はそれぞれのScopeで得られるノード集合の和集合をFocus Nodesとして扱う

### Individual Scopes

個々のノードからそのノードの検証に用いるShapeへのリンクを記述する方法をIndividual Scopesと呼ぶ．このとき，当該Shapeへリンクしている全てのノードがFocus Nodesとなる．

``` DataGraph
ex:ExampleInstance
	sh:nodeShape ex:ExampleShape .
```

```ShapesGraph
ex:ExampleShape
	a sh:Shape ;
	sh:constraint [
		...
	] .
```

### Class-based Scopes

ノードのクラス（rdf:type）によってFocus Nodesの対象を指定する方法をClass-based Scopesと呼ぶ．指定したクラスのサブクラスに属するインスタンスもFocus Nodesとして扱う．

``` DataGraph
ex:ExampleInstance
	rdf:type ex:ExampleClass .
```

``` ShapesGraph
ex:ExampleClass
	a rdfs:Class .

ex:ExampleShape
	a sh:Shape ;
	sh:scopeClass ex:ExampleClass ;
	sh:constraint [
		...
	] .
```

2.1.2のEXAMPLE 5では，sh:scopeClassを使わない記述方法も紹介されているが，この方法でなぜExampleClassのインスタンスをスコープノードとして指定できるのかは分からない．

### General Scopes

#### Property scopes

指定したプロパティの主語をFocus Nodesとする方法．以下の例では，Data Graph中でex:propertyの主語となっているリソースがFocus Nodesとなる．

``` ShapesGraph
ex:PropertyScopeExampleShape
	a sh:Shape ;
	sh:scope [
		a sh:PropertyScope ;
		sh:predicate ex:property ;
	] ;
	sh:constraint [
		...
	] .
```

#### Inverse property scopes
指定したプロパティの目的語をFocus Nodesとする方法．以下の例では，Data Graph中でex:propertyの目的語となっているリソースがFocus Nodesとなる．

``` ShapesGraph
ex:PropertyScopeExampleShape
	a sh:Shape ;
	sh:scope [
		a sh:InversePropertyScope ;
		sh:predicate ex:property ;
	] ;
	sh:constraint [
		...
	] .
```


#### All subjects scopes

Data Graph中の全ての主語をFocus Nodesとする方法．

``` ShapesGraph
ex:PropertyScopeExampleShape
	a sh:Shape ;
	sh:scope [
		a sh:AllSubjectsScope ;
	] ;
	sh:constraint [
		...
	] .
```

#### All objects scopes

リテラルを除く，Data Graph中の全ての目的語をFocus Nodesとする方法．

``` ShapesGraph
ex:PropertyScopeExampleShape
	a sh:Shape ;
	sh:scope [
		a sh:AllSubjectsScope ;
	] ;
	sh:constraint [
		...
	] .
```

#### SPARQL-based Scopes

SPARQLのSELECTクエリでヒットしたノードをFocus Nodesとする方法．クエリの返値は?thisという変数にバインドする必要がある．以下の例では，ex:Personクラスのインスタンス，かつex:bornInの値がex:USAであるノードがFocus Nodesとなる．

```ShapesGraph
ex:USCitizenShape
	a sh:Shape ;
	sh:scope [
		sh:sparql """
			SELECT ?this
			WHERE {
				?this a ex:Person .
				?this ex:bornIn ex:USA .
			}
			""" ;
	] ;
	sh:constraint [
		...
	] .
```


## Filter

Scopeで指定されたFocus Nodesをさらに絞り込みたい時がある．例えば，ex:Personクラスのインスタンスのうち，ex:bornInの値がex:USAであるものは他のインスタンスと飲酒可能年齢が異なる，という制約を記述したい場合である．以下の例では，ex:somePropertyの主語となるノードのうち，ex:requiredPropertyの値がex:requiredValueとなるノードのみをFocus Nodesとして，ex:somePropertyの最小出現回数が1回という制約を与える．

``` DataGraph
ex:FilteredShapeValidExampleInstance
	ex:someProperty ex:someValue ;
	ex:requiredProperty ex:requiredValue .
```

``` ShapeGraph
ex:FilteredExampleShape
	a sh:Shape ;
	sh:property [
		sh:predicate ex:someProperty ;
		sh:minCount 1 ;
		sh:filterShape [
			a sh:Shape ;
			sh:property [
				sh:predicate ex:requiredProperty ;
				sh:hasValue ex:requiredValue ;
			]
		] ;
	] .
```


Filterは，特定のScopeに対してのみでなく，Shapeに含まれる全てのScopeに対して適用することもできる．

``` DataGraph
ex:FilteredShapeValidExampleInstance
	ex:someProperty ex:someValue ;
	ex:requiredProperty ex:requiredValue .
```

``` ShapeGraph
ex:FilteredExampleShape
	a sh:Shape ;
	sh:filterShape [
		sh:property [
			sh:predicate ex:requiredProperty ;
			sh:hasValue ex:requiredValue ;
		]
	] ;
	sh:property [
		sh:predicate ex:someProperty ;
		sh:minCount 1 ;
	] .
```

ScopeとFilterの違いはよく分からない．Scopeは並列に複数指定すると各Focus Nodesの和集合を検証対象としてしまうので，積集合を検証対象にしたい場合はScopeを入れ子にし，内側のScopeをFilterと呼ぶのだろうか．

### Focus NodesとConstraintの関係記述

Shapeは，前述したScopeとFilterの他にConstraintを持つ．複数のScopeとFilter，Constraintがある場合に，あるScopeとFilterで選択したFocus Nodesに対してどのConstraintを適用するのかをShapes Graph中で定義する必要がある．Constraintに関する具体的な説明は後述するとして，ここではScopeとFilterによって選択されるFocus NodesとConstraintとの関係を記述するためのプロパティを紹介する．いずれのプロパティも，主語がScopeで目的語がConstraintである．

* sh:property
	* Focus Nodesを主語とするプロパティに対して与える制約を記述する
* sh:inverseProperty
	* Focus Nodesを目的語とするプロパティに対して与える制約を記述する
* sh:constraint
	* 単一プロパティに対する制約以外を記述する．例えば，プロパティの組に対する制約など

注意したいのは，sh:propertyと前述のsh:predicateの紛らわしさ．

* sh:property
	* 主語がShape，目的語がConstraintである
	* Shapeとそれに含まれるConstraintとを結ぶプロパティ
* sh:predicate
	* 主語がScope，FilterまたはConstraint，目的語がPropertyである
	* Focus Nodes選択の手がかりとするプロパティを記述する（前述のProperty Scopeも参照）
	


Shapeに含まれるConstraintを記述する際に用いるのはsh:propertyであるということ．

## Constraint


Constraintの中でSPARQL式を多用する．SPARQL式中で使用する変数の定義を以下に示す．

* $this
	* 現在のFocus Nodesを指す
* $shapesGraph
	* 検証対象のData Graphに対応するShapes GraphのIRIを指す
* $currentShape
	* 検証中のShapeを指す

SPARQLクエリの結果の各行が，それぞれ一つの検証結果を表している．
?failureの値がTrueのとき，検証に失敗した箇所は必ず報告される．


### Property Constraints

Property Constraintsは，Focus Nodesを主語とするプロパティの値に対する制約である．ShapeとProperty Constraintはsh:propertyというプロパティで関連付けられる．以下の例では，ShapeはFocus Nodesを主語とするプロパティに対して

* ex:somePropertyというプロパティは必ず1回以上記述する
* ex:somePropertyの値はex:SomeClassクラスのインスタンスとする

という制約を与えている．

``` ShapesGraph
ex:InlinePropertyConstraintExampleShape
	a sh:Shape ;
	sh:property [
		a sh:PropertyConstraint ;
		sh:predicate ex:someProperty ;
		sh:minCount 1 ;
		sh:valueClass ex:SomeClass ;
		rdfs:label "some property" ;
		rdfs:comment "Description of the role of ex:someProperty (in the context of the constraint)" ;
	] .
```

SHACLにおいて，Property Constraintsはsh:PropertyConstraintクラスのインスタンスとして表現される．sh:propertyの目的語としてProperty Constraintsを記述する場合は，以下のようにrdf:type sh:PropertyConstraint を省略することができる．


``` ShapesGraph
ex:InlinePropertyConstraintExampleShape
	a sh:Shape ;
	sh:property [
		sh:predicate ex:someProperty ;
		sh:minCount 1 ;
		sh:valueClass ex:SomeClass ;
		rdfs:label "some property" ;
		rdfs:comment "Description of the role of ex:someProperty (in the context of the constraint)" ;
	] .
```

ただし，Constraintを空ノードではなくIRIで表現する場合は，rdf:type sh:PropertyConstraintを記述すべきである．

``` ShapesGraph
ex:StandAlonePropertyConstraintExampleShape
	a sh:Shape ;
	sh:property ex:StandAloneConstraint .

ex:StandAloneConstraint
	a sh:PropertyConstraint ;
	sh:predicate ex:someProperty ;
	sh:defaultValue ex:SomeInstance ;
	sh:minCount 1 ;
	sh:valueClass ex:SomeClass .
```

また，sh:propertyの値がsh:PropertyConstraintのサブクラスのインスタンスである場合，rdf:typeの記述は必須である．


#### Property ConstraintとSHACL対応ツールとの関係

Property Constraintには，rdfs:labelとrdfs:commentを用いてプロパティの名前や説明を記述できる．これらは，SHACLに対応したツールにおいて，プロパティが本来持つラベルや説明よりも優先して表示される．rdfs:labelとrdfs:commentはそれぞれ複数記述することができるが，各言語タグにつきそれぞれ1つまでとすべきである．

以下の例では，dcterms:title本来のラベルである'title'@enとは別に，Property Constraintの中で'Book Title'@en，'書名'@jaというラベルを定義している．このとき，SHACLを扱うツール上では，'Book Title'あるいは'書名'という表記が'title'よりも優先して表示される．

```
dcterms:title rdfs:label 'title'@en .

ex:TitleConstraint
    a sh:PropertyConstraint ;
    sh:predicate dcterms:title ;
    rdfs:label 'Book Title'@en
    rdfs:label '書名'@ja .
```


Property Constraintは，sh:defaultValueとその値を一つだけ持つことがある．デフォルト値は，同一Constraint内のsh:datatype，sh:directValueType，sh:valueClassなどで指定された制約に従うべきである．デフォルト値の意味はSHACLで定められていない．つまり，デフォルト値が定められている項目について，値が入力されなければデフォルト値と同値として扱うといった意味は持たない．

デフォルト値は，ユーザインタフェースを持つSHACL対応ツールへの自動入力に利用することが想定されている．入力フォームを表示する際，あらかじめデフォルト値を入力しておくという用途である．

#### 定義可能な制約

Property Constraintの中で定義できる制約は以下である．ひとつのConstraint内ではそれぞれの制約の種類につきひとつまでしか定義できない．例えば，dc:subjectに対するConstraint Cがあるとき，Cの中でsh:minCountとsh:minLengthを記述することはできるが，sh:hasValueを2つ記述することはできない．同一プロパティに対して同じ種類の制約を複数定義したい場合は，sh:propertyを分けて記述する．

| Properties | Summary |
|:-----------|:--------|
|sh:allowedValues | 記述できる値の一覧 |
|sh:datatype | 記述できる値のデータタイプ |
|sh:hasValue | 値として必ず含まなければならないもの |
|sh:minCount, sh:maxCount | 記述の最小回数，最大回数 |
|sh:minLength, sh:maxLength | 値の最小文字数，最大文字数 |
|sh:maxExclusive | 記述できる値は〜より大きい (>) 値のみ |
|sh:maxInclusive | 記述できる値は〜以上 (>=) の値のみ |
|sh:minExclusive | 記述できる値は〜より小さい (<) 値のみ |
|sh:minInclusive | 記述できる値は〜以下 (<=) の1値のみ |
|sh:nodeKind | 値となるノードの種類 (IRI, 空ノード, リテラル) |
|sh:pattern | 記述できる値を正規表現で制限 |
|sh:valueClass and sh:directValueType |	値となるノードのクラス |
|sh:valueShape | 記述できる値をShapeで制限 |
|sh:qualifiedValueShape, sh:qualifiedMinCount, sh:qualifiedMaxCount | Nested shape of a given minimum/maximum number of values |


#### Property Constraintを用いた検証の結果

Property Constraintを用いてRDFデータを検証するとき，その結果は

* 主語がFocus Nodesのいずれかひとつである
* 述語がsh:predicateで指定したプロパティである
* 目的語が前節の各種制約に違反した値である

という条件に一致するRDFトリプルの一覧として出力される．


#### sh:directValueTypeとsh:valueClassの違い

いずれも値のクラスを限定するための制約であるが，sh:directValueTypeは指定したクラスのインスタンスのみを値として認めるのに対して，sh:valueClassは指定したクラスのインスタンスだけでなく，そのサブクラスのインスタンスも値として認める．

例えば，

``` PropertyConstraints
ex:AuthorConstraint
	a sh:PropertyConstraint ;
	sh:predicate ex:author ;
	sh:valueDirectClass ex:Person .
	
ex:ContributorConstraint
	a sh:PropertyConstraint ;
	sh:predicate ex:contributor ;
	sh:valueClass ex:Person .
```

という制約を定義した場合，ex:authorの値にはex:Personのインスタンスのみを記述できるのに対して，ex:contributorの値にはex:Personのインスタンスだけでなくex:Personのサブクラスのインスタンスも記述できる．


#### sh:hasValue

sh:hasValueは，指定した値がプロパティの値に含まれていなければならないという制約を表現する．

例えば，

``` PropertyConstraint
ex:SubjectConstraint
	a sh:PropertyConstraint ;
	sh:predicate ex:subject ;
	sh:hasValue 'Computer Science' .
```

という制約を定義した場合，各Focus Nodeが持つex:subjectのうち，ひとつ以上は値として'Computer Science'と記述されていなければならない．つまり，

``` ValidData
ex:OneBook
	ex:subject 'Computer Science', 'RDF', 'Web' .
```

というデータは問題無いが，

``` InvalidData
ex:OneBook
	ex:subject 'Library', 'Information' .
```

というデータは制約違反となる．