From e3776021784c67b20b2f4e5fc430bf5ba3718b31 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 19 Jun 2025 19:43:20 -0400 Subject: [PATCH 1/7] 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 0c130e0d..bc7c2e8b 100644 --- a/shacl12-core/index.html +++ b/shacl12-core/index.html @@ -2520,6 +2520,7 @@

Node Expressions

@@ -6074,6 +6075,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. +

+ +
@@ -6976,6 +7191,7 @@

Changes between SHACL 1.0 Core and SHACL 1.2 Core

  • 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
  • The values of sh:class and sh:datatype can now also be lists, indicating a union of choices; see Issue 160
  • From 244e35701f5a154e18a2f87319ea2b8eb2a9b703 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 19 Jun 2025 19:44:30 -0400 Subject: [PATCH 2/7] 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 67ee87b3..d889b971 100644 --- a/shacl12-vocabularies/shacl.ttl +++ b/shacl12-vocabularies/shacl.ttl @@ -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 ; From b210cc7aaeccdd5fb5452855b8d77c1e03681d55 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 19 Jun 2025 19:45:53 -0400 Subject: [PATCH 3/7] 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 59b174b3..16be05e8 100644 --- a/shacl12-test-suite/tests/core/node/manifest.ttl +++ b/shacl12-test-suite/tests/core/node/manifest.ttl @@ -32,6 +32,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 2ab26040..50991ab0 100644 --- a/shacl12-test-suite/tests/core/property/manifest.ttl +++ b/shacl12-test-suite/tests/core/property/manifest.ttl @@ -33,6 +33,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 7725b2faff43dd89598c7bb39de960ba57848530 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/7] 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 bc7c2e8b..5cd922d7 100644 --- a/shacl12-core/index.html +++ b/shacl12-core/index.html @@ -6105,12 +6105,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 22d05a9b3cbdbf1e83e98e5e3b5aab8e9bccfde2 Mon Sep 17 00:00:00 2001 From: Matt Goldberg Date: Wed, 25 Jun 2025 08:26:38 -0400 Subject: [PATCH 5/7] 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 5cd922d7..39593773 100644 --- a/shacl12-core/index.html +++ b/shacl12-core/index.html @@ -2807,7 +2807,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.

    @@ -6106,10 +6106,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 @@ -6128,170 +6127,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`.

    - @@ -7194,8 +7032,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 17dd71b4a160d9a210d856af4f7d95fa3cd02396 Mon Sep 17 00:00:00 2001 From: Matt Goldberg Date: Wed, 25 Jun 2025 08:26:58 -0400 Subject: [PATCH 6/7] 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 7d4abd61847f9208ce41720fd42ad12680bdb347 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/7] 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 39593773..354d4af2 100644 --- a/shacl12-core/index.html +++ b/shacl12-core/index.html @@ -6127,8 +6127,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`.