diff --git a/shacl12-core/index.html b/shacl12-core/index.html
index 0c130e0d..5b7de87b 100644
--- a/shacl12-core/index.html
+++ b/shacl12-core/index.html
@@ -2751,7 +2751,7 @@
+ 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"
+ ]
+ }
+ }
+ ]
+}
+
+
+
+
+
+
Property Pair Constraint Components
diff --git a/shacl12-test-suite/tests/core/node/manifest.ttl b/shacl12-test-suite/tests/core/node/manifest.ttl
index 59b174b3..4cb49515 100644
--- a/shacl12-test-suite/tests/core/node/manifest.ttl
+++ b/shacl12-test-suite/tests/core/node/manifest.ttl
@@ -26,11 +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 ;
@@ -40,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
new file mode 100644
index 00000000..bb251f2b
--- /dev/null
+++ b/shacl12-test-suite/tests/core/node/memberShape-001.ttl
@@ -0,0 +1,174 @@
+@prefix dash: .
+@prefix ex: .
+@prefix mf: .
+@prefix owl: .
+@prefix rdf: .
+@prefix rdfs: .
+@prefix sh: .
+@prefix sht: .
+@prefix xsd: .
+
+ex:IRIShape a sh:NodeShape ;
+ sh:nodeKind sh:IRI .
+
+ex:IRIListShape
+ rdf:type sh:NodeShape ;
+ sh:memberShape ex:IRIShape ;
+ sh:targetNode ex:list0 ;
+ # Valid empty list
+ 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 ;
+.
+
+ex:list0
+ rdf:first ex:Alice ;
+ rdf:rest (
+ ex:Bob
+ ) ;
+.
+
+ex:list1
+ rdf:first ex:Alice ;
+ rdf:rest rdf:nil ;
+ ex:extraProperty "extra" ;
+.
+
+ex:list2
+ rdf:first ex:Alice ;
+ rdf:rest (
+ "Bob"
+ )
+.
+
+ex:list4
+ rdf:first ex:Alice ;
+.
+
+ex:list5
+ rdf:first "Charlie" ;
+ rdf:rest (
+ "Donna"
+ )
+.
+
+ex:list6
+ rdf:first "Charlie" ;
+.
+
+ex:list8 rdfs:label "Malformed SHACL List" .
+
+ex:list7
+ rdf:first "Charlie" ;
+ rdf:rest ex:list8 ;
+.
+
+# using ex:list9 and ex:list10 to test recursive list error
+ex:list9
+ rdf:first ex:Alice ;
+ rdf:rest ex:list10 ;
+.
+
+ex:list10
+ rdf:first "Bob" ;
+ rdf:rest ex:list9 .
+
+<>
+ rdf:type mf:Manifest ;
+ mf:entries (
+
+ ) ;
+.
+
+
+ rdf:type sht:Validate ;
+ rdfs:label "Test of sh:memberShape 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:list2 ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
+ sh:sourceShape ex:IRIListShape ;
+ sh:value ex:list2 ;
+ sh:detail [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode "Bob" ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:NodeKindConstraintComponent ;
+ sh:sourceShape ex:IRIShape ;
+ sh:value "Bob" ;
+ ] ;
+ ] ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:list3 ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
+ sh:sourceShape ex:IRIListShape ;
+ sh:value ex:list3 ;
+ ] ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:list4 ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
+ sh:sourceShape ex:IRIListShape ;
+ sh:value ex:list4 ;
+ ] ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:list5 ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
+ sh:sourceShape ex:IRIListShape ;
+ sh:value ex:list5 ;
+ sh:detail [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode "Charlie" ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:NodeKindConstraintComponent ;
+ sh:sourceShape ex:IRIShape ;
+ sh:value "Charlie" ;
+ ], [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode "Donna" ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:NodeKindConstraintComponent ;
+ sh:sourceShape ex:IRIShape ;
+ sh:value "Donna" ;
+ ]
+ ] ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:list6 ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
+ sh:sourceShape ex:IRIListShape ;
+ sh:value ex:list6 ;
+ ] ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:list7 ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
+ sh:sourceShape ex:IRIListShape ;
+ sh:value ex:list7 ;
+ ] ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode ex:list9 ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
+ sh:sourceShape ex:IRIListShape ;
+ sh:value ex:list9 ;
+ ] ;
+ ] ;
+ 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..5b38bf2f
--- /dev/null
+++ b/shacl12-test-suite/tests/core/node/minListLength-001.ttl
@@ -0,0 +1,59 @@
+@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:value rdf:nil ;
+ ] ;
+ 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..bc2790dc
--- /dev/null
+++ b/shacl12-test-suite/tests/core/node/uniqueMembers-001.ttl
@@ -0,0 +1,78 @@
+@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:value ex:list1 ;
+ 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 2ab26040..a0c3e724 100644
--- a/shacl12-test-suite/tests/core/property/manifest.ttl
+++ b/shacl12-test-suite/tests/core/property/manifest.ttl
@@ -26,11 +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 ;
@@ -46,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
new file mode 100644
index 00000000..f525f1ec
--- /dev/null
+++ b/shacl12-test-suite/tests/core/property/memberShape-001.ttl
@@ -0,0 +1,87 @@
+@prefix dash: .
+@prefix ex: .
+@prefix mf: .
+@prefix owl: .
+@prefix rdf: .
+@prefix rdfs: .
+@prefix sh: .
+@prefix sht: .
+@prefix xsd: .
+
+ex:IRIShape a sh:NodeShape ;
+ sh:nodeKind sh:IRI .
+
+ex:TestShape
+ rdf:type sh:NodeShape ;
+ sh:targetClass ex:ListSubject ;
+ sh:property ex:TestShape-testProperty ;
+.
+
+ex:TestShape-testProperty
+ sh:path ex:testProperty ;
+ sh:memberShape ex:IRIShape ;
+.
+
+# Valid with all IRIs in the list
+ex:list1
+ rdf:type ex:ListSubject ;
+ ex:testProperty (
+ ex:Alice
+ ex:Bob
+ ex:Charlie
+ ) ;
+.
+
+_:bc rdfs:label "Blank node which triggers violation" .
+
+# Invalid with a blank node in the list
+ex:list2
+ rdfs:label "List with a blank node" ;
+ rdf:type ex:ListSubject ;
+ ex:testProperty _:b1 ;
+.
+
+_:b1
+ rdf:first ex:Bob ;
+ rdf:rest (
+ _:bc
+ )
+.
+
+<>
+ rdf:type mf:Manifest ;
+ mf:entries (
+
+ ) ;
+.
+
+
+ rdf:type sht:Validate ;
+ rdfs:label "Test of sh:memberShape at 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:list2 ;
+ sh:resultPath ex:testProperty ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
+ sh:sourceShape ex:TestShape-testProperty ;
+ sh:value _:b1 ;
+ sh:detail [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode _:bc ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:NodeKindConstraintComponent ;
+ sh:sourceShape ex:IRIShape ;
+ sh:value _:bc ;
+ ] ;
+ ] ;
+ ] ;
+ mf:status sht:approved ;
+.
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 043cccbb..0c808319 100644
--- a/shacl12-vocabularies/shacl-shacl.ttl
+++ b/shacl12-vocabularies/shacl-shacl.ttl
@@ -72,10 +72,12 @@ shsh:ShapeShape
sh:targetSubjectsOf sh:targetClass, sh:targetNode, sh:targetObjectsOf, sh:targetSubjectsOf ;
sh:targetSubjectsOf sh:and, sh:class, sh:closed, sh:datatype, sh:disjoint, sh:equals, sh:flags, sh:hasValue,
sh:ignoredProperties, sh:in, sh:languageIn, sh:lessThan, sh:lessThanOrEquals, sh:maxCount, sh:maxExclusive,
- sh:maxInclusive, sh:maxLength, sh:minCount, sh:minExclusive, sh:minInclusive, sh:minLength, sh:node, sh:nodeKind,
+ 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:uniqueMembers ;
+ sh:targetObjectsOf sh:memberShape ; # memberShape-node
sh:targetObjectsOf sh:node ; # node-node
sh:targetObjectsOf sh:not ; # not-node
sh:targetObjectsOf sh:property ; # property-node
@@ -223,6 +225,27 @@ shsh:ShapeShape
sh:maxCount 1 ; # minLength-maxCount
sh:minInclusive 0 ; # minLength-minInclusive
] ;
+ sh:property [
+ sh:path sh:memberShape ;
+ 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 ; # uniqueMembers-datatype
+ sh:maxCount 1 ; # uniqueMembers-maxCount
+ ] ;
sh:property [
sh:path sh:nodeKind ;
sh:in ( sh:BlankNode sh:IRI sh:Literal sh:BlankNodeOrIRI sh:BlankNodeOrLiteral sh:IRIOrLiteral ) ; # nodeKind-in
diff --git a/shacl12-vocabularies/shacl.ttl b/shacl12-vocabularies/shacl.ttl
index 67ee87b3..4223b768 100644
--- a/shacl12-vocabularies/shacl.ttl
+++ b/shacl12-vocabularies/shacl.ttl
@@ -894,6 +894,85 @@ sh:minLength
rdfs:range xsd:integer ;
rdfs:isDefinedBy sh: .
+sh:MemberShapeConstraintComponent
+ a sh:ConstraintComponent ;
+ rdfs:label "Member shape constraint component"@en ;
+ rdfs:comment "Can be used to specify constraints on the members of a given SHACL list. A violation is reported for each member of the list that does not comply with the constraints specified by the given shape. A violation is reported if the value is not a valid SHACL list."@en ;
+ sh:parameter sh:MemberShapeConstraintComponent-memberShape ;
+ rdfs:isDefinedBy sh: .
+
+sh:MemberShapeConstraintComponent-memberShape
+ a sh:Parameter ;
+ sh:path sh: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 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 ;