Skip to content

Issue 189: Add sh:nodeByExpression Constraint Component #408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: gh-pages
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion shacl12-core/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2520,6 +2520,7 @@ <h2>Node Expressions</h2>
<ul>
<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>
<li>At <a href="#targetNode"><code>sh:targetNode</code></a> to dynamically compute the targets of a shape.</li>
<li>At <a href="#NodeByExpressionConstraintComponent"><code>sh:nodeByExpression</code></a> to validate nodes against a dynamically computed set of node shapes.</li>
<li>At <a href="#ExpressionConstraintComponent"><code>sh:expression</code></a> to validate nodes against a condition.</li>
<li>At <a href="#deactivated"><code>sh:deactivated</code></a> to deactivate certain shapes under specific conditions.</li>
</ul>
Expand Down Expand Up @@ -2806,7 +2807,7 @@ <h3>Conformance Checking</h3>
has been reported by it.
</p>
<p>
<dfn>Conformance checking</dfn> produces <code>true</code> if and only if a given <a>focus node</a>
<dfn data-lt="conformance check">Conformance checking</dfn> produces <code>true</code> if and only if a given <a>focus node</a>
<a>conforms</a> to a given <a>shape</a>, and <code>false</code> otherwise.
</p>
<p id="conformance-nested">
Expand Down Expand Up @@ -6074,6 +6075,62 @@ <h4>sh:reifierShape, sh:reificationRequired</h4>
</div>
</aside>
</section>

<section id="NodeByExpressionConstraintComponent">
<h4>sh:nodeByExpression</h4>
<p>
<code>sh:nodeByExpression</code> specifies the condition that each <a>value node</a> conforms to the
<a>node shapes</a> produced by a <a>node expression</a>.
The evaluation of these node expressions is repeated for all <a>value nodes</a> of the <a>shape</a>
as the <a>focus node</a>.
</p>
<p>
<span class="component-class">Constraint Component IRI</span>: <code>sh:NodeByExpressionConstraintComponent</code>
</p>

<div class="parameters">Parameters:</div>
<table class="term-table">
<tr>
<th>Property</th>
<th>Summary and Syntax Rules</th>
</tr>
<tr>
<td><code>sh:nodeByExpression</code></td>
<td>
The <a>node shapes</a> that all value nodes need to conform to.
<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>
</td>
</tr>
</table>
<div class="def def-text">
<div class="def-header">TEXTUAL DEFINITION</div>
<div class="def-text-body" data-validator="NodeByExpression">
Let <code>$expr</code> be a <a>value</a> of <code>sh:nodeByExpression</code>.
For each <a>value node</a> <code>v</code>: perform a <a>conformance check</a> of
<code>v</code> against each <a>output node</a> of <code>evalExpr(expr,
<a>data graph</a>, v, {})</code> <code>s</code>. A <a>failure</a>
MUST be produced if the <a>conformance check</a> of <code>v</code> against
<code>s</code> produces a <a>failure</a>. Otherwise, if <code>v</code> does
not <a>conform</a> to <code>s</code>, there is a <a>validation result</a>
with <code>v</code> as <code>sh:value</code> and a <a>deep copy</a> of
<code>s</code> as <code>sh:sourceConstraint</code>.
</div>
</div>
<p><em>The remainder of this section is informative.</em></p>
<p>
<code>sh:nodeByExpression</code> functions similarly to <code>sh:node</code>, but instead of referencing a fixed <a>node shape</a>,
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.
</p>
<p>
Note that `sh:node` and `sh:nodeByExpression` exhibit the same behavior when given a <a>value</a> that is an <a>IRI</a> of a <a>node shape</a>.
In this case, `sh:node` directly validates against the specified <a>node shape</a>, whereas `sh:nodeByExpression` interprets the <a>IRI</a>
as an <a>IRI expression</a> that evaluates to a set containing the same <a>node shape</a>.
</p>
<p>
For a simple example of its use, refer to the example for <a href="#NodeConstraintComponent"><code>sh:node</code></a>,
replacing `sh:node` with `sh:nodeByExpression`.
</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An example at this point showing where it is different to sh:node would be useful.

