diff --git a/shacl12-core/index.html b/shacl12-core/index.html
index 0c130e0d..b89778b7 100644
--- a/shacl12-core/index.html
+++ b/shacl12-core/index.html
@@ -2751,7 +2751,7 @@
+ sh:memberShape
+
+ sh:memberShape
requires that each value node be a SHACL list ,
+ and that each member of that list conforms 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
value of
sh:memberShape
.
+ Each
value node v
must be a
SHACL list - a
failure MUST be produced if
v
is not a SHACL list.
+ For each member
m
of the list
v
+ a
failure MUST be produced if the
conformance checking of
m
against
$memberShape
produces a
failure .
+ There MUST be at most one
validation result for each list
v
and set of triggering constraints.
+
+ The
sh:detail
property SHOULD be used to specify the validation results for each member
m
of the list that fails conformance checking.
+
m
SHOULD be used as the
sh:focusNode
for each
sh:detail
result.
+
+ The
sh:detail
property SHOULD also be used to specify when
v
is not a valid
SHACL list .
+
v
SHOULD be used as the
sh:focusNode
for this
sh:detail
result.
+
+
+ The remainder of this section is informative.
+
+ In the following example, all values of the property ex:speakerOrder
must be RDF lists, and each member of those lists must be an IRI.
+ Note that sh:memberShape
can be used in both property shapes (as shown) and node shapes (where it would apply to the focus node itself).
+
+ Examples of how to generate sh:detail
s can be found in the test cases for sh:memberShape
in the SHACL test suite: memberShape-001.ttl .
+
+
+
+
+ex:AgendaShape
+ a sh:NodeShape ;
+ sh:targetClass ex:Agenda ;
+ sh:property [
+ sh:path ex:speakerOrder ;
+ sh:memberShape [
+ sh:nodeKind sh:IRI ;
+ ] ;
+ sh:maxCount 1 ;
+ ] .
+
+
+
{
+ "@id": "ex:AgendaShape",
+ "@type": "sh:NodeShape",
+ "sh:property": {
+ "sh:maxCount": {
+ "@type": "xsd:integer",
+ "@value": "1"
+ },
+ "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"
+ ]
+ }
+ }
+ ]
+}
+
+
+
+
+[ a sh:ValidationReport ;
+ sh:conforms false ;
+ sh:result [
+ a sh:ValidationResult ;
+ sh:resultSeverity sh:Violation ;
+ sh:focusNode ex:agenda2 ;
+ sh:resultPath ex:speakerOrder ;
+ sh:value _:list2 ;
+ sh:resultMessage "Value does not conform to Shape ex:AgendaShape. See details for more information." ;
+ sh:sourceConstraintComponent sh:MemberShapeConstraintComponent ;
+ sh:sourceShape ex:AgendaShape ;
+ 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 _:b1 ;
+ sh:value "Charlie" ;
+ ] ;
+ ]
+] .
+
+
+
{
+ "@type": "sh:ValidationReport",
+ "sh:conforms": {
+ "@type": "xsd:boolean",
+ "@value": "false"
+ },
+ "sh:result": {
+ "@type": "sh:ValidationResult",
+ "sh:focusNode": {
+ "@id": "ex:agenda2"
+ },
+ "sh:resultMessage": "List member does not have the required node kind sh:IRI.",
+ "sh:resultPath": {
+ "@id": "ex:speakerOrder"
+ },
+ "sh:resultSeverity": {
+ "@id": "sh:Violation"
+ },
+ "sh:sourceConstraintComponent": {
+ "@id": "sh:MemberShapeConstraintComponent"
+ },
+ "sh:sourceShape": {
+ "@id": "ex:AgendaShape"
+ },
+ "sh:value": "Charlie"
+ }
+}
+
+
+
+
+
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 59b174b3..6dd5e465 100644
--- a/shacl12-test-suite/tests/core/node/manifest.ttl
+++ b/shacl12-test-suite/tests/core/node/manifest.ttl
@@ -26,6 +26,7 @@
mf:include ;
mf:include ;
mf:include ;
+ mf:include ;
mf:include ;
mf:include ;
mf:include ;
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..b2518285
--- /dev/null
+++ b/shacl12-test-suite/tests/core/node/memberShape-001.ttl
@@ -0,0 +1,241 @@
+@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:Alice ex:Bob ) ;
+ # 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 ;
+.
+
+_:b1
+ rdf:first ex:Alice ;
+ rdf:rest rdf:nil ;
+ ex:extraProperty "extra" ;
+.
+
+_:b2
+ rdf:first ex:Alice ;
+ rdf:rest (
+ "Bob"
+ )
+.
+
+_:b4
+ rdf:first ex:Alice ;
+.
+
+_:b5
+ rdf:first "Charlie" ;
+ rdf:rest (
+ "Donna"
+ )
+.
+
+_:b6
+ rdf:first "Charlie" ;
+.
+
+_:b8 rdfs:label "Malformed SHACL List" .
+
+_:b7
+ rdf:first "Charlie" ;
+ rdf:rest _:b8 ;
+.
+
+# using _:b9 and _:b10 to test recursive list error
+_:b9
+ rdf:first ex:Alice ;
+ rdf:rest _:b10 ;
+.
+
+_:b10
+ rdf:first "Bob" ;
+ rdf:rest _:b9 .
+
+<>
+ 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 _:b2 ;
+ sh:resultMessage "Value does not conform to Shape ex:IRIListShape. See details for more information." ;
+ 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 ;
+ sh:value "Bob" ;
+ ] ;
+ ] ;
+ sh:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode _:b3 ;
+ sh:resultMessage "Value is a malformed SHACL List." ;
+ 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:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode _:b4 ;
+ sh:resultMessage "Value does not conform to Shape ex:IRIListShape. See details for more information." ;
+ 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:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode _:b5 ;
+ sh:resultMessage "Value does not conform to Shape ex:IRIListShape. See details for more information." ;
+ 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 "Donna" ;
+ sh:resultMessage "Value is not of Node Kind sh:IRI" ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:NodeKindConstraintComponent ;
+ sh:sourceShape ex:IRIShape ;
+ sh:value "Donna" ;
+ ]
+ ] ;
+ 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: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:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode _:b7 ;
+ sh:resultMessage "Value does not conform to Shape ex:IRIListShape. See details for more information." ;
+ 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:result [
+ rdf:type sh:ValidationResult ;
+ sh:focusNode _:b9 ;
+ sh:resultMessage "Value does not conform to Shape ex:IRIListShape. See details for more information." ;
+ 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 ;
+ ] ;
+ ] ;
+ ] ;
+ 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..b6463005 100644
--- a/shacl12-test-suite/tests/core/property/manifest.ttl
+++ b/shacl12-test-suite/tests/core/property/manifest.ttl
@@ -26,6 +26,7 @@
mf:include ;
mf:include ;
mf:include ;
+ mf:include ;
mf:include ;
mf:include ;
mf:include ;
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..40bc2c7b
--- /dev/null
+++ b/shacl12-test-suite/tests/core/property/memberShape-001.ttl
@@ -0,0 +1,89 @@
+@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: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: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 ;
+ sh:value _:bc ;
+ ] ;
+ ] ;
+ ] ;
+ mf:status sht:approved ;
+.
diff --git a/shacl12-vocabularies/shacl-shacl.ttl b/shacl12-vocabularies/shacl-shacl.ttl
index 043cccbb..08c78d98 100644
--- a/shacl12-vocabularies/shacl-shacl.ttl
+++ b/shacl12-vocabularies/shacl-shacl.ttl
@@ -72,10 +72,11 @@ 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: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 +224,10 @@ 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: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..2b26c205 100644
--- a/shacl12-vocabularies/shacl.ttl
+++ b/shacl12-vocabularies/shacl.ttl
@@ -894,6 +894,25 @@ 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: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:range sh:NodeShape ;
+ rdfs:isDefinedBy sh: .
sh:NodeConstraintComponent
a sh:ConstraintComponent ;