Skip to content

Commit 73e80c4

Browse files
authored
Merge pull request #408 from w3c/issue-189
Issue 189: Add `sh:nodeByExpression` Constraint Component
2 parents b84f651 + 6376bfa commit 73e80c4

File tree

6 files changed

+434
-1
lines changed

6 files changed

+434
-1
lines changed

shacl12-core/index.html

Lines changed: 236 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2531,6 +2531,7 @@ <h2>Node Expressions</h2>
25312531
<ul>
25322532
<li>At <a href="#property-shapes"><code>sh:values</code> and <code>sh:defaultValue</code></a> to derive the value nodes of a property shape.</li>
25332533
<li>At <a href="#targetNode"><code>sh:targetNode</code></a> to dynamically compute the targets of a shape.</li>
2534+
<li>At <a href="#NodeByExpressionConstraintComponent"><code>sh:nodeByExpression</code></a> to validate nodes against a dynamically computed set of node shapes.</li>
25342535
<li>At <a href="#ExpressionConstraintComponent"><code>sh:expression</code></a> to validate nodes against a condition.</li>
25352536
<li>At <a href="#deactivated"><code>sh:deactivated</code></a> to deactivate certain shapes under specific conditions.</li>
25362537
</ul>
@@ -2821,7 +2822,7 @@ <h3>Conformance Checking</h3>
28212822
has been reported by it.
28222823
</p>
28232824
<p>
2824-
<dfn>Conformance checking</dfn> produces <code>true</code> if and only if a given <a>focus node</a>
2825+
<dfn data-lt="conformance check">Conformance checking</dfn> produces <code>true</code> if and only if a given <a>focus node</a>
28252826
<a>conforms</a> to a given <a>shape</a>, and <code>false</code> otherwise.
28262827
</p>
28272828
<p id="conformance-nested">
@@ -6619,6 +6620,239 @@ <h4>sh:reifierShape, sh:reificationRequired</h4>
66196620
}
66206621
}
66216622
}
6623+
}</pre>
6624+
</div>
6625+
</div>
6626+
</aside>
6627+
</section>
6628+
6629+
<section id="NodeByExpressionConstraintComponent">
6630+
<h4>sh:nodeByExpression</h4>
6631+
<p>
6632+
<code>sh:nodeByExpression</code> specifies the condition that each <a>value node</a> conforms to the
6633+
<a>node shapes</a> produced by a <a>node expression</a>.
6634+
The evaluation of these node expressions is repeated for all <a>value nodes</a> of the <a>shape</a>
6635+
as the <a>focus node</a>.
6636+
</p>
6637+
<p>
6638+
<span class="component-class">Constraint Component IRI</span>: <code>sh:NodeByExpressionConstraintComponent</code>
6639+
</p>
6640+
6641+
<div class="parameters">Parameters:</div>
6642+
<table class="term-table">
6643+
<tr>
6644+
<th>Property</th>
6645+
<th>Summary and Syntax Rules</th>
6646+
</tr>
6647+
<tr>
6648+
<td><code>sh:nodeByExpression</code></td>
6649+
<td>
6650+
The <a>node shapes</a> that all value nodes need to conform to.
6651+
<span data-syntax-rule="nodeByExpression-scope">The <a>values</a> of <code>sh:nodeByExpression</code> in a shape must be <a>well-formed</a> <a>node expressions</a>.</span>
6652+
</td>
6653+
</tr>
6654+
</table>
6655+
<div class="def def-text">
6656+
<div class="def-header">TEXTUAL DEFINITION</div>
6657+
<div class="def-text-body" data-validator="NodeByExpression">
6658+
Let <code>$expr</code> be a <a>value</a> of <code>sh:nodeByExpression</code>.
6659+
For each <a>value node</a> <code>v</code>: perform a <a>conformance check</a> of
6660+
<code>v</code> against each <a>output node</a> of <code>evalExpr(expr,
6661+
<a>data graph</a>, v, {})</code> <code>s</code>. A <a>failure</a>
6662+
MUST be produced if the <a>conformance check</a> of <code>v</code> against
6663+
<code>s</code> produces a <a>failure</a>. Otherwise, if <code>v</code> does
6664+
not <a>conform</a> to <code>s</code>, there is a <a>validation result</a>
6665+
with <code>v</code> as <code>sh:value</code> and a <a>deep copy</a> of
6666+
<code>s</code> as <code>sh:sourceConstraint</code>.
6667+
</div>
6668+
</div>
6669+
<p><em>The remainder of this section is informative.</em></p>
6670+
<p>
6671+
<code>sh:nodeByExpression</code> functions similarly to <code>sh:node</code>, but instead of referencing a fixed <a>node shape</a>,
6672+
a referenced <a>node expression</a> is used to dynamically compute the set of <a>node shapes</a> to which each <a>value node</a> must conform.
6673+
</p>
6674+
<p>
6675+
There are three key differences between <code>sh:nodeByExpression</code> and <a href="#NodeConstraintComponent"><code>sh:node</code></a>:
6676+
<ol>
6677+
<li>
6678+
<code>sh:nodeByExpression</code> references a <a>node expression</a> instead of a fixed <a>node shape</a> as <code>sh:node</code> does.
6679+
</li>
6680+
<li>
6681+
<code>sh:nodeByExpression</code> cannot reference a <a>node shape</a> that is a <a>blank node</a> as a value like <code>sh:node</code> can,
6682+
as a <a>blank node</a> would be interpreted as a <a>node expression</a>.
6683+
</li>
6684+
<li>
6685+
<a>Results</a> generated by <code>sh:nodeByExpression</code> additionally include a value for `sh:sourceConstraint`.
6686+
</li>
6687+
6688+
</ol>
6689+
</p>
6690+
<p>
6691+
Note that <code>sh:node</code> and <code>sh:nodeByExpression</code> exhibit the same behavior when given a <a>value</a> that is an <a>IRI</a> of a <a>node shape</a>.
6692+
In this case, <code>sh:node</code> directly validates against the specified <a>node shape</a>, whereas <code>sh:nodeByExpression</code> interprets the <a>IRI</a>
6693+
as an <a>IRI expression</a> that evaluates to a set containing the same <a>node shape</a>.
6694+
</p>
6695+
<p>
6696+
In the following example, all values of the property <code>ex:address</code> must fulfill the
6697+
constraints expressed by the <a>shape</a> <code>ex:AddressShape</code>.
6698+
</p>
6699+
<aside class="example">
6700+
<div class="shapes-graph">
6701+
<div class="turtle">
6702+
ex:AddressShape
6703+
a sh:NodeShape ;
6704+
sh:property [
6705+
sh:path ex:postalCode ;
6706+
sh:datatype xsd:string ;
6707+
sh:maxCount 1 ;
6708+
] .
6709+
6710+
ex:PersonShape
6711+
a sh:NodeShape ;
6712+
<span class="target-can-be-skipped">sh:targetClass ex:Person ;</span>
6713+
sh:property [ # _:b1
6714+
sh:path ex:address ;
6715+
sh:minCount 1 ;
6716+
sh:nodeByExpression ex:AddressShape ;
6717+
] .
6718+
</div>
6719+
<div class="jsonld">
6720+
<pre class="jsonld">{
6721+
"@graph": [
6722+
{
6723+
"@id": "ex:AddressShape",
6724+
"@type": "sh:NodeShape",
6725+
"sh:property": {
6726+
"sh:datatype": {
6727+
"@id": "xsd:string"
6728+
},
6729+
"sh:maxCount": {
6730+
"@type": "xsd:integer",
6731+
"@value": "1"
6732+
},
6733+
"sh:path": {
6734+
"@id": "ex:postalCode"
6735+
}
6736+
}
6737+
},
6738+
{
6739+
"@id": "ex:PersonShape",
6740+
"@type": "sh:NodeShape",
6741+
"sh:property": {
6742+
"sh:minCount": {
6743+
"@type": "xsd:integer",
6744+
"@value": "1"
6745+
},
6746+
"sh:nodeByExpression": {
6747+
"@id": "ex:AddressShape"
6748+
},
6749+
"sh:path": {
6750+
"@id": "ex:address"
6751+
}
6752+
},
6753+
"sh:targetClass": {
6754+
"@id": "ex:Person"
6755+
}
6756+
}
6757+
]
6758+
}</pre>
6759+
</div>
6760+
</div>
6761+
<div class="data-graph">
6762+
<div class="turtle">
6763+
ex:Bob a ex:Person ;
6764+
ex:address ex:BobsAddress .
6765+
6766+
ex:BobsAddress
6767+
ex:postalCode "1234" .
6768+
6769+
<span class="focus-node-error">ex:Reto</span> a ex:Person ;
6770+
ex:address ex:RetosAddress .
6771+
6772+
ex:RetosAddress
6773+
ex:postalCode 5678 .
6774+
</div>
6775+
<div class="jsonld">
6776+
<pre class="jsonld">{
6777+
"@graph": [
6778+
{
6779+
"@id": "ex:Bob",
6780+
"@type": "ex:Person",
6781+
"ex:address": {
6782+
"@id": "ex:BobsAddress"
6783+
}
6784+
},
6785+
{
6786+
"@id": "ex:BobsAddress",
6787+
"ex:postalCode": "1234"
6788+
},
6789+
{
6790+
"@id": "ex:Reto",
6791+
"@type": "ex:Person",
6792+
"ex:address": {
6793+
"@id": "ex:RetosAddress"
6794+
}
6795+
},
6796+
{
6797+
"@id": "ex:RetosAddress",
6798+
"ex:postalCode": {
6799+
"@type": "xsd:integer",
6800+
"@value": "5678"
6801+
}
6802+
}
6803+
]
6804+
}</pre>
6805+
</div>
6806+
</div>
6807+
<div class="results-graph">
6808+
<div class="turtle">
6809+
[ a sh:ValidationReport ;
6810+
sh:conforms false ;
6811+
sh:result [
6812+
a sh:ValidationResult ;
6813+
sh:resultSeverity sh:Violation ;
6814+
sh:focusNode ex:Reto ;
6815+
sh:resultPath ex:address ;
6816+
sh:value ex:RetosAddress ;
6817+
sh:resultMessage "Value does not conform to shape ex:AddressShape." ;
6818+
sh:sourceConstraint ex:AddressShape ;
6819+
sh:sourceConstraintComponent sh:NodeByExpressionConstraintComponent ;
6820+
sh:sourceShape _:b1 ;
6821+
]
6822+
] .
6823+
</div>
6824+
<div class="jsonld">
6825+
<pre class="jsonld">{
6826+
"@type": "sh:ValidationReport",
6827+
"sh:conforms": {
6828+
"@type": "xsd:boolean",
6829+
"@value": "false"
6830+
},
6831+
"sh:result": {
6832+
"@type": "sh:ValidationResult",
6833+
"sh:focusNode": {
6834+
"@id": "ex:Reto"
6835+
},
6836+
"sh:resultMessage": "Value does not conform to shape ex:AddressShape.",
6837+
"sh:resultPath": {
6838+
"@id": "ex:address"
6839+
},
6840+
"sh:resultSeverity": {
6841+
"@id": "sh:Violation"
6842+
},
6843+
"sh:sourceConstraint": {
6844+
"@id": "ex:AddressShape"
6845+
},
6846+
"sh:sourceConstraintComponent": {
6847+
"@id": "sh:NodeByExpressionConstraintComponent"
6848+
},
6849+
"sh:sourceShape": {
6850+
"@id": "_:b66_b1"
6851+
},
6852+
"sh:value": {
6853+
"@id": "ex:RetosAddress"
6854+
}
6855+
}
66226856
}</pre>
66236857
</div>
66246858
</div>
@@ -7525,6 +7759,7 @@ <h2>Changes between SHACL 1.0 Core and SHACL 1.2 Core</h2>
75257759
<li>Added the new class <a href="#ShapeClass"><code>sh:ShapeClass</code></a> for implicit class targets; see <a href="https://github.com/w3c/data-shapes/issues/212">Issue 212</a></li>
75267760
<li>Moved SPARQL-based validators from Core to an Appendix of SHACL-SPARQL; see <a href="https://github.com/w3c/data-shapes/issues/271">Issue 271</a></li>
75277761
<li>Added the new constraint component <a href="#ExpressionConstraintComponent"><code>sh:expression</code></a>; see <a href="https://github.com/w3c/data-shapes/issues/357">Issue 357</a></li>
7762+
<li>Added the new constraint component <a href="#NodeByExpressionConstraintComponent"><code>sh:nodeByExpression</code></a>, see <a href="https://github.com/w3c/data-shapes/issues/408">Issue 408</a></li>
75287763
<li>Added the new value <code>sh:ByTypes</code> for <a href="#ClosedConstraintComponent"><code>sh:closed</code></a>; see <a href="https://github.com/w3c/data-shapes/issues/172">Issue 172</a></li>
75297764
<li>The values of <a href="#ClassConstraintComponent"><code>sh:class</code></a> and <a href="#DatatypeConstraintComponent"><code>sh:datatype</code></a> can now also be lists, indicating a union of choices; see <a href="https://github.com/w3c/data-shapes/issues/160">Issue 160</a></li>
75307765
</ul>