Copy link
Author

@mgberg mgberg Jun 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An example was removed as per the discussion above (here and here).

There are two differences between sh:nodeByExpression and sh:node within the scope of the core spec:

  1. sh:nodeByExpression cannot take a node shape that is a blank node as sh:node can, as a blank node would be interpreted as a Node Expression.
  2. Results generated by sh:nodeByExpression additionally include a value for sh:sourceConstraint.

The example I originally included was essentially a duplicate of the example for sh:node with the constraint component replaced and a value for sh:sourceConstraint in the example result. @HolgerKnublauch suggested I remove it and wait to put a more comprehensive example in the Node Expression spec.

I can certainly revert that change if it is desired to have that example back.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation.

As a general comment, I'm not sure we are helping users by not including examples that show clear node expressions in Core but that's not for this PR.

(Do people really think there will be common static only SHACL 1.2 implementations?)

</section>
</section>

<section id="core-components-others">
Expand Down Expand Up @@ -6975,6 +7032,7 @@ <h2>Changes between SHACL 1.0 Core and SHACL 1.2 Core</h2>
<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>
<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>
<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>
<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>
<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>
<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>
</ul>
Expand Down
1 change: 1 addition & 0 deletions shacl12-test-suite/tests/core/node/manifest.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
mf:include <minInclusive-003.ttl> ;
mf:include <minLength-001.ttl> ;
mf:include <node-001.ttl> ;
mf:include <nodeByExpression-001.ttl> ;
mf:include <nodeKind-001.ttl> ;
mf:include <not-001.ttl> ;
mf:include <not-002.ttl> ;
Expand Down
59 changes: 59 additions & 0 deletions shacl12-test-suite/tests/core/node/nodeByExpression-001.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
@prefix dash: <http://datashapes.org/dash#> .
@prefix ex: <http://datashapes.org/sh/tests/core/node/nodeByExpression-001.test#> .
@prefix mf: <http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix sht: <http://www.w3.org/ns/shacl-test#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

ex:InvalidInstance
rdf:type ex:TestClass ;
rdfs:label "Invalid instance" ;
.
ex:TestClass
rdf:type rdfs:Class ;
rdf:type sh:NodeShape ;
rdfs:label "Test class" ;
rdfs:subClassOf rdfs:Resource ;
# Only using an IRI Expression here because Core doesn't define interesting node expressions
sh:nodeByExpression ex:TestNodeShape ;
.
ex:TestNodeShape
rdf:type sh:NodeShape ;
sh:class ex:OtherClass ;
.
ex:ValidInstance
rdf:type ex:OtherClass ;
rdf:type ex:TestClass ;
rdfs:label "Valid instance" ;
.
<>
rdf:type mf:Manifest ;
mf:entries (
<nodeByExpression-001>
) ;
.
<nodeByExpression-001>
rdf:type sht:Validate ;
rdfs:label "Test of sh:nodeByExpression at 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:InvalidInstance ;
sh:resultSeverity sh:Violation ;
sh:sourceConstraint ex:TestNodeShape ;
sh:sourceConstraintComponent sh:NodeConstraintComponent ;
sh:sourceShape ex:TestClass ;
sh:value ex:InvalidInstance ;
] ;
] ;
mf:status sht:approved ;
.
1 change: 1 addition & 0 deletions shacl12-test-suite/tests/core/property/manifest.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
mf:include <minLength-001.ttl> ;
mf:include <node-001.ttl> ;
mf:include <node-002.ttl> ;
mf:include <nodeByExpression-001.ttl> ;
mf:include <nodeKind-001.ttl> ;
mf:include <not-001.ttl> ;
mf:include <or-001.ttl> ;
Expand Down
117 changes: 117 additions & 0 deletions shacl12-test-suite/tests/core/property/nodeByExpression-001.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
@prefix dash: <http://datashapes.org/dash#> .
@prefix ex: <http://datashapes.org/sh/tests/core/property/nodeByExpression-001.test#> .
@prefix mf: <http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix sht: <http://www.w3.org/ns/shacl-test#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

