Skip to content
2 changes: 1 addition & 1 deletion shacl12-core/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2616,7 +2616,7 @@ <h2>Node Expressions</h2>
During evaluation, the engine can access <a>triples</a> related to <code>expr</code> in the <a>shapes graph</a>.</li>
<li><code>focusGraph</code> is a <a>graph</a>, called the <dfn>focus graph</dfn>. This is the default query graph for the evaluation of the node expression.</li>
<li><code>focusNode</code> is a <a>node</a>, called the <dfn>input focus node</dfn>. This variable may have no value.</li>
<li><code>scope</code> is a map from <a href="https://www.w3.org/TR/sparql12-query/#defn_QueryVariable">variable names</a> to individual <a>nodes</a>.
<li><code>scope</code> is a map from (key) <a>nodes</a> to individual (value) <a>nodes</a>.
The empty map is written as <code>{}</code>.
</li>
</ul>
Expand Down
265 changes: 262 additions & 3 deletions shacl12-node-expr/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2189,6 +2189,265 @@ <h3>InstancesOf Expressions</h3>

</section>

<section id="custom-node-expressions">
<h2>Custom Node Expressions</h2>
<p>
SHACL includes vocabulary terms that can be used to define new <a>node expression functions</a>
by wrapping other (parameterized) <a>node expressions</a>.
This makes it possible to extend the library of available SHACL node expressions without having to
hard-code changes to an engine.
</p>

<section>
<h3>Custom Named Parameter Functions</h3>
<p class="syntax">
<span data-syntax-rule="CustomNamedParameterFunction-syntax">
A <dfn>custom named parameter function</dfn> is an <a>IRI</a> in a <a>shapes graph</a>
that is a <a>SHACL instance</a> of <code>sh:NamedParameterExpressionFunction</code> and
a <a>SHACL subclass</a> of <code>sh:NamedParameterExpression</code>.
It has a single <a>value</a> for <code>sh:bodyExpression</code> that is a <a>well-formed</a>
<a>node expression</a>.
<br/><br/>
A <a>custom named parameter function</a> declares one or more <a>parameters</a> as <a>values</a>
of <code>sh:parameter</code>, where each such <a>parameter</a> has exactly one <a>value</a> for
<code>sh:path</code> and that value is an <a>IRI</a>.
At least one of the parameters has <code>sh:keyParameter true</code>, declaring the <a>key parameters</a>
for the function.
The <a>key parameters</a> of all <a>node expression functions</a>
(including the built-in ones from the <code>shnex:</code> namespace) must be disjoint.
<br/><br/>
<a>Custom named parameter functions</a> can reference the declared <a>parameters</a> using an
<a>arg expression</a> such as <code>[ shnex:arg ex:param ]</code>, where the <a>value</a> of <code>shnex:arg</code>
matches the <a>IRI</a> of the <a>parameter</a>'s <code>sh:path</code>.
</span>
</p>
<p class="syntax">
<span data-syntax-rule="CustomNamedParameterExpression-syntax">
A <dfn>custom named parameter expression</dfn> is a <a>node expression</a>
represented by a <a>blank node</a> that has exactly one <a>value</a> for at least one of
the <a>key parameters</a>.
</span>
</p>
<div class="def" id="CustomNamedParameterExpression-evaluation">
<div class="def-header">EVALUATION OF CUSTOM NAMED PARAMETER EXPRESSIONS</div>
<p>
Let <code>expr</code> be a <a>custom named parameter expression</a> with the
<a>custom named parameter function</a> <code>f</code>.
Let <code>body</code> be the <a>value</a> of <code>sh:bodyExpression</code> at <code>f</code>
in the <a>shapes graph</a>.
<br/><br/>
Let <code>argScope</code> be a map of (parameter) <a>nodes</a> as keys and (argument) <a>nodes</a>
as values, so that each <a>parameter</a> of <code>f</code> has the <a>value</a> of the parameter's
<code>sh:path</code> from <code>expr</code>.
For example, if <code>f</code> declares just one <a>parameter</a> with <code>sh:path</code> <code>ex:param</code>
and <code>expr</code> is <code>[ ex:param 42 ]</code> then <code>argScope</code> is <code>{ ex:param : 42 }</code>.
<br/><br/>
The <a>output nodes</a> of <code>expr</code> are computed using
<code>evalExpr(expr, focusGraph, focusNode, scope) -> evalExpr(body, focusGraph, focusNode, argScope)</code>
</p>
</div>
<p><em>The remainder of this section is informative.</em></p>
<p>
The following example defines a new <a>node expression function</a> <code>ex:AverageExpression</code>
that takes another node expression as input using the <a>key parameter</a> <code>ex:average</code>
and then calculates the sum of all input nodes and divides it by the number of nodes,
returning the average value of these nodes.
</p>
<aside class="example" id="custom-named-function-example">
<div class="shapes-graph">
<div class="turtle">
ex:AverageExpression
a sh:NamedParameterExpressionFunction ;
rdfs:label "Average expression"@en ;
rdfs:comment "Computes the average of the nodes provided by ex:average." ;
rdfs:subClassOf sh:NamedParameterExpression ;
sh:parameter ex:AverageExpression-average ;
sh:bodyExpression [
sparql:divide (
[ shnex:sum [ shnex:arg ex:average ] ]
[ shnex:count [ shnex:arg ex:average ] ]
)
] ;
.
ex:AverageExpression-average
a sh:Parameter ;
sh:path ex:average ;
sh:name "average" ;
sh:description "The nodes of which the average shall be computed." ;
sh:keyParameter true ;
.
</div>
</div>
</aside>
<p>
This new node expression function can the be used as follows:
</p>
<aside class="example">
<div class="shapes-graph">
<div class="turtle">
ex:CompanyShape-averageIncome
a sh:PropertyShape ;
sh:path ex:averageIncome ;
sh:datatype xsd:decimal ;
sh:values [
<b>ex:average [
shnex:pathValues ( ex:employee ex:income )
]</b>
] .
</div>
</div>
</aside>
</section>

