From ef4d05bd7c69507a3803db9c0d894d2200d91b51 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 19 Jun 2025 19:43:20 -0400 Subject: [PATCH 1/9] Add sh:nodeByExpression to spec --- shacl12-core/index.html | 216 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) diff --git a/shacl12-core/index.html b/shacl12-core/index.html index 1fce7e21..8c0c6359 100644 --- a/shacl12-core/index.html +++ b/shacl12-core/index.html @@ -2531,6 +2531,7 @@

Node Expressions

@@ -6619,6 +6620,220 @@

sh:reifierShape, sh:reificationRequired

} } } +} + + + + + +
+

sh:nodeByExpression

+

+ sh:nodeByExpression specifies the condition that each value node conforms to the + node shapes produced by a node expression. + The evaluation of these node expressions is repeated for all value nodes of the shape + as the focus node. +

+

+ Constraint Component IRI: sh:NodeByExpressionConstraintComponent +

+ +
Parameters:
+ + + + + + + + + +
PropertySummary and Syntax Rules
sh:nodeByExpression + The node shapes that all value nodes need to conform to. + The values of sh:nodeByExpression in a shape must be well-formed node expressions. +
+
+
TEXTUAL DEFINITION
+
+ Let $expr be a value of sh:nodeByExpression. + For each value node v: perform conformance checking of v against each output node + of evalExpr(expr, data graph, v, {}) s that is a node shape in the shapes graph. + For each conformance check, a failure MUST be produced if the conformance checking of v against s produces a failure. + Otherwise, if v does not conform to s, + there is a validation result with v as sh:value and a deep copy of s as sh:sourceConstraint. +
+
+

The remainder of this section is informative.

+

+ sh:nodeByExpression functions similarly to sh:node, but instead of referencing a fixed node shape, + a referenced node expression is used to dynamically compute the set of node shapes to which each value node must conform. +

+

+ Note that `sh:node` and `sh:nodeByExpression` exhibit the same behavior when given a value that is an IRI of a node shape. + In this case, `sh:node` directly validates against the specified node shape, whereas `sh:nodeByExpression` interprets the IRI + as an IRI expression that evaluates to a set containing the same node shape. +

+

+ In the following example, all values of the property ex:address must fulfill the + constraints expressed by the shape ex:AddressShape. +

+
From a0cd31b4e110c81e846576133e5409075e8d9644 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 19 Jun 2025 19:44:30 -0400 Subject: [PATCH 2/9] Add sh:nodeByExpression to vocabulary --- shacl12-vocabularies/shacl.ttl | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/shacl12-vocabularies/shacl.ttl b/shacl12-vocabularies/shacl.ttl index 4223b768..e0b98adb 100644 --- a/shacl12-vocabularies/shacl.ttl +++ b/shacl12-vocabularies/shacl.ttl @@ -994,6 +994,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 ; From f959d1c23174c3debb5447ceee430b72c1db8392 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 19 Jun 2025 19:45:53 -0400 Subject: [PATCH 3/9] Add test cases for sh:nodeByExpression --- .../tests/core/node/manifest.ttl | 1 + .../tests/core/node/nodeByExpression-001.ttl | 59 +++++++++ .../tests/core/property/manifest.ttl | 1 + .../core/property/nodeByExpression-001.ttl | 117 ++++++++++++++++++ 4 files changed, 178 insertions(+) create mode 100644 shacl12-test-suite/tests/core/node/nodeByExpression-001.ttl create mode 100644 shacl12-test-suite/tests/core/property/nodeByExpression-001.ttl diff --git a/shacl12-test-suite/tests/core/node/manifest.ttl b/shacl12-test-suite/tests/core/node/manifest.ttl index 4cb49515..966bd4f6 100644 --- a/shacl12-test-suite/tests/core/node/manifest.ttl +++ b/shacl12-test-suite/tests/core/node/manifest.ttl @@ -35,6 +35,7 @@ mf:include ; mf:include ; mf:include ; + mf:include ; mf:include ; mf:include ; mf:include ; diff --git a/shacl12-test-suite/tests/core/node/nodeByExpression-001.ttl b/shacl12-test-suite/tests/core/node/nodeByExpression-001.ttl new file mode 100644 index 00000000..53e69ba8 --- /dev/null +++ b/shacl12-test-suite/tests/core/node/nodeByExpression-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: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:node 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 ( + + ) ; +. + + 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 ; +. diff --git a/shacl12-test-suite/tests/core/property/manifest.ttl b/shacl12-test-suite/tests/core/property/manifest.ttl index a0c3e724..8f4c3ced 100644 --- a/shacl12-test-suite/tests/core/property/manifest.ttl +++ b/shacl12-test-suite/tests/core/property/manifest.ttl @@ -36,6 +36,7 @@ mf:include ; mf:include ; mf:include ; + mf:include ; mf:include ; mf:include ; mf:include ; diff --git a/shacl12-test-suite/tests/core/property/nodeByExpression-001.ttl b/shacl12-test-suite/tests/core/property/nodeByExpression-001.ttl new file mode 100644 index 00000000..53ac1cd2 --- /dev/null +++ b/shacl12-test-suite/tests/core/property/nodeByExpression-001.ttl @@ -0,0 +1,117 @@ +@prefix dash: . +@prefix ex: . +@prefix mf: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix sht: . +@prefix xsd: . + +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:node 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 "john@doe.com" ; + 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 ( + + ) ; +. + + 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 ; +. From 23d40574ac1506c1b01572e38dab0a4d58ee5cdc Mon Sep 17 00:00:00 2001 From: Matt Goldberg <59745812+mgberg@users.noreply.github.com> Date: Fri, 20 Jun 2025 19:36:40 -0400 Subject: [PATCH 4/9] Replace "conformance checking" with "conformance check", remove leading spaces for body text Co-authored-by: Ted Thibodeau Jr --- shacl12-core/index.html | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/shacl12-core/index.html b/shacl12-core/index.html index 8c0c6359..2b34a872 100644 --- a/shacl12-core/index.html +++ b/shacl12-core/index.html @@ -6655,12 +6655,16 @@

