diff --git a/shacl12-core/index.html b/shacl12-core/index.html
index b89778b7..5b7de87b 100644
--- a/shacl12-core/index.html
+++ b/shacl12-core/index.html
@@ -2762,6 +2762,9 @@
+ List Constraint Components
+
+ The constraint components in this section apply to value nodes that are SHACL lists .
+ They specify conditions on the structure, length, and members of SHACL lists.
+
+
+
+ sh:memberShape
+
+ sh:memberShape
specifies that all members of SHACL list value nodes must conform to the given node shape .
+
+
+ Constraint Component IRI : sh:MemberShapeConstraintComponent
+
+
+ Parameters:
+
+
+ Property
+ Summary and Syntax Rules
+
+
+ sh:memberShape
+
+ The shape that all members of the SHACL list must conform to.
+ The value of sh:memberShape
must be a well-formed node shape .
+
+
+
+
+
+
+ Let
$memberShape
be a
parameter value for
sh:memberShape
.
+ Each
value node v
must be a
SHACL list - if
v
is not a SHACL list there is a
validation result .
+ If any member
m
of the
SHACL list v
does not
conform to
$memberShape
, there is a
validation result .
+
+
+ The remainder of this section is informative.
+
+ Each member m
of a value node v
that does not conform to the $memberShape
should be reported as a separate sh:detail
in the validation result for v
.
+ If v
is not a valid SHACL list , this should be reported as a top-level validation result and validation of individual members should not be attempted.
+
+
+ Examples of how to generate sh:detail
s in validation results can be found in the test cases for sh:memberShape
in the SHACL test suite: memberShape-001.ttl .
+
+
+ In the following example, all values of the property ex:speakerOrder
must be SHACL lists with members that are IRIs.
+
+
+
+
+ex:AgendaShape
+ a sh:NodeShape ;
+ sh:targetClass ex:Agenda ;
+ sh:property [
+ sh:path ex:speakerOrder ;
+ sh:memberShape [
+ sh:nodeKind sh:IRI ;
+ ] ;
+ ] .
+
+
+
{
+ "@id": "ex:AgendaShape",
+ "@type": "sh:NodeShape",
+ "sh:property": {
+ "sh:memberShape": {
+ "sh:nodeKind": {
+ "@id": "sh:IRI"
+ }
+ },
+ "sh:path": {
+ "@id": "ex:speakerOrder"
+ }
+ },
+ "sh:targetClass": {
+ "@id": "ex:Agenda"
+ }
+}
+
+
+
+
+ex:agenda1 a ex:Agenda ;
+ ex:speakerOrder ( ex:Alice ex:Bob ex:Charlie ) .
+
+ex:agenda2 a ex:Agenda ;
+ ex:speakerOrder ( ex:Alice ex:Bob "Charlie" ) .
+
+
+
{
+ "@graph": [
+ {
+ "@id": "ex:agenda1",
+ "@type": "ex:Agenda",
+ "ex:speakerOrder": {
+ "@list": [
+ {
+ "@id": "ex:Alice"
+ },
+ {
+ "@id": "ex:Bob"
+ },
+ {
+ "@id": "ex:Charlie"
+ }
+ ]
+ }
+ },
+ {
+ "@id": "ex:agenda2",
+ "@type": "ex:Agenda",
+ "ex:speakerOrder": {
+ "@list": [
+ {
+ "@id": "ex:Alice"
+ },
+ {
+ "@id": "ex:Bob"
+ },
+ "Charlie"
+ ]
+ }
+ }
+ ]
+}
+
+
+
+
+
+
+ sh:minListLength
+
+ sh:minListLength
specifies the minimum number of members that SHACL list value nodes must have.
+
+
+ Constraint Component IRI : sh:MinListLengthConstraintComponent
+
+
+ Parameters:
+
+
+ Property
+ Summary and Syntax Rules
+
+
+ sh:minListLength
+
+ The minimum number of members in the SHACL list .
+ The values of sh:minListLength
in a shape are literals with datatype xsd:integer
.
+ The values of sh:minListLength
in a shape are integers greater than or equal to 0.
+
+
+
+
+
+
+ Let
$minListLength
be a
parameter value for
sh:minListLength
.
+ Each
value node v
must be a
SHACL list - if
v
is not a SHACL list there is a
validation result .
+ If the number of members in a list
v
is less than
$minListLength
,
+ there is a
validation result .
+
+
+ The remainder of this section is informative.
+
+ In the following example, all values of the property ex:skills
must be SHACL lists with at least 1 member.
+ Additional test cases for sh:minListLength
can be found in the SHACL test suite: minListLength-001.ttl .
+
+
+
+
+ex:PersonShape
+ a sh:NodeShape ;
+ sh:targetClass ex:Person ;
+ sh:property [
+ sh:path ex:skills ;
+ sh:minListLength 1 ;
+ ] .
+
+
+
{
+ "@id": "ex:PersonShape",
+ "@type": "sh:NodeShape",
+ "sh:property": {
+ "sh:minListLength": {
+ "@type": "xsd:integer",
+ "@value": "1"
+ },
+ "sh:path": {
+ "@id": "ex:skills"
+ }
+ },
+ "sh:targetClass": {
+ "@id": "ex:Person"
+ }
+}
+
+
+
+
+ex:person1 a ex:Person ;
+ ex:skills ( "programming" "design" ) .
+
+ex:person2 a ex:Person ;
+ ex:skills () .
+
+
+
{
+ "@graph": [
+ {
+ "@id": "ex:person1",
+ "@type": "ex:Person",
+ "ex:skills": {
+ "@list": [
+ "programming",
+ "design"
+ ]
+ }
+ },
+ {
+ "@id": "ex:person2",
+ "@type": "ex:Person",
+ "ex:skills": {
+ "@list": []
+ }
+ }
+ ]
+}
+
+
+
+
+
+
+ sh:maxListLength
+
+ sh:maxListLength
specifies the maximum number of members that SHACL list value nodes must have.
+
+
+ Constraint Component IRI : sh:MaxListLengthConstraintComponent
+
+
+ Parameters:
+
+
+ Property
+ Summary and Syntax Rules
+
+
+ sh:maxListLength
+
+ The maximum number of members in the SHACL list .
+ The values of sh:maxListLength
in a shape are literals with datatype xsd:integer
.
+ The values of sh:maxListLength
in a shape are integers greater than or equal to 0.
+
+
+
+
+
+
+ Let
$maxListLength
be a
parameter value for
sh:maxListLength
.
+ Each
value node v
must be a
SHACL list - if
v
is not a SHACL list there is a
validation result .
+ If the number of members in the list
v
is greater than
$maxListLength
,
+ there is a
validation result .
+
+
+ The remainder of this section is informative.
+
+ In the following example, all values of the property ex:hobbies
must be SHACL lists with at most 2 members.
+ Additional test cases for sh:maxListLength
can be found in the SHACL test suite: maxListLength-001.ttl .
+
+
+
+
+ex:PersonShape
+ a sh:NodeShape ;
+ sh:targetClass ex:Person ;
+ sh:property [
+ sh:path ex:hobbies ;
+ sh:maxListLength 2 ;
+ ] .
+
+
+
{
+ "@id": "ex:PersonShape",
+ "@type": "sh:NodeShape",
+ "sh:property": {
+ "sh:maxListLength": {
+ "@type": "xsd:integer",
+ "@value": "2"
+ },
+ "sh:path": {
+ "@id": "ex:hobbies"
+ }
+ },
+ "sh:targetClass": {
+ "@id": "ex:Person"
+ }
+}
+
+
+
+
+ex:person1 a ex:Person ;
+ ex:hobbies ( "reading" "writing" ) .
+
+ex:person2 a ex:Person ;
+ ex:hobbies ( "reading" "writing" "swimming" ) .
+
+
+
{
+ "@graph": [
+ {
+ "@id": "ex:person1",
+ "@type": "ex:Person",
+ "ex:hobbies": {
+ "@list": [
+ "reading",
+ "writing"
+ ]
+ }
+ },
+ {
+ "@id": "ex:person2",
+ "@type": "ex:Person",
+ "ex:hobbies": {
+ "@list": [
+ "reading",
+ "writing",
+ "swimming"
+ ]
+ }
+ }
+ ]
+}
+
+
+
+
+
+
+ sh:uniqueMembers
+
+ sh:uniqueMembers
specifies whether SHACL list value nodes must have unique members.
+
+
+ Constraint Component IRI : sh:UniqueMembersConstraintComponent
+
+
+ Parameters:
+
+
+ Property
+ Summary and Syntax Rules
+
+
+ sh:uniqueMembers
+
+ A boolean that specifies whether the members of the SHACL list must be unique.
+ The values of sh:uniqueMembers
in a shape are literals with datatype xsd:boolean
.
+
+
+
+
+
+
+ Let
$uniqueMembers
be a
parameter value for
sh:uniqueMembers
.
+ Each
value node v
must be a
SHACL list - if
v
is not a SHACL list there is a
validation result .
+ If
$uniqueMembers
is
true
and the list
v
has duplicate members,
+ there is a
validation result .
+
+
+ The remainder of this section is informative.
+
+ Each duplicate member m
of a list v
should be reported as a separate sh:detail
in the validation result for v
. If the list v
is not a valid SHACL list , this should be reported as a top-level validation result and validation of unique membership should not be attempted.
+
+
+ Examples of how to generate sh:detail
s in validation results can be found in the test cases for sh:uniqueMembers
in the SHACL test suite: uniqueMembers-001.ttl .
+
+
+ In the following example, all values of the property ex:preferences
must be SHACL lists with members that have unique values within each SHACL list.
+
+
+
+
+ex:PersonShape
+ a sh:NodeShape ;
+ sh:targetClass ex:Person ;
+ sh:property [
+ sh:path ex:preferences ;
+ sh:uniqueMembers true ;
+ ] .
+
+
+
{
+ "@id": "ex:PersonShape",
+ "@type": "sh:NodeShape",
+ "sh:property": {
+ "sh:uniqueMembers": {
+ "@type": "xsd:boolean",
+ "@value": "true"
+ },
+ "sh:path": {
+ "@id": "ex:preferences"
+ }
+ },
+ "sh:targetClass": {
+ "@id": "ex:Person"
+ }
+}
+
+
+
+
+ex:person1 a ex:Person ;
+ ex:preferences ( "coffee" "tea" ) .
+
+ex:person2 a ex:Person ;
+ ex:preferences ( "coffee" "tea" "coffee" "tea" "tea" ) .
+
+
+
{
+ "@graph": [
+ {
+ "@id": "ex:person1",
+ "@type": "ex:Person",
+ "ex:preferences": {
+ "@list": [
+ "coffee",
+ "tea"
+ ]
+ }
+ },
+ {
+ "@id": "ex:person2",
+ "@type": "ex:Person",
+ "ex:preferences": {
+ "@list": [
+ "coffee",
+ "tea",
+ "coffee",
+ "tea",
+ "tea"
+ ]
+ }
+ }
+ ]
+}
+
+
+
+
+
+
sh:qualifiedValueShape, sh:qualifiedMinCount, sh:qualifiedMaxCount
diff --git a/shacl12-test-suite/tests/core/node/manifest.ttl b/shacl12-test-suite/tests/core/node/manifest.ttl
index 6dd5e465..4cb49515 100644
--- a/shacl12-test-suite/tests/core/node/manifest.ttl
+++ b/shacl12-test-suite/tests/core/node/manifest.ttl
@@ -26,12 +26,14 @@
mf:include ;
mf:include ;
mf:include ;
+ mf:include ;
mf:include ;
mf:include ;
mf:include ;
mf:include ;
mf:include ;
mf:include ;
+ mf:include ;
mf:include ;
mf:include ;
mf:include ;
@@ -41,6 +43,7 @@
mf:include ;
mf:include ;
mf:include ;
+ mf:include ;
mf:include ;
mf:include ;
mf:include ;
diff --git a/shacl12-test-suite/tests/core/node/maxListLength-001.ttl b/shacl12-test-suite/tests/core/node/maxListLength-001.ttl
new file mode 100644
index 00000000..5e412494
--- /dev/null
+++ b/shacl12-test-suite/tests/core/node/maxListLength-001.ttl
@@ -0,0 +1,63 @@
+@prefix dash: .
+@prefix ex: .
+@prefix mf: .
+@prefix owl: .
+@prefix rdf: .
+@prefix rdfs: .
+@prefix sh: .
+@prefix sht: .
+@prefix xsd: .
+
+ex:ListShape
+ rdf:type sh:NodeShape ;
+ sh:maxListLength 2 ;
+ # Satisfies all constraints
+ sh:targetNode ex:list0, rdf:nil ;
+ # Violates maxListLength constraint
+ sh:targetNode ex:list1, ex:notAList ;
+.
+
+ex:list0
+ rdf:first 1 ;
+ rdf:rest ( 2 ) .
+
+ex:list1
+ rdf:first 1 ;
+ rdf:rest ( 2 3 ) .
+
+<>
+ rdf:type mf:Manifest ;
+ mf:entries (
+
+ ) ;
+.
+
+
+ rdf:type sht:Validate ;
+ rdfs:label "Test of sh:maxListLength on node shape 001" ;
+ mf:action [
+ sht:dataGraph <> ;
+ sht:shapesGraph <> ;
+ ] ;
+ mf:result [
+ rdf:type sh:ValidationReport ;
+ sh:conforms "false"^^xsd:boolean ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:list1 ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:MaxListLengthConstraintComponent ;
+ sh:sourceShape ex:ListShape ;
+ sh:value ex:list1 ;
+ ] ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:notAList ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:MaxListLengthConstraintComponent ;
+ sh:sourceShape ex:ListShape ;
+ sh:value ex:notAList ;
+ ] ;
+ ] ;
+ mf:status sht:approved ;
+.
diff --git a/shacl12-test-suite/tests/core/node/memberShape-001.ttl b/shacl12-test-suite/tests/core/node/memberShape-001.ttl
index b2518285..9dde67e2 100644
--- a/shacl12-test-suite/tests/core/node/memberShape-001.ttl
+++ b/shacl12-test-suite/tests/core/node/memberShape-001.ttl
@@ -14,57 +14,64 @@ ex:IRIShape a sh:NodeShape ;
ex:IRIListShape
rdf:type sh:NodeShape ;
sh:memberShape ex:IRIShape ;
- sh:targetNode ( ex:Alice ex:Bob ) ;
+ sh:targetNode ex:list0 ;
# Valid empty list
- sh:targetNode () ;
- # _:b1 is valid, the remainder trigger violations (including _:b3 which has no properties in this graph)
- sh:targetNode _:b1, _:b2, _:b3, _:b4, _:b5, _:b6, _:b7, _:b9 ;
+ sh:targetNode rdf:nil ;
+ # ex:list1 is valid, the remainder trigger violations (including ex:list3 which has no properties in this graph)
+ sh:targetNode ex:list1, ex:list2, ex:list3, ex:list4, ex:list5, ex:list6, ex:list7, ex:list9 ;
.
-_:b1
+ex:list0
+ rdf:first ex:Alice ;
+ rdf:rest (
+ ex:Bob
+ ) ;
+.
+
+ex:list1
rdf:first ex:Alice ;
rdf:rest rdf:nil ;
ex:extraProperty "extra" ;
.
-_:b2
+ex:list2
rdf:first ex:Alice ;
rdf:rest (
"Bob"
)
.
-_:b4
+ex:list4
rdf:first ex:Alice ;
.
-_:b5
+ex:list5
rdf:first "Charlie" ;
rdf:rest (
"Donna"
)
.
-_:b6
+ex:list6
rdf:first "Charlie" ;
.
-_:b8 rdfs:label "Malformed SHACL List" .
+ex:list8 rdfs:label "Malformed SHACL List" .
-_:b7
+ex:list7
rdf:first "Charlie" ;
- rdf:rest _:b8 ;
+ rdf:rest ex:list8 ;
.
-# using _:b9 and _:b10 to test recursive list error
-_:b9
+# using ex:list9 and ex:list10 to test recursive list error
+ex:list9
rdf:first ex:Alice ;
- rdf:rest _:b10 ;
+ rdf:rest ex:list10 ;
.
-_:b10
+ex:list10
rdf:first "Bob" ;
- rdf:rest _:b9 .
+ rdf:rest ex:list9 .
<>
rdf:type mf:Manifest ;
@@ -85,15 +92,13 @@ _:b10
sh:conforms "false"^^xsd:boolean ;
sh:result [
rdf:type sh:ValidationResult ;
- sh:focusNode _:b2 ;
- sh:resultMessage "Value does not conform to Shape ex:IRIListShape. See details for more information." ;
+ sh:focusNode ex:list2 ;
sh:resultSeverity sh:Violation ;
sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
sh:sourceShape ex:IRIListShape ;
sh:detail [
rdf:type sh:ValidationResult ;
sh:focusNode "Bob" ;
- sh:resultMessage "Value is not of Node Kind sh:IRI" ;
sh:resultSeverity sh:Violation ;
sh:sourceConstraintComponent sh:NodeKindConstraintComponent ;
sh:sourceShape ex:IRIShape ;
@@ -102,51 +107,29 @@ _:b10
] ;
sh:result [
rdf:type sh:ValidationResult ;
- sh:focusNode _:b3 ;
- sh:resultMessage "Value is a malformed SHACL List." ;
+ sh:focusNode ex:list3 ;
sh:resultSeverity sh:Violation ;
sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
sh:sourceShape ex:IRIListShape ;
- sh:value _:b3 ;
- sh:detail [
- rdf:type sh:ValidationResult ;
- sh:focusNode _:b3 ;
- sh:resultMessage "Value is a malformed SHACL List. _:b3 is missing rdf:first and rdf:rest properties." ;
- sh:resultSeverity sh:Violation ;
- sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
- sh:sourceShape ex:IRIListShape ;
- sh:value _:b3 ;
- ] ;
+ sh:value ex:list3 ;
] ;
sh:result [
rdf:type sh:ValidationResult ;
- sh:focusNode _:b4 ;
- sh:resultMessage "Value does not conform to Shape ex:IRIListShape. See details for more information." ;
+ sh:focusNode ex:list4 ;
sh:resultSeverity sh:Violation ;
sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
sh:sourceShape ex:IRIListShape ;
- sh:value _:b4 ;
- sh:detail [
- rdf:type sh:ValidationResult ;
- sh:focusNode _:b4 ;
- sh:resultMessage "Value is a malformed SHACL List. _:b4 is missing rdf:rest property." ;
- sh:resultSeverity sh:Violation ;
- sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
- sh:sourceShape ex:IRIListShape ;
- sh:value _:b4 ;
- ] ;
+ sh:value ex:list4 ;
] ;
sh:result [
rdf:type sh:ValidationResult ;
- sh:focusNode _:b5 ;
- sh:resultMessage "Value does not conform to Shape ex:IRIListShape. See details for more information." ;
+ sh:focusNode ex:list5 ;
sh:resultSeverity sh:Violation ;
sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
sh:sourceShape ex:IRIListShape ;
sh:detail [
rdf:type sh:ValidationResult ;
sh:focusNode "Charlie" ;
- sh:resultMessage "Value is not of Node Kind sh:IRI" ;
sh:resultSeverity sh:Violation ;
sh:sourceConstraintComponent sh:NodeKindConstraintComponent ;
sh:sourceShape ex:IRIShape ;
@@ -154,7 +137,6 @@ _:b10
], [
rdf:type sh:ValidationResult ;
sh:focusNode "Donna" ;
- sh:resultMessage "Value is not of Node Kind sh:IRI" ;
sh:resultSeverity sh:Violation ;
sh:sourceConstraintComponent sh:NodeKindConstraintComponent ;
sh:sourceShape ex:IRIShape ;
@@ -163,78 +145,27 @@ _:b10
] ;
sh:result [
rdf:type sh:ValidationResult ;
- sh:focusNode _:b6 ;
- sh:resultMessage "Value does not conform to Shape ex:IRIListShape. See details for more information." ;
+ sh:focusNode ex:list6 ;
sh:resultSeverity sh:Violation ;
sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
sh:sourceShape ex:IRIListShape ;
- sh:detail [
- rdf:type sh:ValidationResult ;
- sh:focusNode "Charlie" ;
- sh:resultMessage "Value is not of Node Kind sh:IRI" ;
- sh:resultSeverity sh:Violation ;
- sh:sourceConstraintComponent sh:NodeKindConstraintComponent ;
- sh:sourceShape ex:IRIShape ;
- sh:value "Charlie" ;
- ], [
- rdf:type sh:ValidationResult ;
- sh:focusNode _:b6 ;
- sh:resultMessage "Value is a malformed SHACL List. _:b6 is missing rdf:rest property." ;
- sh:resultSeverity sh:Violation ;
- sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
- sh:sourceShape ex:IRIListShape ;
- sh:value _:b6 ;
- ] ;
+ sh:value ex:list6 ;
] ;
sh:result [
rdf:type sh:ValidationResult ;
- sh:focusNode _:b7 ;
- sh:resultMessage "Value does not conform to Shape ex:IRIListShape. See details for more information." ;
+ sh:focusNode ex:list7 ;
sh:resultSeverity sh:Violation ;
sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
sh:sourceShape ex:IRIListShape ;
- sh:detail [
- rdf:type sh:ValidationResult ;
- sh:focusNode "Charlie" ;
- sh:resultMessage "Value is not of Node Kind sh:IRI" ;
- sh:resultSeverity sh:Violation ;
- sh:sourceConstraintComponent sh:NodeKindConstraintComponent ;
- sh:sourceShape ex:IRIShape ;
- sh:value "Charlie" ;
- ], [
- rdf:type sh:ValidationResult ;
- sh:focusNode _:b8 ;
- sh:resultMessage "Value is a malformed SHACL List. _:b8 is missing rdf:first and rdf:rest property." ;
- sh:resultSeverity sh:Violation ;
- sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
- sh:sourceShape ex:IRIListShape ;
- sh:value _:b8 ;
- ] ;
+ sh:value ex:list8 ;
] ;
sh:result [
rdf:type sh:ValidationResult ;
- sh:focusNode _:b9 ;
- sh:resultMessage "Value does not conform to Shape ex:IRIListShape. See details for more information." ;
+ sh:focusNode ex:list9 ;
sh:resultSeverity sh:Violation ;
sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
sh:sourceShape ex:IRIListShape ;
- sh:detail [
- rdf:type sh:ValidationResult ;
- sh:focusNode "Charlie" ;
- sh:resultMessage "Value is not of Node Kind sh:IRI" ;
- sh:resultSeverity sh:Violation ;
- sh:sourceConstraintComponent sh:NodeKindConstraintComponent ;
- sh:sourceShape ex:IRIShape ;
- sh:value "Charlie" ;
- ], [
- rdf:type sh:ValidationResult ;
- sh:focusNode _:b10 ;
- sh:resultMessage "Value is a malformed SHACL List. A list cannot have itself in the rdf:rest+ path. List _:b9 follows from _:b10." ;
- sh:resultSeverity sh:Violation ;
- sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
- sh:sourceShape ex:IRIListShape ;
- sh:value _:b9 ;
- ] ;
+ sh:value ex:list10 ;
] ;
] ;
mf:status sht:approved ;
diff --git a/shacl12-test-suite/tests/core/node/minListLength-001.ttl b/shacl12-test-suite/tests/core/node/minListLength-001.ttl
new file mode 100644
index 00000000..118a7ec0
--- /dev/null
+++ b/shacl12-test-suite/tests/core/node/minListLength-001.ttl
@@ -0,0 +1,58 @@
+@prefix dash: .
+@prefix ex: .
+@prefix mf: .
+@prefix owl: .
+@prefix rdf: .
+@prefix rdfs: .
+@prefix sh: .
+@prefix sht: .
+@prefix xsd: .
+
+ex:ListShape
+ rdf:type sh:NodeShape ;
+ sh:minListLength 1 ;
+ # Satisfies all constraints
+ sh:targetNode ex:list0 ;
+ # Violates minListLength constraint
+ sh:targetNode rdf:nil, ex:notAList ;
+.
+
+ex:list0
+ rdf:first 1 ;
+ rdf:rest ( 2 ) .
+
+<>
+ rdf:type mf:Manifest ;
+ mf:entries (
+
+ ) ;
+.
+
+
+ rdf:type sht:Validate ;
+ rdfs:label "Test of sh:minListLength on node shape 001" ;
+ mf:action [
+ sht:dataGraph <> ;
+ sht:shapesGraph <> ;
+ ] ;
+ mf:result [
+ rdf:type sh:ValidationReport ;
+ sh:conforms "false"^^xsd:boolean ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode rdf:nil ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:MinListLengthConstraintComponent ;
+ sh:sourceShape ex:ListShape ;
+ ] ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:notAList ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:MinListLengthConstraintComponent ;
+ sh:sourceShape ex:ListShape ;
+ sh:value ex:notAList ;
+ ] ;
+ ] ;
+ mf:status sht:approved ;
+.
diff --git a/shacl12-test-suite/tests/core/node/uniqueMembers-001.ttl b/shacl12-test-suite/tests/core/node/uniqueMembers-001.ttl
new file mode 100644
index 00000000..287e9137
--- /dev/null
+++ b/shacl12-test-suite/tests/core/node/uniqueMembers-001.ttl
@@ -0,0 +1,77 @@
+@prefix dash: .
+@prefix ex: .
+@prefix mf: .
+@prefix owl: .
+@prefix rdf: .
+@prefix rdfs: .
+@prefix sh: .
+@prefix sht: .
+@prefix xsd: .
+
+ex:ListShape
+ rdf:type sh:NodeShape ;
+ sh:uniqueMembers true ;
+ # Satisfies all constraints
+ sh:targetNode ex:list0, rdf:nil ;
+ # Violates uniqueMembers constraint
+ sh:targetNode ex:list1, ex:notAList ;
+.
+
+ex:list0
+ rdf:first 1 ;
+ rdf:rest ( 2 ) .
+
+ex:list1
+ rdf:first 1 ;
+ rdf:rest ( 2 1 2 2 ) .
+
+<>
+ rdf:type mf:Manifest ;
+ mf:entries (
+
+ ) ;
+.
+
+
+ rdf:type sht:Validate ;
+ rdfs:label "Test of sh:uniqueMembers on node shape 001" ;
+ mf:action [
+ sht:dataGraph <> ;
+ sht:shapesGraph <> ;
+ ] ;
+ mf:result [
+ rdf:type sh:ValidationReport ;
+ sh:conforms "false"^^xsd:boolean ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:list1 ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:UniqueMembersConstraintComponent ;
+ sh:sourceShape ex:ListShape ;
+ sh:detail [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:list1 ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:UniqueMembersConstraintComponent ;
+ sh:sourceShape ex:ListShape ;
+ sh:value 1 ;
+ ], [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:list1 ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:UniqueMembersConstraintComponent ;
+ sh:sourceShape ex:ListShape ;
+ sh:value 2 ;
+ ]
+ ] ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:notAList ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:UniqueMembersConstraintComponent ;
+ sh:sourceShape ex:ListShape ;
+ sh:value ex:notAList ;
+ ] ;
+ ] ;
+ mf:status sht:approved ;
+.
diff --git a/shacl12-test-suite/tests/core/property/manifest.ttl b/shacl12-test-suite/tests/core/property/manifest.ttl
index b6463005..a0c3e724 100644
--- a/shacl12-test-suite/tests/core/property/manifest.ttl
+++ b/shacl12-test-suite/tests/core/property/manifest.ttl
@@ -26,12 +26,14 @@
mf:include ;
mf:include ;
mf:include ;
+ mf:include ;
mf:include ;
mf:include ;
mf:include ;
mf:include ;
mf:include ;
mf:include ;
+ mf:include ;
mf:include ;
mf:include ;
mf:include ;
@@ -47,4 +49,5 @@
mf:include ;
mf:include ;
mf:include ;
+ mf:include ;
.
\ No newline at end of file
diff --git a/shacl12-test-suite/tests/core/property/maxListLength-001.ttl b/shacl12-test-suite/tests/core/property/maxListLength-001.ttl
new file mode 100644
index 00000000..6b98001b
--- /dev/null
+++ b/shacl12-test-suite/tests/core/property/maxListLength-001.ttl
@@ -0,0 +1,86 @@
+@prefix dash: .
+@prefix ex: .
+@prefix mf: .
+@prefix owl: .
+@prefix rdf: .
+@prefix rdfs: .
+@prefix sh: .
+@prefix sht: .
+@prefix xsd: .
+
+ex:PersonShape
+ rdf:type sh:NodeShape ;
+ sh:targetClass ex:Person ;
+ sh:property ex:PersonShape-hobbies ;
+.
+
+ex:PersonShape-hobbies
+ rdf:type sh:PropertyShape ;
+ sh:path ex:hobbies ;
+ sh:maxListLength 2 ;
+.
+
+# Satisfies all constraints
+ex:person1
+ rdf:type ex:Person ;
+ ex:hobbies ( "reading" "writing" ) ;
+.
+
+ex:person2
+ rdf:type ex:Person ;
+ ex:hobbies rdf:nil ;
+.
+
+# Violates maxListLength constraint
+ex:person3
+ rdf:type ex:Person ;
+ ex:hobbies _:b1 ;
+.
+
+ex:person4
+ rdf:type ex:Person ;
+ ex:hobbies ex:notAList ;
+.
+
+_:b1
+ rdf:first "reading" ;
+ rdf:rest ( "writing" "swimming" ) .
+
+<>
+ rdf:type mf:Manifest ;
+ mf:entries (
+
+ ) ;
+.
+
+
+ rdf:type sht:Validate ;
+ rdfs:label "Test of sh:maxListLength on property shape 001" ;
+ mf:action [
+ sht:dataGraph <> ;
+ sht:shapesGraph <> ;
+ ] ;
+ mf:result [
+ rdf:type sh:ValidationReport ;
+ sh:conforms "false"^^xsd:boolean ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:person3 ;
+ sh:resultPath ex:hobbies ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:MaxListLengthConstraintComponent ;
+ sh:sourceShape ex:PersonShape-hobbies ;
+ sh:value _:b1 ;
+ ] ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:person4 ;
+ sh:resultPath ex:hobbies ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:MaxListLengthConstraintComponent ;
+ sh:sourceShape ex:PersonShape-hobbies ;
+ sh:value ex:notAList ;
+ ] ;
+ ] ;
+ mf:status sht:approved ;
+.
diff --git a/shacl12-test-suite/tests/core/property/memberShape-001.ttl b/shacl12-test-suite/tests/core/property/memberShape-001.ttl
index 40bc2c7b..f525f1ec 100644
--- a/shacl12-test-suite/tests/core/property/memberShape-001.ttl
+++ b/shacl12-test-suite/tests/core/property/memberShape-001.ttl
@@ -68,16 +68,14 @@ _:b1
sh:result [
rdf:type sh:ValidationResult ;
sh:focusNode ex:list2 ;
- sh:resultMessage "Value does not conform to Shape ex:TestShape. See details for more information." ;
sh:resultPath ex:testProperty ;
sh:resultSeverity sh:Violation ;
sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
- sh:sourceShape ex:TestShape ;
+ sh:sourceShape ex:TestShape-testProperty ;
sh:value _:b1 ;
sh:detail [
rdf:type sh:ValidationResult ;
sh:focusNode _:bc ;
- sh:resultMessage "Value is not of Node Kind sh:IRI" ;
sh:resultSeverity sh:Violation ;
sh:sourceConstraintComponent sh:NodeKindConstraintComponent ;
sh:sourceShape ex:IRIShape ;
diff --git a/shacl12-test-suite/tests/core/property/minListLength-001.ttl b/shacl12-test-suite/tests/core/property/minListLength-001.ttl
new file mode 100644
index 00000000..ebe5bf8a
--- /dev/null
+++ b/shacl12-test-suite/tests/core/property/minListLength-001.ttl
@@ -0,0 +1,80 @@
+@prefix dash: .
+@prefix ex: .
+@prefix mf: .
+@prefix owl: .
+@prefix rdf: .
+@prefix rdfs: .
+@prefix sh: .
+@prefix sht: .
+@prefix xsd: .
+
+ex:PersonShape
+ rdf:type sh:NodeShape ;
+ sh:targetClass ex:Person ;
+ sh:property [
+ sh:path ex:skills ;
+ sh:minListLength 1 ;
+ ] ;
+.
+
+ex:PersonShape-skills
+ rdf:type sh:PropertyShape ;
+ sh:path ex:skills ;
+ sh:minListLength 1 ;
+.
+
+# Satisfies all constraints
+ex:person1
+ rdf:type ex:Person ;
+ ex:skills ( "programming" "design" ) ;
+.
+
+# Violates minListLength constraint
+ex:person2
+ rdf:type ex:Person ;
+ ex:skills rdf:nil ;
+.
+
+ex:person3
+ rdf:type ex:Person ;
+ ex:skills ex:notAList ;
+.
+
+<>
+ rdf:type mf:Manifest ;
+ mf:entries (
+
+ ) ;
+.
+
+
+ rdf:type sht:Validate ;
+ rdfs:label "Test of sh:minListLength on property shape 001" ;
+ mf:action [
+ sht:dataGraph <> ;
+ sht:shapesGraph <> ;
+ ] ;
+ mf:result [
+ rdf:type sh:ValidationReport ;
+ sh:conforms "false"^^xsd:boolean ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:person2 ;
+ sh:resultPath ex:skills ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:MinListLengthConstraintComponent ;
+ sh:sourceShape ex:PersonShape-skills ;
+ sh:value rdf:nil ;
+ ] ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:person3 ;
+ sh:resultPath ex:skills ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:MinListLengthConstraintComponent ;
+ sh:sourceShape ex:PersonShape-skills ;
+ sh:value ex:notAList ;
+ ] ;
+ ] ;
+ mf:status sht:approved ;
+.
diff --git a/shacl12-test-suite/tests/core/property/uniqueMembers-001.ttl b/shacl12-test-suite/tests/core/property/uniqueMembers-001.ttl
new file mode 100644
index 00000000..2f77226a
--- /dev/null
+++ b/shacl12-test-suite/tests/core/property/uniqueMembers-001.ttl
@@ -0,0 +1,104 @@
+@prefix dash: .
+@prefix ex: .
+@prefix mf: .
+@prefix owl: .
+@prefix rdf: .
+@prefix rdfs: .
+@prefix sh: .
+@prefix sht: .
+@prefix xsd: .
+
+ex:PersonShape
+ rdf:type sh:NodeShape ;
+ sh:targetClass ex:Person ;
+ sh:property ex:PersonShape-preferences
+ ;
+.
+
+ex:PersonShape-preferences
+ rdf:type sh:PropertyShape ;
+ sh:path ex:preferences ;
+ sh:uniqueMembers true ;
+.
+
+# Satisfies all constraints
+ex:person1
+ rdf:type ex:Person ;
+ ex:preferences ( "coffee" "tea" ) ;
+.
+
+ex:person2
+ rdf:type ex:Person ;
+ ex:preferences rdf:nil ;
+.
+
+# Violates uniqueMembers constraint
+ex:person3
+ rdf:type ex:Person ;
+ ex:preferences _:b1 ;
+.
+
+ex:person4
+ rdf:type ex:Person ;
+ ex:preferences ex:notAList ;
+.
+
+_:b1
+ rdf:first "coffee" ;
+ rdf:rest ( "tea" "coffee" "tea" "tea" ) .
+
+<>
+ rdf:type mf:Manifest ;
+ mf:entries (
+
+ ) ;
+.
+
+
+ rdf:type sht:Validate ;
+ rdfs:label "Test of sh:uniqueMembers on property shape 001" ;
+ mf:action [
+ sht:dataGraph <> ;
+ sht:shapesGraph <> ;
+ ] ;
+ mf:result [
+ rdf:type sh:ValidationReport ;
+ sh:conforms "false"^^xsd:boolean ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:person3 ;
+ sh:resultPath ex:preferences ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:UniqueMembersConstraintComponent ;
+ sh:sourceShape ex:PersonShape-preferences ;
+ sh:value _:b1 ;
+ sh:detail [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:person3 ;
+ sh:resultPath ex:preferences ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:UniqueMembersConstraintComponent ;
+ sh:sourceShape ex:PersonShape-preferences ;
+ sh:value "coffee" ;
+ ], [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:person3 ;
+ sh:resultPath ex:preferences ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:UniqueMembersConstraintComponent ;
+ sh:sourceShape ex:PersonShape-preferences ;
+ sh:value "tea" ;
+ ]
+ ] ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:person4 ;
+ sh:resultPath ex:preferences ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:UniqueMembersConstraintComponent ;
+ sh:sourceShape ex:PersonShape-preferences ;
+ sh:value ex:notAList ;
+ ] ;
+ ] ;
+ mf:status sht:approved ;
+.
diff --git a/shacl12-vocabularies/shacl-shacl.ttl b/shacl12-vocabularies/shacl-shacl.ttl
index 08c78d98..67871c82 100644
--- a/shacl12-vocabularies/shacl-shacl.ttl
+++ b/shacl12-vocabularies/shacl-shacl.ttl
@@ -74,7 +74,8 @@ shsh:ShapeShape
sh:ignoredProperties, sh:in, sh:languageIn, sh:lessThan, sh:lessThanOrEquals, sh:maxCount, sh:maxExclusive,
sh:maxInclusive, sh:maxLength, sh:memberShape, sh:minCount, sh:minExclusive, sh:minInclusive, sh:minLength, sh:node, sh:nodeKind,
sh:not, sh:or, sh:pattern, sh:property, sh:qualifiedMaxCount, sh:qualifiedMinCount, sh:qualifiedValueShape,
- sh:qualifiedValueShape, sh:qualifiedValueShapesDisjoint, sh:qualifiedValueShapesDisjoint, sh:uniqueLang, sh:xone ;
+ sh:qualifiedValueShape, sh:qualifiedValueShapesDisjoint, sh:qualifiedValueShapesDisjoint, sh:uniqueLang, sh:xone ,
+ sh:minListLength, sh:maxListLength, sh:uniqList ;
sh:targetObjectsOf sh:memberShape ; # memberShape-node
sh:targetObjectsOf sh:node ; # node-node
@@ -226,7 +227,24 @@ shsh:ShapeShape
] ;
sh:property [
sh:path sh:memberShape ;
- sh:node shsh:NodeShapeShape ; # memberShape-node
+ sh:node shsh:NodeShapeShape ; # memberShape-node
+ ] ;
+ sh:property [
+ sh:path sh:minListLength ;
+ sh:datatype xsd:integer ; # minListLength-datatype
+ sh:maxCount 1 ; # minListLength-maxCount
+ sh:minInclusive 0 ; # minListLength-minInclusive
+ ] ;
+ sh:property [
+ sh:path sh:maxListLength ;
+ sh:datatype xsd:integer ; # maxListLength-datatype
+ sh:maxCount 1 ; # maxListLength-maxCount
+ sh:minInclusive 0 ; # maxListLength-minInclusive
+ ] ;
+ sh:property [
+ sh:path sh:uniqueMembers ;
+ sh:datatype xsd:boolean ; # uniqList-datatype
+ sh:maxCount 1 ; # uniqList-maxCount
] ;
sh:property [
sh:path sh:nodeKind ;
diff --git a/shacl12-vocabularies/shacl.ttl b/shacl12-vocabularies/shacl.ttl
index 2b26c205..4223b768 100644
--- a/shacl12-vocabularies/shacl.ttl
+++ b/shacl12-vocabularies/shacl.ttl
@@ -907,13 +907,73 @@ sh:MemberShapeConstraintComponent-memberShape
sh:node sh:NodeShape ;
rdfs:isDefinedBy sh: .
+sh:MinListLengthConstraintComponent
+ a sh:ConstraintComponent ;
+ rdfs:label "Minimum list length constraint component"@en ;
+ rdfs:comment "Specifies the minimum length of a SHACL list. A violation is reported if the value is not a valid SHACL list."@en ;
+ sh:parameter sh:MinListLengthConstraintComponent-minListLength ;
+ rdfs:isDefinedBy sh: .
+
+sh:MinListLengthConstraintComponent-minListLength
+ a sh:Parameter ;
+ sh:path sh:minListLength ;
+ sh:datatype xsd:integer ;
+ rdfs:isDefinedBy sh: .
+
+sh:MaxListLengthConstraintComponent
+ a sh:ConstraintComponent ;
+ rdfs:label "Maximum list length constraint component"@en ;
+ rdfs:comment "Specifies the maximum length of a SHACL list. A violation is reported if the value is not a valid SHACL list."@en ;
+ sh:parameter sh:MaxListLengthConstraintComponent-maxListLength ;
+ rdfs:isDefinedBy sh: .
+
+sh:MaxListLengthConstraintComponent-maxListLength
+ a sh:Parameter ;
+ sh:path sh:maxListLength ;
+ sh:datatype xsd:integer ;
+ rdfs:isDefinedBy sh: .
+
+sh:UniqueMembersConstraintComponent
+ a sh:ConstraintComponent ;
+ rdfs:label "Unique list members constraint component"@en ;
+ rdfs:comment "Specifies that all members of a SHACL list must be unique. A violation is reported if the value is not a valid SHACL list."@en ;
+ sh:parameter sh:UniqueMembersConstraintComponent-uniqueMembers ;
+ rdfs:isDefinedBy sh: .
+
+sh:UniqueMembersConstraintComponent-uniqueMembers
+ a sh:Parameter ;
+ sh:path sh:uniqueMembers ;
+ sh:datatype xsd:boolean ;
+ rdfs:isDefinedBy sh: .
+
sh:memberShape
a rdf:Property ;
rdfs:label "member shape"@en ;
- rdfs:comment "Specifies the shape that all members of RDF lists must conform to."@en ;
+ rdfs:comment "Specifies the shape that all members of SHACL lists must conform to."@en ;
rdfs:range sh:NodeShape ;
rdfs:isDefinedBy sh: .
+sh:minListLength
+ a rdf:Property ;
+ rdfs:label "Minimum list length"@en ;
+ rdfs:comment "Specifies the minimum length that a given SHACL list must have."@en ;
+ rdfs:range xsd:integer ;
+ rdfs:isDefinedBy sh: .
+
+sh:maxListLength
+ a rdf:Property ;
+ rdfs:label "Maximum list length"@en ;
+ rdfs:comment "Specifies the maximum length that a given SHACL list must have."@en ;
+ rdfs:range xsd:integer ;
+ rdfs:isDefinedBy sh: .
+
+sh:uniqueMembers
+ a rdf:Property ;
+ rdfs:label "Unique list members"@en ;
+ rdfs:comment "Specifies that all members of a SHACL list must be unique."@en ;
+ rdfs:range xsd:boolean ;
+ rdfs:isDefinedBy sh: .
+
sh:NodeConstraintComponent
a sh:ConstraintComponent ;
rdfs:label "Node constraint component"@en ;