ex:Anon
rdf:type ex:Person ;
ex:firstName "Anon" ;
.
ex:AssignedToShape
rdf:type sh:NodeShape ;
rdfs:comment "All assignees must have an email and a last name." ;
sh:property [
sh:path ex:email ;
sh:maxCount 1 ;
sh:minCount 1 ;
] ;
sh:property [
sh:path ex:lastName ;
sh:maxCount 1 ;
sh:minCount 1 ;
] ;
.
ex:Issue
rdf:type rdfs:Class ;
rdf:type sh:NodeShape ;
rdfs:label "Issue" ;
rdfs:subClassOf rdfs:Resource ;
sh:property ex:Issue-assignedTo ;
sh:property ex:Issue-submittedBy ;
.
ex:Issue-assignedTo
sh:path ex:assignedTo ;
sh:class ex:Person ;
# Only using an IRI Expression here because Core doesn't define interesting node expressions
sh:nodeByExpression ex:AssignedToShape ;
.
ex:Issue-submittedBy
sh:path ex:submittedBy ;
sh:class ex:Person ;
sh:minCount 1 ;
.
ex:Issue_1
rdf:type ex:Issue ;
ex:assignedTo ex:Anon ;
ex:submittedBy ex:Anon ;
rdfs:label "Issue 1" ;
.
ex:Issue_2
rdf:type ex:Issue ;
ex:assignedTo ex:JohnDoeWithEmail ;
ex:submittedBy ex:Anon ;
rdfs:label "Issue 2" ;
.
ex:JohnDoeWithEmail
rdf:type ex:Person ;
ex:email "[email protected]" ;
ex:firstName "John" ;
ex:lastName "Doe" ;
.
ex:Person
rdf:type rdfs:Class ;
rdf:type sh:NodeShape ;
rdfs:label "Person" ;
rdfs:subClassOf rdfs:Resource ;
sh:property [
sh:path ex:email ;
ex:datatype xsd:string ;
rdfs:label "email" ;
] ;
sh:property [
sh:path ex:firstName ;
rdfs:label "first name" ;
sh:datatype xsd:string ;
sh:maxCount 1 ;
sh:minCount 1 ;
] ;
sh:property [
sh:path ex:lastName ;
rdfs:label "last name" ;
sh:datatype xsd:string ;
] ;
.
<>
rdf:type mf:Manifest ;
mf:entries (
<nodeByExpression-001>
) ;
.
<nodeByExpression-001>
rdf:type sht:Validate ;
rdfs:label "Test of sh:nodeByExpression 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:Issue_1 ;
sh:resultPath ex:assignedTo ;
sh:resultSeverity sh:Violation ;
sh:sourceConstraint ex:AssignedToShape ;
sh:sourceConstraintComponent sh:NodeConstraintComponent ;
sh:sourceShape ex:Issue-assignedTo ;
sh:value ex:Anon ;
] ;
] ;
mf:status sht:approved ;
.
20 changes: 20 additions & 0 deletions shacl12-vocabularies/shacl.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,26 @@ sh:node
rdfs:isDefinedBy sh: .


sh:NodeByExpressionConstraintComponent
a sh:ConstraintComponent ;
rdfs:label "Node by expression constraint component"@en ;
rdfs:comment "A constraint component that can be used to verify that all value nodes conform to the node shape(s) produced by the given node expression."@en ;
sh:parameter sh:NodeByExpressionConstraintComponent-nodeByExpression ;
rdfs:isDefinedBy sh: .

sh:NodeByExpressionConstraintComponent-nodeByExpression
a sh:Parameter ;
sh:path sh:nodeByExpression ;
rdfs:isDefinedBy sh: .

sh:nodeByExpression
a rdf:Property ;
rdfs:label "node by expression"@en ;
rdfs:comment "Links a shape to node expressions, indicating the node shape(s) that all value nodes must conform to."@en ;
# rdfs:range sh:NodeShape ; (node expression)
rdfs:isDefinedBy sh: .


sh:NodeKindConstraintComponent
a sh:ConstraintComponent ;
rdfs:label "Node-kind constraint component"@en ;
Expand Down