shacl12-test-suite/tests/core/node/manifest.ttl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
mf:include <minLength-001.ttl> ;
3636
mf:include <minListLength-001.ttl> ;
3737
mf:include <node-001.ttl> ;
38+
mf:include <nodeByExpression-001.ttl> ;
3839
mf:include <nodeKind-001.ttl> ;
3940
mf:include <not-001.ttl> ;
4041
mf:include <not-002.ttl> ;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
@prefix dash: <http://datashapes.org/dash#> .
2+
@prefix ex: <http://datashapes.org/sh/tests/core/node/nodeByExpression-001.test#> .
3+
@prefix mf: <http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#> .
4+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
5+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
6+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
7+
@prefix sh: <http://www.w3.org/ns/shacl#> .
8+
@prefix sht: <http://www.w3.org/ns/shacl-test#> .
9+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
10+
11+
ex:InvalidInstance
12+
rdf:type ex:TestClass ;
13+
rdfs:label "Invalid instance" ;
14+
.
15+
ex:TestClass
16+
rdf:type rdfs:Class ;
17+
rdf:type sh:NodeShape ;
18+
rdfs:label "Test class" ;
19+
rdfs:subClassOf rdfs:Resource ;
20+
# Only using an IRI Expression here because Core doesn't define interesting node expressions
21+
sh:nodeByExpression ex:TestNodeShape ;
22+
.
23+
ex:TestNodeShape
24+
rdf:type sh:NodeShape ;
25+
sh:class ex:OtherClass ;
26+
.
27+
ex:ValidInstance
28+
rdf:type ex:OtherClass ;
29+
rdf:type ex:TestClass ;
30+
rdfs:label "Valid instance" ;
31+
.
32+
<>
33+
rdf:type mf:Manifest ;
34+
mf:entries (
35+
<nodeByExpression-001>
36+
) ;
37+
.
38+
<nodeByExpression-001>
39+
rdf:type sht:Validate ;
40+
rdfs:label "Test of sh:nodeByExpression at node shape 001" ;
41+
mf:action [
42+
sht:dataGraph <> ;
43+
sht:shapesGraph <> ;
44+
] ;
45+
mf:result [
46+
rdf:type sh:ValidationReport ;
47+
sh:conforms "false"^^xsd:boolean ;
48+
sh:result [
49+
rdf:type sh:ValidationResult ;
50+
sh:focusNode ex:InvalidInstance ;
51+
sh:resultSeverity sh:Violation ;
52+
sh:sourceConstraint ex:TestNodeShape ;
53+
sh:sourceConstraintComponent sh:NodeConstraintComponent ;
54+
sh:sourceShape ex:TestClass ;
55+
sh:value ex:InvalidInstance ;
56+
] ;
57+
] ;
58+
mf:status sht:approved ;
59+
.

shacl12-test-suite/tests/core/property/manifest.ttl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
mf:include <minListLength-001.ttl> ;
3737
mf:include <node-001.ttl> ;
3838
mf:include <node-002.ttl> ;
39+
mf:include <nodeByExpression-001.ttl> ;
3940
mf:include <nodeKind-001.ttl> ;
4041
mf:include <not-001.ttl> ;
4142
mf:include <or-001.ttl> ;

0 commit comments

Comments
 (0)