1. Overview
This page documents how OCL expressions are translated to Schematron, based upon the xslt2 query binding, which has been standardized by ISO 19757-3:2016. More specifically, it documents the translation of the OCL language constructs that are supported by ShapeChange.
Note
|
This translation is available since ShapeChange v2.9.0. |
ShapeChange configuration items relevant for the generation of Schematron schemas are documented as well.
2. Relevant ShapeChange configuration items
XmlSchema target conversion rules:
-
rule-xsd-pkg-schematron: Must be part of the encoding rule of a model element in order for OCL constraints defined on that element to be translated to Schematron rules.
-
rule-xsd-cls-codelist-constraints-codeAbsenceInModelAllowed: If OCL constraints refer to code list codes that are not explicitly modelled in the respective code list, then this rule must be added to the XML Schema encoding rule, in order for the constraints to be parsed correctly.
-
rule-xsd-cls-codelist-constraints2: May be used for creating a Schematron schema that checks the content of code list typed properties, more specifically that the code list exists, is correctly referenced, and also that the code is defined by the code list. This conversion rule is added here only for the sake of completeness, since the Schematron generated by this rule does not translate any OCL constraints. It is purely generated based upon class and property information available in the model.
The XmlSchema target parameter schematronQueryBinding must be set to "xslt2" in order for ShapeChange to use the Schematron translation documented on this page.
3. Translation of OCL to Schematron
Conversion from OCL to Schematron is performed on the basis of a ShapeChange-internal syntax representation of OCL expressions. The representation is close to the Concrete Syntax structure described in the OCL 2.2 standard.
Naturally, the syntax representation of OCL is recursive. Therefore the principles of translation from OCL to another language can best be described using a recursive notation. Below, we describe how some particular constructs such as the application of the select() iterator
x→select(t|pred(t))
translate to XPath 2.0, in the xslt2 Schematron query binding, where the translation results of the constituent parts (such as x and pred(t)) are presumed.
For a valid OCL expression x, τ(x) denotes the equivalent XPath 2.0 expression (in the xslt2 query binding). The expression x may contain free variables (explicit or implicit), which need to be treated when computing τ(x). One typical variable is self, which translates to current(). So, τ(self)=current().
Note
|
Do not use variables called BYREFVAR, METAREFVAR, COUNT1, COUNT2, ISUVAR1, ISUVAR2, or that matches the regular expression 'VAR\d+' within OCL expressions. The translation of property calls, isUnique(..), and calls of operation propertyMetadata() may generate and use such variables, which can result in naming conflicts if the OCL expression contained variables with these names. |
3.2. Iterator variable access
3.2.3. Schematron translation
$t
Note
|
Do not use variables called BYREFVAR, METAREFVAR, COUNT1, COUNT2, ISUVAR1, ISUVAR2, or that matches the regular expression 'VAR\d+' within OCL expressions. The translation of property calls, isUnique(..), and calls of operation propertyMetadata() may generate and use such variables, which can result in naming conflicts if the OCL expression contained variables with these names. |
3.3. Let variable access
3.3.3. Schematron translation
If t is defined in the outer (current()) context: $id, where id is some unique <let> variable. The let initializer is translated in the current() context and initializes a Schemtron <let> element.
Other: The let initializer of the variable t is translated in the current context and substitutes t.
Note
|
Do not use variables called BYREFVAR, METAREFVAR, COUNT1, COUNT2, ISUVAR1, ISUVAR2, or that matches the regular expression 'VAR\d+' within OCL expressions. The translation of property calls, isUnique(..), and calls of operation propertyMetadata() may generate and use such variables, which can result in naming conflicts if the OCL expression contained variables with these names. |
3.4. Let expression
3.11. Property call
3.11.1. OCL syntax
x . property
Note
|
This is the shorthand notation for the OCL "Collect" operation. |
3.11.2. In words
Collection of property values reached from the instance or collection represented by x by applying property property.
3.11.3. Schematron translation
-
If simple-typed:
-
value type is NOT a code list:
-
property encoding is GML 3.2 or GML 3.3: τ(x)/property
-
property encoding is ISO 19139: τ(x)/property/*
-
-
value type IS a code list:
-
A so-called code list value pattern is used. The default pattern is {value}. If property is not GML 3.3 encoded, and the code list has tagged value 'codeList', then the default changes to {codeList}/{value}. The default can be overridden by 1) tagged value 'codeListValuePattern' on the code list, or 2) the XmlSchema target parameter defaultCodeListValuePattern. The pattern should always contain the keyword {value} and may also contain the keyword {codeList}. When creating the XPath expression, these keywords will be replaced. If the pattern is not just {value} then it is translated as a concat( ) operation. In general, the keywords are mapped to XML items as follows:
-
GML 3.2:
-
codeList → @codeSpace
-
value → text() (i.e. the text of the XML element that represents property)
-
-
ISO 19139:
-
codeList → @codeList
-
value → @codeListValue
-
-
If prop is GML 3.3 encoded, then the code list value pattern is ignored. The @xlink:href provides the code value.
-
-
property encoding is GML 3.2:
-
code list IS encoded as dictionary: τ(x)/property/ + result of code list value pattern (see above)
-
code list is NOT encoded as dictionary: τ(x)/property
-
-
property encoding is GML 3.3:
-
code list IS encoded as dictionary: τ(x)/property/@xlink:href
-
code list is NOT encoded as dictionary: τ(x)/property
-
-
property encoding is ISO 19139: τ(x)/property/*/ + result of code list value pattern (see above)
-
-
-
If the value type is a complex type without identity - typically a data type or union: τ(x)/property/*
-
If the value type is a complex type with identity:
-
This requires careful consideration of how the value can be encoded: inline, by reference, or both. Tagged value inlineOrByReference on property can be used to control this encoding (default is both, i.e. inline or by reference).
-
If the value is encoded by reference, then it must be looked up in the XML document that is being validated, using an expression as the following: (for $BYREFVAR in variable/property/@xlink:href return key('idKey',identifierExpression)) - where:
-
variable is either current(), the variable defined by a surrounding iterator, or a variable that is generated as part of a 'for' expression while translating the OCL expression of a property path.
-
identifierExpression is an XPath expression to extract from an xlink:href value the @id or @gml:id of the referenced object. That ID is then used to look up the referenced object in the key-construct 'idKey'. The 'key' provides an efficient lookup mechanism for all objects in the dataset, based upon their @id or @gml:id.
-
The identifierExpression is different, depending upon whether the xlink:href reference contains a prefix α and/or a postfix β:
-
α can be configured via the XmlSchema target parameter schematronXlinkHrefPrefix (default is #).
-
β can be configured via the XmlSchema target parameter schematronXlinkHrefPostfix (default is the empty string).
-
If both α and β have a value, the identifierExpression is: substring-before(substring-after($BYREFVAR, α), β)
-
If only α has a value, the identifierExpression is: substring-after($BYREFVAR, α)
-
If only β has a value, the identifierExpression is: substring-before($BYREFVAR, β)
-
If neither α nor β have a value, the identifierExpression boils down to: $BYREFVAR
-
-
-
Inline encoded properties are translated to property/*.
-
If a property can (also) be encoded by reference, then a 'for' expression is constructed. The expression merges the sequences produced by the expressions for the inline and by reference cases.
-
NOTE: Cases where a property value is given both inline and by reference are currently not handled. Such cases will lead to incorrect results.
-
-
If the OCL expression is a path of multiple property names (e.g. property1.property2.property3) then a nesting of 'for' expressions may be created. Such a complex expression is necessary, in order to correctly set the context for computing referenced objects in all cases.
-
-
-
If the metadata of the property shall be accessed (see operation call propertyMetadata()): τ(x)/property
3.13. Operation call propertyMetadata()
3.13.3. Schematron translation
-
Case x is a property: This is translated like a property call for the case of a property whose value is a complex type with identity, given by reference - just using @metadata instead of @xlink:href: for $METAREFVAR in variable/property/@metadata return key('idKey',identifierExpression)
-
Case x is a variable: Cannot be translated, because the variable may be bound to different kinds of expressions, not just a property call, and even if it was bound to a property call, then the variable may be used multiple times: not only for accessing the metadata of the property, but also its value. That would require different translations of the property call. However, a variable can only be bound to a single expression.
3.14. Operation call allInstances()
3.14.2. In words
Collection of all object instances of type x.
x represents a type-valued expression.
3.14.3. Schematron translation
If x is a type constant:
An expression that selects all elements based upon a predicate which
uses a combination of local-names and namespace-uris of the type and its
(direct and indirect) subtypes (if they are instantiable, i.e. not
abstract and not suppressed). We differentiate three cases:
-
Case multiple instantiable classes: //*[(local-name()='localName_type_1′ and namespace-uri()='namespace_type_1′) or … or (local-name()='localName_type_n' and namespace-uri()='namespace_type_n')]
-
Case single instantiable class: //*[local-name()='localName_type' and namespace-uri()='namespace_type']
-
Case no instantiable class: //*[false()]
Note
|
ShapeChange will store the expression in a new let variable, since it makes sense to only compute the collection of instances of a certain type once. An example where this would immediately be useful is Type.allInstances().inlineOrByReferenceProperty… - because the let variable can then be used both for the translation of the inline and the byReference case of the property access. |
If x is a type expression:
Cannot be translated because required schema information is not
available at run-time.
3.15. Operation call oclIsKindOf()
3.15.3. Schematron translation
If y is a type constant:
boolean(τ(x)[(local-name()='localName_T1' and
namespace-uri()='namespace_T1') or … or (local-name()='localName_Ti' and
namespace-uri()='namespace_Ti')]), where Tk is one of the concrete
derivations of y, including y.
Note
|
boolean(…) may be omitted if the argument is known to be used by operands, which do an implicit conversion to Boolean. |
If y is a type expression: Cannot be translated because required schema information is not available at run-time.
3.16. Operation call oclIsTypeOf()
3.16.3. Schematron translation
If y is a type constant: boolean(τ(x) [local-name()='localName_y' and namespace-uri()='namespace_y']).
boolean(…) may be omitted if the argument is known to be used by operands, which do an implicit conversion to Boolean.
If x is a type expression:
Cannot be translated because required schema information is not
available at run-time.
3.17. Operation call oclAsType()
3.17.2. In words
The single object instance x is downcast to type y. The value is 'undefined' if this is not possible.
Note
|
This operation is typically used in situations where ISO 19139 encoding applies, to cast an attribute with value type CharacterString to a code list type, so that comparison with a code/enum is possible. |
3.17.3. Schematron translation
If y is a type constant:
τ(x) [(local-name()='localName_T1' and namespace-uri()='namespace_T1')
or … or (local-name()='localName_Ti' and
namespace-uri()='namespace_Ti')], where Tk is one of the concrete
derivations of y, including y.
If y is a type expression: Cannot be translated because required schema information is not available at run-time.
Casting CharacterString to a code list: We are making an exception to the strict rules with simple data elements. We permit CharacterString being cast to a code list type.
3.23. Operation call substring()
3.28. Collection operation call exists()
3.29. Collection operation call forAll()
3.30. Collection operation call isUnique()
3.30.2. In words
Determine if y(t) evaluates to a different, possibly null, value for each element in the source collection x.
If y(t) equals t, then uniqueness of the values in the collection x is checked.
3.30.3. Schematron translation
This can only be translated in a few cases:
-
If y is a constant, y(t)=const: count(τ(x))<=1
-
If y is identity (x->isUnique(t|t)):
-
if value t has a simple type:
-
type other than Boolean: count(T(x)) = count(distinct-values(T(x))
-
type is Boolean: count(T(x)) = count(distinct-values(T(x)/xs:boolean(.)))
-
-
if value is a complex type with identity: count(T(x)) = count(distinct-values(T(x)/@*:id)
-
else (value is a complex type without identity - for example a data type or union): for $COUNT1 in count(T(X)), $COUNT2 in sum(for $ISUVAR1 in T(X), $ISUVAR2 in T(X) return (if(empty($ISUVAR1) and empty($ISUVAR2)) then 0 else if empty($ISUVAR1) and not(empty($ISUVAR2) or (not(empty($ISUVAR1)) and empty($ISUVAR2))) then 1 else if (generate-id($ISUVAR1) = generate-id($ISUVAR2)) then 0 else if (deep-equal($ISUVAR1,$ISUVAR2)) then 0 else 1)) return $COUNT1 * ($COUNT1 - 1) = $COUNT2
-
-
If y(t) is a property call that is based on variable t and:
-
if the property call only involves properties with max cardinality = 1:
-
if the value resulting from the property call has a simple type:
-
type other than Boolean: count(T(x)) = count(distinct-values(for $t in T(x) return (if (empty(T(y(t)))) then 'SC_EMPTY_ISU_BODY' else T(y(t)) )))
-
type is Boolean: count(T(x)) = count(distinct-values(for $t in T(x) return (if (empty(T(y(t)))) then 'SC_EMPTY_ISU_BODY' else T(y(t))/xs:boolean(.) )))
-
NOTE: An empty value for T(y(t)) is mapped to the string 'SC_EMPTY_ISU_BODY' in order to correctly handle null values in the evaluation of the iterator isUnique(). Without this mapping, empty/null values would be ignored by the function distinct-values().
-
-
if the value resulting from the property call has a complex type with identity: count(T(x)) = count(distinct-values(for $t in T(x) return (if (empty(T(y(t)))) then 'SC_EMPTY_ISU_BODY' else T(y(t))/@*:id )))
-
else (value resulting from the property call is a complex type without identity - for example a data type or union): for $COUNT1 in count(T(X)), $COUNT2 in sum(for $t in T(x), $ISUVAR1 in T(y(t)), $ISUVAR2 in T(y(t)) return (if(empty($ISUVAR1) and empty($ISUVAR2)) then 0 else if empty($ISUVAR1) and not(empty($ISUVAR2) or (not(empty($ISUVAR1)) and empty($ISUVAR2))) then 1 else if (generate-id($ISUVAR1) = generate-id($ISUVAR2)) then 0 else if (deep-equal($ISUVAR1,$ISUVAR2)) then 0 else 1)) return $COUNT1 * ($COUNT1 - 1) = $COUNT2
-
-
if the property call involves a property with max cardinality > 1: cannot be translated; each evaluation of y(t) must result in at most one value. ShapeChange will log an error if this condition is not fulfilled.
-
-
Any other, particularly arbitrary expressions: Cannot be translated because no way to express this in XPath 2.0 has been found. isUnique bodies must not contain expressions other than constants, identity, or a property call. ShapeChange will log an error if this condition is not fulfilled.