sh:nodeByExpression

TEXTUAL DEFINITION
- Let $expr be a value of sh:nodeByExpression. - For each value node v: perform conformance checking of v against each output node - of evalExpr(expr, data graph, v, {}) s that is a node shape in the shapes graph. - For each conformance check, a failure MUST be produced if the conformance checking of v against s produces a failure. - Otherwise, if v does not conform to s, - there is a validation result with v as sh:value and a deep copy of s as sh:sourceConstraint. +Let $expr be a value of sh:nodeByExpression. +For each value node v: perform conformance check of +v against each output node of evalExpr(expr, +data graph, v, {}) s that is a node shape +in the shapes graph. For each conformance check, a failure +MUST be produced if the conformance check of v against +s produces a failure. Otherwise, if v does +not conform to s, there is a validation result +with v as sh:value and a deep copy of +s as sh:sourceConstraint.

The remainder of this section is informative.

From 0cb88b4fa39fc734667bd9542a56957a229b28f5 Mon Sep 17 00:00:00 2001 From: Matt Goldberg Date: Wed, 25 Jun 2025 08:26:38 -0400 Subject: [PATCH 5/9] Apply requested spec edits for sh:nodeByExpression section --- shacl12-core/index.html | 174 ++-------------------------------------- 1 file changed, 6 insertions(+), 168 deletions(-) diff --git a/shacl12-core/index.html b/shacl12-core/index.html index 2b34a872..abc89f9b 100644 --- a/shacl12-core/index.html +++ b/shacl12-core/index.html @@ -2822,7 +2822,7 @@

Conformance Checking

has been reported by it.

- Conformance checking produces true if and only if a given focus node + Conformance checking produces true if and only if a given focus node conforms to a given shape, and false otherwise.

@@ -6656,10 +6656,9 @@

sh:nodeByExpression

TEXTUAL DEFINITION
Let $expr be a value of sh:nodeByExpression. -For each value node v: perform conformance check of +For each value node v: perform a conformance check of v against each output node of evalExpr(expr, -data graph, v, {}) s that is a node shape -in the shapes graph. For each conformance check, a failure +data graph, v, {}) s. A failure MUST be produced if the conformance check of v against s produces a failure. Otherwise, if v does not conform to s, there is a validation result @@ -6678,170 +6677,9 @@

sh:nodeByExpression

as an IRI expression that evaluates to a set containing the same node shape.

- In the following example, all values of the property ex:address must fulfill the - constraints expressed by the shape ex:AddressShape. + For a simple usage example, refer to the example for sh:node, + but replace `sh:node` with `sh:nodeByExpression`.

- @@ -7744,8 +7582,8 @@

