.. include:: /includes.rst.txt .. _scripting_shacl: Using ADS with SHACL ==================== .. _scripting_shacl_validation: Constraint Validation --------------------- While the SHACL Core language defines built-in constraint properties such as `sh:minCount`, SHACL is also an extensible language, allowing extension languages such as SPARQL to declare constraints that go beyond the Core. In this section we introduce how the Active Data Shapes framework can be used as one such extension mechanism, allowing SHACL users to declare constraints and also constraint components, using JavaScript. JavaScript offers significantly greater expressiveness than, for example, SPARQL, allowing the representation of very complex conditions and taking better control over the execution speed. Script Constraints ^^^^^^^^^^^^^^^^^^ The property `dash:scriptConstraint` links a shape with instances of `dash:ScriptConstraint`. This is similar to how `sh:sparql` links a shape with instances of `sh:SPARQLConstraint`. Each `dash:ScriptConstraint` must have a value for `dash:js` to represent the JavaScript expression that shall be evaluated. It may also use `sh:message` to declare which validation messages shall be produced. The optional property `dash:onAllValues` may be used to control what variables can be used by the script, as explained in the following examples. Example: Validating an individual value """"""""""""""""""""""""""""""""""""""" This example illustrates the most simple form of script-based constraints: a single JavaScript expression that gets executed for each individual value of a property. Here we have a class `ex:AdultPerson` with a property `ex:age` that must not have values smaller than 18. .. literalinclude:: _code/scriptConstraint_individual_example.ttl :linenos: :language: turtle Such constraints will be executed for each value node of the property. In each script execution, the variable value will point at the actual property value. In this case, assuming that the value(s) of `ex:age` are `xsd:integer` literals, the value will be a JavaScript number. As usual with ADS, `xsd:string` literals are represented as JavaScript strings, while `xsd:boolean` literals become true or false in JavaScript. Literals of other datatypes become instances of `LiteralNode`, and `NamedNode` is used to represent URIs or blank nodes. The `dash:js` script can also access the current focus node using the variable `focusNode` as an instance of `NamedNode`, if needed. The `dash:js` expression will be evaluated against the current data graph. As usual in JavaScript, such expressions may consist of multiple lines, and the final result of the expression will be the result of its last line. * If the result is a string then the validation will produce a validation result with the given string as `sh:resultMessage`. * If the result is true then the validation passes OK without violations. * If the result is false the validation will produce a validation result that uses the provided `sh:message` or a generic fallback message. The values of `sh:message` can access the values `{$focusNode}`, `{$value}` etc as template variables. * If the result an object, a validation result will be produced using the value of the field `message` of the object as result message. If the field `value` has a value then this will become the `sh:value` in the violation. Alternatively, the script may return an array then each array member will be mapped to one validation result, following the mapping rules above. Example: Validating all values at once """""""""""""""""""""""""""""""""""""" In some scenarios, constraints need to look at all value nodes at once, for example to count the number of available values. This mode is activated if `dash:onAllValues` is true, and the JavaScript variable `values` will then be an array of the value nodes. In this example we verify that at least one of the values of the `ex:supervision` property of a person is an adult. .. literalinclude:: _code/scriptConstraint_onAllValues_example.ttl :linenos: :language: turtle Script Constraint Components ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SHACL constraint components are reusable building blocks that declare property types that others may use. Similar to `SPARQL-based Constraint Components `_, ADS makes it possible to declare new constraint components using JavaScript. Create an instance of `sh:ConstraintComponent` to get started, and declare the parameters as usual in SHACL. Then declare a `dash:ScriptValidator` as value of `sh:validator` and store the ADS script that shall be executed using `dash:js` at the validator. Within the JavaScript code, the same variables may be used as for constraints, and `dash:onAllValues` has the same meaning. In addition, the parameters will be mapped to JavaScript variables that have the same name as the local name of the parameter. This is similar to how SPARQL-based constraint components work. This is best explained using an example. This example declares a SHACL constraint component based on a parameter `ex:isJSONArray` which can be used to validate that all values of a given property can be parsed into JSON arrays. .. literalinclude:: _code/ScriptValidator_example.ttl :linenos: :language: turtle Note that the `dash:js` script queries the value of `isJSONArray` which will be a boolean that is automatically set to the correct value by the engine. Once declared, the new property can be used as in the following example shapes graph. Here we have set `ex:isJSONArray` to true which will trigger the execution of the script validator, which will verify that the value is true before proceeding with the actual check. .. literalinclude:: _code/ScriptValidator_shapes.ttl :linenos: :language: turtle Here is a valid instance from a data graph: .. literalinclude:: _code/ScriptValidator_instance.ttl :linenos: :language: turtle .. _scripting_shacl_values: Inferring Values ---------------- The SHACL Advanced Features include a mechanism to dynamically compute (infer) values of certain properties, based on the `sh:values` property. Active Data Shapes technology introduces a new kind of SHACL Node Expression based on scripts that makes it possible to dynamically compute property values from JavaScript expressions. These node expressions are blank nodes that have the script as value of `dash:js`. In the script, you can use the variable `focusNode` to reference the current context node. In the following example, we introduce a new derived property `skos:narrowerConceptCount` which is an integer computed as the number of narrower concepts. .. literalinclude:: _code/values_inference_example.ttl :linenos: :language: turtle With the new property defined as above, forms of SKOS concepts will now show the count: .. figure:: _images/narrowerConceptCount.png :alt: Form panel with values that are inferred by an ADS script :align: center :class: edg-figure **ADS scripts can be used to infer/derive new property values such as the narrower concept count above** The `dash:js` expression may also return an array, in which case multiple values would be inferred. .. warning:: Make sure to use this (powerful) feature with care, as it might have a performance impact if the system needs to repeatedly fire up a Script Runtime for each individual value on a form. Performance should however, be good as long as you call your inference as part of some other script. In the above case, you could now query the new property like any other property of the instance: .. figure:: _images/valuesPropertyUse.png :alt: Script Editor panel with an example call to an inferred property value :align: center :class: edg-figure **Inferred property values such as the narrower concept count can be queried like any other property** As usual, inferred properties cannot be directly modified, i.e. you cannot assign them. Change the underlying values (here: `skos:broader`) instead. Pro tip: if you want the Form panel and other parts of the UI update automatically after changes to the asserted properties annotate your node expression with `dash:dependencyPredicate`. .. literalinclude:: _code/dependencyPredicate_example.ttl :linenos: :language: turtle The above will mean that the counter will automatically update whenever some `skos:broader` triple has been changed (we are using `skos:broader` as the `sh:inversePath` in the computation of the narrower property). Current limitations: these script-based inferred values only work within graphs that are under teamwork control, i.e. you can only query them within EDG asset collections, not from other (file based) graphs in your installation. Furthermore, make sure that the focus node only has a single type, so that it is able to pick the right JavaScript class for the `focusNode` variable.