@@ -2189,6 +2189,265 @@ <h3>InstancesOf Expressions</h3>
21892189
21902190 </ section >
21912191
2192+ < section id ="custom-node-expressions ">
2193+ < h2 > Custom Node Expressions</ h2 >
2194+ < p >
2195+ SHACL includes vocabulary terms that can be used to define new < a > node expression functions</ a >
2196+ by wrapping other (parameterized) < a > node expressions</ a > .
2197+ This makes it possible to extend the library of available SHACL node expressions without having to
2198+ hard-code changes to an engine.
2199+ </ p >
2200+
2201+ < section >
2202+ < h3 > Custom Named Parameter Functions</ h3 >
2203+ < p class ="syntax ">
2204+ < span data-syntax-rule ="CustomNamedParameterFunction-syntax ">
2205+ A < dfn > custom named parameter function</ dfn > is an < a > IRI</ a > in a < a > shapes graph</ a >
2206+ that is a < a > SHACL instance</ a > of < code > sh:NamedParameterExpressionFunction</ code > and
2207+ a < a > SHACL subclass</ a > of < code > sh:NamedParameterExpression</ code > .
2208+ It has a single < a > value</ a > for < code > sh:bodyExpression</ code > that is a < a > well-formed</ a >
2209+ < a > node expression</ a > .
2210+ < br /> < br />
2211+ A < a > custom named parameter function</ a > declares one or more < a > parameters</ a > as < a > values</ a >
2212+ of < code > sh:parameter</ code > , where each such < a > parameter</ a > has exactly one < a > value</ a > for
2213+ < code > sh:path</ code > and that value is an < a > IRI</ a > .
2214+ At least one of the parameters has < code > sh:keyParameter true</ code > , declaring the < a > key parameters</ a >
2215+ for the function.
2216+ The < a > key parameters</ a > of all < a > node expression functions</ a >
2217+ (including the built-in ones from the < code > shnex:</ code > namespace) must be disjoint.
2218+ < br /> < br />
2219+ < a > Custom named parameter functions</ a > can reference the declared < a > parameters</ a > using an
2220+ < a > arg expression</ a > such as < code > [ shnex:arg ex:param ]</ code > , where the < a > value</ a > of < code > shnex:arg</ code >
2221+ matches the < a > IRI</ a > of the < a > parameter</ a > 's < code > sh:path</ code > .
2222+ </ span >
2223+ </ p >
2224+ < p class ="syntax ">
2225+ < span data-syntax-rule ="CustomNamedParameterExpression-syntax ">
2226+ A < dfn > custom named parameter expression</ dfn > is a < a > node expression</ a >
2227+ represented by a < a > blank node</ a > that has exactly one < a > value</ a > for at least one of
2228+ the < a > key parameters</ a > .
2229+ </ span >
2230+ </ p >
2231+ < div class ="def " id ="CustomNamedParameterExpression-evaluation ">
2232+ < div class ="def-header "> EVALUATION OF CUSTOM NAMED PARAMETER EXPRESSIONS</ div >
2233+ < p >
2234+ Let < code > expr</ code > be a < a > custom named parameter expression</ a > with the
2235+ < a > custom named parameter function</ a > < code > f</ code > .
2236+ Let < code > body</ code > be the < a > value</ a > of < code > sh:bodyExpression</ code > at < code > f</ code >
2237+ in the < a > shapes graph</ a > .
2238+ < br /> < br />
2239+ Let < code > argScope</ code > be a map of (parameter) < a > nodes</ a > as keys and (argument) < a > nodes</ a >
2240+ as values, so that each < a > parameter</ a > of < code > f</ code > has the < a > value</ a > of the parameter's
2241+ < code > sh:path</ code > from < code > expr</ code > .
2242+ For example, if < code > f</ code > declares just one < a > parameter</ a > with < code > sh:path</ code > < code > ex:param</ code >
2243+ and < code > expr</ code > is < code > [ ex:param 42 ]</ code > then < code > argScope</ code > is < code > { ex:param : 42 }</ code > .
2244+ < br /> < br />
2245+ The < a > output nodes</ a > of < code > expr</ code > are computed using
2246+ < code > evalExpr(expr, focusGraph, focusNode, scope) -> evalExpr(body, focusGraph, focusNode, argScope)</ code >
2247+ </ p >
2248+ </ div >
2249+ < p > < em > The remainder of this section is informative.</ em > </ p >
2250+ < p >
2251+ The following example defines a new < a > node expression function</ a > < code > ex:AverageExpression</ code >
2252+ that takes another node expression as input using the < a > key parameter</ a > < code > ex:average</ code >
2253+ and then calculates the sum of all input nodes and divides it by the number of nodes,
2254+ returning the average value of these nodes.
2255+ </ p >
2256+ < aside class ="example " id ="custom-named-function-example ">
2257+ < div class ="shapes-graph ">
2258+ < div class ="turtle ">
2259+ ex:AverageExpression
2260+ a sh:NamedParameterExpressionFunction ;
2261+ rdfs:label "Average expression"@en ;
2262+ rdfs:comment "Computes the average of the nodes provided by ex:average." ;
2263+ rdfs:subClassOf sh:NamedParameterExpression ;
2264+ sh:parameter ex:AverageExpression-average ;
2265+ sh:bodyExpression [
2266+ sparql:divide (
2267+ [ shnex:sum [ shnex:arg ex:average ] ]
2268+ [ shnex:count [ shnex:arg ex:average ] ]
2269+ )
2270+ ] ;
2271+ .
2272+ ex:AverageExpression-average
2273+ a sh:Parameter ;
2274+ sh:path ex:average ;
2275+ sh:name "average" ;
2276+ sh:description "The nodes of which the average shall be computed." ;
2277+ sh:keyParameter true ;
2278+ .
2279+ </ div >
2280+ </ div >
2281+ </ aside >
2282+ < p >
2283+ This new node expression function can the be used as follows:
2284+ </ p >
2285+ < aside class ="example ">
2286+ < div class ="shapes-graph ">
2287+ < div class ="turtle ">
2288+ ex:CompanyShape-averageIncome
2289+ a sh:PropertyShape ;
2290+ sh:path ex:averageIncome ;
2291+ sh:datatype xsd:decimal ;
2292+ sh:values [
2293+ < b > ex:average [
2294+ shnex:pathValues ( ex:employee ex:income )
2295+ ]</ b >
2296+ ] .
2297+ </ div >
2298+ </ div >
2299+ </ aside >
2300+ </ section >
2301+
2302+ < section >
2303+ < h3 > Custom List Parameter Functions</ h3 >
2304+ < p class ="syntax ">
2305+ < span data-syntax-rule ="CustomListParameterFunction-syntax ">
2306+ A < dfn > custom list parameter function</ dfn > is an < a > IRI</ a > in a < a > shapes graph</ a >
2307+ that is a < a > SHACL instance</ a > of < code > sh:ListParameterExpressionFunction</ code > and
2308+ a < a > SHACL subclass</ a > of < code > sh:ListParameterExpression</ code > .
2309+ The < a > IRI</ a > of a < a > custom list parameter function</ a > is its < a > list parameter property</ a > .
2310+ It has a single < a > value</ a > for < code > sh:bodyExpression</ code > that is a < a > well-formed</ a >
2311+ < a > node expression</ a > .
2312+ < br /> < br />
2313+ < a > Custom list parameter functions</ a > can reference the arguments using an
2314+ < a > arg expression</ a > such as < code > [ shnex:arg 0 ]</ code > and < code > [ shnex:arg 1 ]</ code >
2315+ where the < code > xsd:integer</ code > < code > n</ code > corresponds to the < code > n</ code > th < a > member</ a >
2316+ of the arguments list, starting with < code > 0</ code > as the first member.
2317+ </ span >
2318+ </ p >
2319+ < p class ="syntax ">
2320+ < span data-syntax-rule ="CustomListParameterExpression-syntax ">
2321+ A < dfn > custom list parameter expression</ dfn > is a < a > node expression</ a >
2322+ represented by a < a > blank node</ a > that is the < a > subject</ a > of exactly one < a > triple</ a >
2323+ and the < a > predicate</ a > of that triple is the < a > list parameter property</ a > of a
2324+ < a > custom list parameter function</ a > in the < a > shapes graph</ a > .
2325+ </ span >
2326+ </ p >
2327+ < div class ="def " id ="CustomListParameterExpression-evaluation ">
2328+ < div class ="def-header "> EVALUATION OF CUSTOM LIST PARAMETER EXPRESSIONS</ div >
2329+ < p >
2330+ Let < code > expr</ code > be a < a > custom list parameter expression</ a > with the
2331+ < a > custom list parameter function</ a > < code > f</ code > .
2332+ Let < code > body</ code > be the < a > value</ a > of < code > sh:bodyExpression</ code > at < code > f</ code >
2333+ in the < a > shapes graph</ a > .
2334+ < br /> < br />
2335+ Let < code > argScope</ code > be a map of (parameter index) < a > nodes</ a > as keys and (argument) < a > nodes</ a >
2336+ as values, so that each list argument of < code > expr</ code > has the index of the argument as an
2337+ < code > xsd:integer</ code > as key, starting with < code > 0</ code > for the first argument.
2338+ For example, if < code > expr</ code > has arguments < code > ( 38 4 )</ code > then the
2339+ < code > argScope</ code > is < code > { 0 : 38, 1 : 4 }</ code > .
2340+ < br /> < br />
2341+ The < a > output nodes</ a > of < code > expr</ code > are computed using
2342+ < code > evalExpr(expr, focusGraph, focusNode, scope) -> evalExpr(body, focusGraph, focusNode, argScope)</ code >
2343+ where an < a > evaluation failure</ a > is reported when there is more than 1 output node.
2344+ </ p >
2345+ </ div >
2346+ < p > < em > The remainder of this section is informative.</ em > </ p >
2347+ < p >
2348+ The following example defines a new < a > node expression function</ a > < code > ex:spacedConcat</ code >
2349+ that takes two nodes as input and returns a string concatenating the two nodes with a space in between.
2350+ </ p >
2351+ < aside class ="example " id ="custom-list-function-example ">
2352+ < div class ="shapes-graph ">
2353+ < div class ="turtle ">
2354+ ex:spacedConcat
2355+ a sh:ListParameterExpressionFunction ;
2356+ rdfs:label "Spaced concat expression"@en ;
2357+ rdfs:subClassOf sh:ListParameterExpression ;
2358+ sh:bodyExpression [
2359+ sparql:concat (
2360+ [ shnex:arg 0 ]
2361+ " "
2362+ [ shnex:arg 1 ]
2363+ )
2364+ ] .
2365+ </ div >
2366+ </ div >
2367+ </ aside >
2368+ < p >
2369+ This new node expression function can the be used as follows:
2370+ </ p >
2371+ < aside class ="example ">
2372+ < div class ="shapes-graph ">
2373+ < div class ="turtle ">
2374+ ex:Person-fullName
2375+ a sh:PropertyShape ;
2376+ sh:path ex:fullName ;
2377+ sh:datatype xsd:string ;
2378+ sh:values [
2379+ < b > ex:spacedConcat (
2380+ [ shnex:pathValues ex:firstName ]
2381+ [ shnex:pathValues ex:lastName ]
2382+ )</ b >
2383+ ] .
2384+ </ div >
2385+ </ div >
2386+ </ aside >
2387+ </ section >
2388+
2389+ < section id ="ArgExpression ">
2390+ < h3 > Arg Expressions</ h3 >
2391+ < p >
2392+ Custom node expressions can use < code > shnex:arg</ code > to access the arguments.
2393+ </ p >
2394+ < p class ="syntax ">
2395+ < span data-syntax-rule ="ArgExpression-syntax ">
2396+ A < a > blank node</ a > that is the < a > subject</ a > of the following properties
2397+ is called an < dfn > arg expression</ dfn > with the < a > function name</ a > < code > shnex:ArgExpression</ code > :
2398+ < table class ="term-table ">
2399+ < thead >
2400+ < th > Property</ th >
2401+ < th > Constraints</ th >
2402+ < th > Description</ th >
2403+ </ thead >
2404+ < tbody >
2405+ < tr >
2406+ < td > < b > < code > shnex:arg</ code > </ b > </ td >
2407+ < td >
2408+ < code >
2409+ sh:or (< br />
2410+ [ sh:nodeKind sh:IRI ]< br />
2411+ [ sh:datatype xsd:integer ]< br />
2412+ )
2413+ </ code >
2414+ </ td >
2415+ < td >
2416+ The argument key, e.g. < code > ex:myParameter</ code > or < code > 1</ code > .
2417+ </ td >
2418+ </ tr >
2419+ </ tbody >
2420+ </ table >
2421+ </ span >
2422+ </ p >
2423+ < div class ="def " id ="ArgExpression-evaluation ">
2424+ < div class ="def-header "> EVALUATION OF ARG EXPRESSIONS</ div >
2425+ < p >
2426+ Let < code > arg</ code > be the < a > value</ a > of < code > shnex:arg</ code > in the < a > arg expression</ a > .
2427+ The < a > output nodes</ a > of the < a > var expression</ a > are computed as follows, in order:
2428+ </ p >
2429+ < ol >
2430+ < li >
2431+ if < code > arg</ code > is in the < code > scope</ code > and has the value < code > a</ code > then
2432+ < code > evalExpr(expr, focusGraph, focusNode, scope) -> evalExpr(a, focusGraph, focusNode, {})</ code >
2433+ </ li >
2434+ < li > otherwise < code > evalExpr(expr, focusGraph, focusNode, scope) -> []</ code > </ li >
2435+ </ ol >
2436+ </ div >
2437+ < p > < em > The remainder of this section is informative.</ em > </ p >
2438+ < p >
2439+ Both < code > shnex:arg</ code > and < code > shnex:var</ code > access values from the scope.
2440+ The difference is that < code > shnex:arg</ code > interprets the values as node expressions, while
2441+ < code > shnex:var</ code > treats the values as individual nodes.
2442+ As a result, a custom node expression can evaluate nested node expressions that are passed in as arguments.
2443+ </ p >
2444+ < p >
2445+ Examples of < code > shnex:arg</ code > can be found in
2446+ < a href ="#custom-named-function-example "> </ a > and < a href ="#custom-list-function-example "> </ a > .
2447+ </ p >
2448+ </ section >
2449+ </ section >
2450+
21922451 < section id ="constraint-components ">
21932452 < h2 > Constraint Components</ h2 >
21942453 < p >
@@ -2765,7 +3024,7 @@ <h3>Example: Dynamic Minimum Age of Presidents</h3>
27653024 < b > sh:minInclusive [
27663025 shnex:if [
27673026 sparql:eq (
2768- [ shnex:path ex:country ]
3027+ [ shnex:pathValues ex:country ]
27693028 ex:USA
27703029 )
27713030 ]
@@ -2859,7 +3118,7 @@ <h3>Example: Dynamic Enumerations</h3>
28593118 a sh:PropertyShape ;
28603119 sh:path ex:state ;
28613120 < b > sh:in [
2862- shnex:path ( ex:country ex:stateCode )
3121+ shnex:pathValues ( ex:country ex:stateCode )
28633122 ]</ b > .
28643123 </ div >
28653124 < div class ="jsonld ">
@@ -2870,7 +3129,7 @@ <h3>Example: Dynamic Enumerations</h3>
28703129 </ div >
28713130 </ aside >
28723131 < p >
2873- During validation, a Dynamic SHACL engine will evaluate the path expression at < code > sh:in</ code >
3132+ During validation, a Dynamic SHACL engine will evaluate the path values expression at < code > sh:in</ code >
28743133 and use the resulting nodes as members of the allowed values.
28753134 Thus, when the value of < code > ex:country</ code > is < code > ex:USA</ code > , it will look up the
28763135 state codes that are linked to < code > ex:USA</ code > .
0 commit comments