Changes between SHACL 1.0 Core and SHACL 1.2 Core

  • Added the new class sh:ShapeClass for implicit class targets; see Issue 212
  • Moved SPARQL-based validators from Core to an Appendix of SHACL-SPARQL; see Issue 271
  • Added the new constraint component sh:expression; see Issue 357
  • -
  • Added the new value sh:ByTypes for sh:closed; see Issue 172
  • Added the new constraint component sh:nodeByExpression, see Issue 408
  • +
  • Added the new value sh:ByTypes for sh:closed; see Issue 172
  • The values of sh:class and sh:datatype can now also be lists, indicating a union of choices; see Issue 160
  • From 1672ee960f70a3936027ec5774dca035e83d5496 Mon Sep 17 00:00:00 2001 From: Matt Goldberg Date: Wed, 25 Jun 2025 08:26:58 -0400 Subject: [PATCH 6/9] Apply requested test edits for sh:nodeByExpression --- shacl12-test-suite/tests/core/node/nodeByExpression-001.ttl | 2 +- .../tests/core/property/nodeByExpression-001.ttl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shacl12-test-suite/tests/core/node/nodeByExpression-001.ttl b/shacl12-test-suite/tests/core/node/nodeByExpression-001.ttl index 53e69ba8..b687e6ce 100644 --- a/shacl12-test-suite/tests/core/node/nodeByExpression-001.ttl +++ b/shacl12-test-suite/tests/core/node/nodeByExpression-001.ttl @@ -18,7 +18,7 @@ ex:TestClass rdfs:label "Test class" ; rdfs:subClassOf rdfs:Resource ; # Only using an IRI Expression here because Core doesn't define interesting node expressions - sh:node ex:TestNodeShape ; + sh:nodeByExpression ex:TestNodeShape ; . ex:TestNodeShape rdf:type sh:NodeShape ; diff --git a/shacl12-test-suite/tests/core/property/nodeByExpression-001.ttl b/shacl12-test-suite/tests/core/property/nodeByExpression-001.ttl index 53ac1cd2..9de6e0df 100644 --- a/shacl12-test-suite/tests/core/property/nodeByExpression-001.ttl +++ b/shacl12-test-suite/tests/core/property/nodeByExpression-001.ttl @@ -12,7 +12,7 @@ ex:Anon rdf:type ex:Person ; ex:firstName "Anon" ; . -ex:AssignedToShape ; +ex:AssignedToShape rdf:type sh:NodeShape ; rdfs:comment "All assignees must have an email and a last name." ; sh:property [ @@ -38,7 +38,7 @@ 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:node ex:AssignedToShape ; + sh:nodeByExpression ex:AssignedToShape ; . ex:Issue-submittedBy sh:path ex:submittedBy ; From 401f7c2c277ffc9fa253dabf03c48e69fa3ac4e5 Mon Sep 17 00:00:00 2001 From: Matt Goldberg <59745812+mgberg@users.noreply.github.com> Date: Fri, 27 Jun 2025 21:11:08 -0400 Subject: [PATCH 7/9] Update shacl12-core/index.html Co-authored-by: Ted Thibodeau Jr --- shacl12-core/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shacl12-core/index.html b/shacl12-core/index.html index abc89f9b..650d0d3b 100644 --- a/shacl12-core/index.html +++ b/shacl12-core/index.html @@ -6677,8 +6677,8 @@

    sh:nodeByExpression

    as an IRI expression that evaluates to a set containing the same node shape.

    - For a simple usage example, refer to the example for sh:node, - but replace `sh:node` with `sh:nodeByExpression`. + For a simple example of its use, refer to the example for sh:node, + replacing `sh:node` with `sh:nodeByExpression`.

    From a355688bbfd743fedcbd095aaaf2ddd24c8850c8 Mon Sep 17 00:00:00 2001 From: Matt Goldberg Date: Wed, 9 Jul 2025 13:29:19 -0400 Subject: [PATCH 8/9] Extend sh:nodeByExpression description --- shacl12-core/index.html | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/shacl12-core/index.html b/shacl12-core/index.html index 650d0d3b..7dbc4efe 100644 --- a/shacl12-core/index.html +++ b/shacl12-core/index.html @@ -6672,8 +6672,24 @@

    sh:nodeByExpression

    a referenced node expression is used to dynamically compute the set of node shapes to which each value node must conform.

    - Note that `sh:node` and `sh:nodeByExpression` exhibit the same behavior when given a value that is an IRI of a node shape. - In this case, `sh:node` directly validates against the specified node shape, whereas `sh:nodeByExpression` interprets the IRI + There are three key differences between sh:nodeByExpression and sh:node: +

      +
    1. + sh:nodeByExpression references a node expression instead of a fixed node shape as sh:node does. +
    2. +
    3. + sh:nodeByExpression cannot reference a node shape that is a blank node as a value like sh:node can, + as a blank node would be interpreted as a node expression. +
    4. +
    5. + Results generated by sh:nodeByExpression additionally include a value for `sh:sourceConstraint`. +
    6. + +
    +

    +

    + Note that sh:node and sh:nodeByExpression exhibit the same behavior when given a value that is an IRI of a node shape. + In this case, sh:node directly validates against the specified node shape, whereas sh:nodeByExpression interprets the IRI as an IRI expression that evaluates to a set containing the same node shape.

    From 6376bfab5123f0fb6efe35904ccb330e6d3e4d56 Mon Sep 17 00:00:00 2001 From: Matt Goldberg Date: Wed, 9 Jul 2025 13:30:58 -0400 Subject: [PATCH 9/9] Include sh:nodeByExpression example in the core spec scoped to the core spec --- shacl12-core/index.html | 165 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 2 deletions(-) diff --git a/shacl12-core/index.html b/shacl12-core/index.html index 7dbc4efe..a08ffc43 100644 --- a/shacl12-core/index.html +++ b/shacl12-core/index.html @@ -6693,9 +6693,170 @@

    sh:nodeByExpression

    as an IRI expression that evaluates to a set containing the same node shape.

    - For a simple example of its use, refer to the example for sh:node, - replacing `sh:node` with `sh:nodeByExpression`. + In the following example, all values of the property ex:address must fulfill the + constraints expressed by the shape ex:AddressShape.

    +