<section>
<h3>Custom List Parameter Functions</h3>
<p class="syntax">
<span data-syntax-rule="CustomListParameterFunction-syntax">
A <dfn>custom list parameter function</dfn> is an <a>IRI</a> in a <a>shapes graph</a>
that is a <a>SHACL instance</a> of <code>sh:ListParameterExpressionFunction</code> and
a <a>SHACL subclass</a> of <code>sh:ListParameterExpression</code>.
The <a>IRI</a> of a <a>custom list parameter function</a> is its <a>list parameter property</a>.
It has a single <a>value</a> for <code>sh:bodyExpression</code> that is a <a>well-formed</a>
<a>node expression</a>.
<br/><br/>
<a>Custom list parameter functions</a> can reference the arguments using an
<a>arg expression</a> such as <code>[ shnex:arg 0 ]</code> and <code>[ shnex:arg 1 ]</code>
where the <code>xsd:integer</code> <code>n</code> corresponds to the <code>n</code>th <a>member</a>
of the arguments list, starting with <code>0</code> as the first member.
</span>
</p>
<p class="syntax">
<span data-syntax-rule="CustomListParameterExpression-syntax">
A <dfn>custom list parameter expression</dfn> is a <a>node expression</a>
represented by a <a>blank node</a> that is the <a>subject</a> of exactly one <a>triple</a>
and the <a>predicate</a> of that triple is the <a>list parameter property</a> of a
<a>custom list parameter function</a> in the <a>shapes graph</a>.
</span>
</p>
<div class="def" id="CustomListParameterExpression-evaluation">
<div class="def-header">EVALUATION OF CUSTOM LIST PARAMETER EXPRESSIONS</div>
<p>
Let <code>expr</code> be a <a>custom list parameter expression</a> with the
<a>custom list parameter function</a> <code>f</code>.
Let <code>body</code> be the <a>value</a> of <code>sh:bodyExpression</code> at <code>f</code>
in the <a>shapes graph</a>.
<br/><br/>
Let <code>argScope</code> be a map of (parameter index) <a>nodes</a> as keys and (argument) <a>nodes</a>
as values, so that each list argument of <code>expr</code> has the index of the argument as an
<code>xsd:integer</code> as key, starting with <code>0</code> for the first argument.
For example, if <code>expr</code> has arguments <code>( 38 4 )</code> then the
<code>argScope</code> is <code>{ 0 : 38, 1 : 4 }</code>.
<br/><br/>
The <a>output nodes</a> of <code>expr</code> are computed using
<code>evalExpr(expr, focusGraph, focusNode, scope) -> evalExpr(body, focusGraph, focusNode, argScope)</code>
where an <a>evaluation failure</a> is reported when there is more than 1 output node.
</p>
</div>
<p><em>The remainder of this section is informative.</em></p>
<p>
The following example defines a new <a>node expression function</a> <code>ex:spacedConcat</code>
that takes two nodes as input and returns a string concatenating the two nodes with a space in between.
</p>
<aside class="example" id="custom-list-function-example">
<div class="shapes-graph">
<div class="turtle">
ex:spacedConcat
a sh:ListParameterExpressionFunction ;
rdfs:label "Spaced concat expression"@en ;
rdfs:subClassOf sh:ListParameterExpression ;
sh:bodyExpression [
sparql:concat (
[ shnex:arg 0 ]
" "
[ shnex:arg 1 ]
)
] .
</div>
</div>
</aside>
<p>
This new node expression function can the be used as follows:
</p>
<aside class="example">
<div class="shapes-graph">
<div class="turtle">
ex:Person-fullName
a sh:PropertyShape ;
sh:path ex:fullName ;
sh:datatype xsd:string ;
sh:values [
<b>ex:spacedConcat (
[ shnex:pathValues ex:firstName ]
[ shnex:pathValues ex:lastName ]
)</b>
] .
</div>
</div>
</aside>
</section>

<section id="ArgExpression">
<h3>Arg Expressions</h3>
<p>
Custom node expressions can use <code>shnex:arg</code> to access the arguments.
</p>
<p class="syntax">
<span data-syntax-rule="ArgExpression-syntax">
A <a>blank node</a> that is the <a>subject</a> of the following properties
is called an <dfn>arg expression</dfn> with the <a>function name</a> <code>shnex:ArgExpression</code>:
<table class="term-table">
<thead>
<th>Property</th>
<th>Constraints</th>
<th>Description</th>
</thead>
<tbody>
<tr>
<td><b><code>shnex:arg</code></b></td>
<td>
<code>
sh:or (<br/>
&nbsp;&nbsp;[ sh:nodeKind sh:IRI ]<br />
&nbsp;&nbsp;[ sh:datatype xsd:integer ]<br />
)
</code>
</td>
<td>
The argument key, e.g. <code>ex:myParameter</code> or <code>1</code>.
</td>
</tr>
</tbody>
</table>
</span>
</p>
<div class="def" id="ArgExpression-evaluation">
<div class="def-header">EVALUATION OF ARG EXPRESSIONS</div>
<p>
Let <code>arg</code> be the <a>value</a> of <code>shnex:arg</code> in the <a>arg expression</a>.
The <a>output nodes</a> of the <a>var expression</a> are computed as follows, in order:
</p>
<ol>
<li>
if <code>arg</code> is in the <code>scope</code> and has the value <code>a</code> then
<code>evalExpr(expr, focusGraph, focusNode, scope) -> evalExpr(a, focusGraph, focusNode, {})</code>
</li>
<li>otherwise <code>evalExpr(expr, focusGraph, focusNode, scope) -> []</code></li>
</ol>
</div>
<p><em>The remainder of this section is informative.</em></p>
<p>
Both <code>shnex:arg</code> and <code>shnex:var</code> access values from the scope.
The difference is that <code>shnex:arg</code> interprets the values as node expressions, while
<code>shnex:var</code> treats the values as individual nodes.
As a result, a custom node expression can evaluate nested node expressions that are passed in as arguments.
</p>
<p>
Examples of <code>shnex:arg</code> can be found in
<a href="#custom-named-function-example"></a> and <a href="#custom-list-function-example"></a>.
</p>
</section>
</section>

<section id="constraint-components">
<h2>Constraint Components</h2>
<p>
Expand Down Expand Up @@ -2765,7 +3024,7 @@ <h3>Example: Dynamic Minimum Age of Presidents</h3>
<b>sh:minInclusive [
shnex:if [
sparql:eq (
[ shnex:path ex:country ]
[ shnex:pathValues ex:country ]
ex:USA
)
]
Expand Down Expand Up @@ -2859,7 +3118,7 @@ <h3>Example: Dynamic Enumerations</h3>
a sh:PropertyShape ;
sh:path ex:state ;
<b>sh:in [
shnex:path ( ex:country ex:stateCode )
shnex:pathValues ( ex:country ex:stateCode )
]</b> .
</div>
<div class="jsonld">
Expand All @@ -2870,7 +3129,7 @@ <h3>Example: Dynamic Enumerations</h3>
</div>
</aside>
<p>
During validation, a Dynamic SHACL engine will evaluate the path expression at <code>sh:in</code>
During validation, a Dynamic SHACL engine will evaluate the path values expression at <code>sh:in</code>
and use the resulting nodes as members of the allowed values.
Thus, when the value of <code>ex:country</code> is <code>ex:USA</code>, it will look up the
state codes that are linked to <code>ex:USA</code>.
Expand Down
7 changes: 7 additions & 0 deletions shacl12-vocabularies/shacl.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -1386,6 +1386,13 @@ sh:ListParameterExpression
rdfs:subClassOf sh:NodeExpression ;
rdfs:isDefinedBy sh: .

sh:bodyExpression
a rdf:Property ;
rdfs:label "body expression"@en ;
rdfs:comment "A node expression that is the implementation/body of a custom node expression function."@en ;
rdfs:domain sh:NodeExpressionFunction ;
rdfs:isDefinedBy sh: .


# General SPARQL execution support --------------------------------------------

Expand Down