Re: SHACL Shape definition for including and excluding parent types

Unfortunately, SHACL-AF is not available to thei system. I have asked for
an enhancement to include SHACL-AF, but in the meantime I'm stuck with
SHACL only.

-- Scott

On Wed, Sep 15, 2021 at 4:56 AM Sommer, Ashley (L&W, Dutton Park) <
Ashley.Sommer@csiro.au> wrote:

> Hi Again Scott,
>
> After thinking about this for a bit, (I admit I still don't quite
> understand what exactly you're trying to achieve) I believe I have found
> three different solutions to solve your requirements, with the given Cases.?
> The *best *solution I think would be an Expression Constraint from the
> SHACL Advanced Features spec (SHACL-AF) in particular, using the
> FilterShape
> <https://www.w3.org/TR/shacl-af/#node-expressions-filter-shape>
> expression. You can also do this with SHACL Rules (again from SHACL-AF).
> However first, here is a solution without any advanced features, using
> just combinational logic:
>
> my_sh:ClassShape
>   a sh:NodeShape ;
>   sh:targetClass ex:Cls1 ;
>   sh:message "Should not be a violation" ;
>   sh:xone ( [ sh:and ( my_sh:PreReqClasses my_sh:MustHaveAName ) ]
>             [ sh:not my_sh:PreReqClasses ]
>   ) .
>
> my_sh:PreReqClasses
>  a sh:NodeShape ;
>  sh:property [ sh:path ex:parent ; sh:minCount 1 ; sh:maxCount 1 ] ;
>  sh:xone ( [sh:path ex:parent ; sh:class ex:P_cls1 ]
>            [sh:path ex:parent ; sh:class ex:P_cls2 ]
>            [sh:path ex:parent ; sh:class ex:P_cls3 ]
>  )  .
>
> my_sh:MustHaveAName
>   a sh:PropertyShape ;
>   sh:path ex:name ;
>   sh:minCount 1 ;
>   sh:maxCount 1 ;
>   sh:message "Instance of targeted class must have one and only one
> ex:name" ; .
>
> This has two disadvantages: a) Running "PreReqClasses" twice, one in the
> sh:and, and once in the sh:not,  and b) runs MustHaveAName (but doesn't
> report errors) even on the nodes which don't meet PreReqClasses. But it
> matches the logic you're looking for.
>
> Now, fun with advanced features:
> Firstly an example using SHACLRules:
> my_sh:ClassShape
>   a sh:NodeShape ;
>   sh:targetClass ex:Cls1 ;
>   sh:rule [
>     a sh:TripleRule ;
>     sh:subject sh:this ;
>     sh:predicate rdf:type ;
>     sh:object ex:ChosenChildren ;
>     sh:condition my_sh:PreReqClasses ;
>   ] ;
>   sh:message "Should not be a violation" ;
>  .
>
> my_sh:PreReqClasses
>  a sh:NodeShape ;
>  sh:property [ sh:path ex:parent ; sh:minCount 1 ; sh:maxCount 1 ] ;
>  sh:xone ( [sh:path ex:parent ; sh:class ex:P_cls1 ]
>            [sh:path ex:parent ; sh:class ex:P_cls2 ]
>            [sh:path ex:parent ; sh:class ex:P_cls3 ]
>  ) .
>
> my_sh:MustHaveAName
>   a sh:NodeShape ;
>   sh:targetClass ex:ChosenChildren ;
>   sh:property [
>       sh:path ex:name ;
>       sh:minCount 1 ;
>       sh:maxCount 1 ;
>       sh:message "Instance of targeted class must have one and only one
> ex:name" ;
>   ] .
>
> This uses a TripleRule <https://www.w3.org/TR/shacl-af/#TripleRule> to
> add a new temporary rdf type identifier to the nodes objects in the
> datagraph that match the prerequisite condition.
> This allows the validator to then subsequently run the MustHaveAName shape
> with the targetClass being that new type.
>
> Finally, as I mentioned up the top, was trying to get an example working
> using ExpressionConstraints
> <https://www.w3.org/TR/shacl-af/#ExpressionConstraintComponent>
>  (particularly FilterShape
> <https://www.w3.org/TR/shacl-af/#node-expressions-filter-shape>) but it
> seems you can only pass the filtered nodes into a SHACL Function, not into
> a SHACL Constraint.
> Anyway, I hope those examples helped.
>
> - Ashley
>
>
>
>
>
> ------------------------------
> *From:* Scott Henninger <scotthenninger@gmail.com>
> *Sent:* Wednesday, 15 September 2021 1:44 PM
> *To:* public-shacl@w3.org <public-shacl@w3.org>
> *Subject:* Re: SHACL Shape definition for including and excluding parent
> types
>
> Thanks for the detailed analysis, Ashley.  It gave me some insights.
> However, I'm guilty of not expressing the problem correctly.  The main
> requirements are that 1) the target is ex:Cls1 and 2) the ex:parent is 3)
> one of n classes, which I have arbitrarily named ex:P_Cls{1, 2, 3}.   But
> these are intended not as constraints but as a prerequisite.  I.e. if the
> three are met, then there is a set of constraints that can be evaluated -
> such as ex:Cls1 must have one ex:name property. Otherwise the constraints
> are ignored.
>
> So let me start over with some examples.
>
> Case 1.  Adding the constraint that the target class must have an ex:name,
> the following is valid for both the class and property constraints.
>    ex:Child1
>       a ex:Cls1 ;
>       ex:parent ex:Parent1 ;
>       ex:name "John" .
>    ex:Parent1
>       a ex:P_cls1 .
>
> Case 2.  This is invalid because the ex:name is missing.
>    ex:Child1
>       a ex:Cls1 ;
>       ex:parent ex:Parent1 .
>    ex:Parent1
>       a ex:P_cls2 .
>
> Case 3.  This is valid because the parent is not in ex:P_Cls{1, 2, 3} and
> therefore no shapes are applied.
>    ex:Child1
>       a ex:Cls1 ;
>       ex:parent ex:Parent1 .
>    ex:Parent1
>       a ex:P_cls_x .
>
> That to me is the challenging part.  I need to express that the three main
> requirements are present before the other constraints can be validated.
> I.e. the three main requirements (above 1), 2), 3) ) are prerequisites for
> applying other shapes.
>
> Here's an attempt that does some of this (and taking your advice not to
> overthink).  The only problem is that I do not want a violation on the
> class defs.
>    my_sh:ClassShape
>       a sh:NodeShape ;
>       sh:targetClass ex:Cls1 ; .
>       sh:xone ( [sh:path ex:parent ; sh:class ex:ex:P_cls1 ]
>                 [sh:path ex:parent ; sh:class ex:ex:P_cls2 ]
>                 [sh:path ex:parent ; sh:class ex:ex:P_cls3 ]
>       ) ;
>       sh:message "Should not be a violation" ;
>       sh:property my_sh:MustHaveAName .
>
>   my_sh:MustHaveAName
>       a sh:PropertyShape ;
>       sh:path ex:name ;
>       sh:minCount 1 ;
>       sh:maxCount 1 ;
>       sh:message "Instance of targeted class must have one and only one
> ex:name" .
>
> What I would want this to expressis that only Case 2 should cause a
> violation.
>
> Sorry for bungling the initial question (and thanks again for a thorough
> response).  I'm not entirely sure whether or how SHACL could satisfy this
> kind of "prerequisite" constraint.  The above fails, as it will throw a
> violation if the parent is not one of ex:P_cls{1, 2, 3}.  If anyone has
> ideas on how to avoid this, I'm all ears.
>
> Let me know if anything isn't clear or is incorrect (thanks for catching
> my sh:not inside of sh:or mistake, BTW).
>
> Thanks again for taking a look and helping out.
> -- Scott
>
> On Tue, Sep 14, 2021 at 6:44 PM Sommer, Ashley (L&W, Dutton Park) <
> Ashley.Sommer@csiro.au> wrote:
>
> Hi Scott,
>
> I think you're over thinking this. From what I can tell, you have a simple
> requirement.
>
> Firstly, an obvious error: You have your sh:not constraint in your list of
> sh:or options.
> So you're effectively saying "class is P_cls1 OR P_cls2 OR P_cls3 OR NOT
> ex:P_clsInvalid_a" which as you can see is not logically consistent,
> because it would pass for *any*​ parent class that is
> NOT ex:P_clsInvalid_a.
> You instead need an AND in there, like "(class is P_cls1 OR P_cls2
> OR P_cls3) AND (NOT ex:P_clsInvalid_a)" like this:
> sh:and (
>   [
>     sh:or (
>       [ sh:class  ex:P_cls1 ]
>       [ sh:class  ex:P_cls2 ]
>       [ sh:class  ex:P_cls3 ]
>     )
>   ]
>   [ sh:not [ sh:class ex:P_clsInvalid_a ] ]
> )
>
> Secondly, to match one and only one of those in the OR list, use
> exclusive-or logic (sh:xone in SHACL
> <https://www.w3.org/TR/shacl/#XoneConstraintComponent>). Like this:
> sh:xone (
>   [ sh:class  ex:P_cls1 ]
>   [ sh:class  ex:P_cls2 ]
>   [ sh:class  ex:P_cls3 ]
> ) ;
>
> Next, if you want to maintain a list of invalid options, you can use sh:in
> <https://www.w3.org/TR/shacl/#InConstraintComponent> wrapped in a sh:not,
> to make a "not in" constraint. Like this:
> sh:not [ sh:in ( ex:P_clsInvalid_a ex:P_clsInvalid_b ) ] ;
>
> But when you break it down that far, why do you need the NOT? Wouldn't
> just "(class is P_cls1 XOR P_cls2 XOR P_cls3)" do the trick? Anything
> with a parent not in that list will be invalid, why does there need to be
> invalid classes specified, unless those invalid class are subclasses of
> P_cls1, P_cls2, P_cls3 ?
> Or are there other unspecified parent classes which are not in the list of
> three possible required, but also not invalid? If that is the case, your
> requirements need to be restated like:
>
>    - path ex:parent will have only one value
>    - the value for ex:parent can have one or more types (classes)
>    - one of those types needs to be {P_cls1 OR P_cls2 OR P_cls3}
>    - maximum of one of those types can be {P_cls1, P_cls2, P_cls3}
>    - parent's types cannot be one of {ex:P_clsInvalid_a,
>    ex:P_clsInvalid_b}
>
> You can use qualifiedvalueshape to ensure that at least one of the parents
> match the OR constraint, not need to use sh:xone in this case.
> Note, to emulate sh:class semantics on value-taking shapes like
> qualifiedValueShape and sh:in, I am using the same transitive SHACL
> Properly Path as demonstrated in the sh:class docs.
> <https://www.w3.org/TR/shacl/#ClassConstraintComponent>
>
> So, if I were writing a shape to match the requirements above, it would
> look like this:
>
> my_sh:ParentTypeRestriction
>   a sh:NodeShape ;
>   sh:targetClass ex:Cls1 ;
>   sh:property [
>     sh:path ex:parent ;
>     sh:minCount 1 ;
>     sh:maxCount 1 ;
>     sh:property [
>       sh:path ( rdf:type [ sh:zeroOrMorePath rdfs:subClassOf ] ) ;
>       sh:minCount 1 ;
>       sh:qualifiedMinCount 1 ;
>       sh:qualifiedMaxCount 1 ;
>       sh:qualifiedValueShape [
>         sh:or (
>           [ sh:hasValue ex:P_cls1 ]
>           [ sh:hasValue ex:P_cls2 ]
>           [ sh:hasValue ex:P_cls3 ]
>         )
>       ] ;
>       sh:not [ sh:in ( ex:P_clsInvalid_a ex:P_clsInvalid_b ) ] ;
>     ] ;
>     sh:message "parent type for ex:Cls1 is not in x:P_cls{1, 2, 3}" ;
>   ] ;
> .
> (Tested in PySHACL, using the valid and invalid examples you gave in your
> correspondence).
>
> This may be overkill for your requirements, and I may have misinterpreted
> your use-case. But it should at least give you something to work with.
> It could be taken even further by doing regex on type names to match
> P_cls{1,2,3}, if there is a known pattern they match, but that might be
> going too far.
>
> - Ashley
>
> ------------------------------
> *From:* Scott Henninger <scotthenninger@gmail.com>
> *Sent:* Tuesday, 14 September 2021 3:24 PM
> *To:* public-shacl@w3.org <public-shacl@w3.org>
> *Subject:* SHACL Shape definition for including and excluding parent types
>
> I have a couple of scenarios that involve restrictions on classes.  Most
> of them I can get working, but a couple I'm finding a bit difficult to
> figure out.  To make it simple, I'll start with the parent type restriction
> I'm trying to express.
>
> The following would be a valid shape as it targets ex:Cls1, specifies a
> parent with ex:parent, and the parent has one of the types ex:P_cls{1, 2,
> 3}.  (In all of these cases the targetClass is ex:Cls1).
>    ex:Child1
>       a ex:Cls1 ;
>       ex:parent ex:Parent1 ;
> .
>    ex:Parent1
>       a ex:Pcls1 ;
> .
>
> This would be an invalid shape because ex:Pcls{1,2,3} types must be
> defined for the parent:
>    ex:Child1
>       a ex:Cls1 ;
>       ex:parent ex:Parent1 ;
> .
>    ex:Parent1
>       a ex:P_clsInvalid_a ;
> .
>
> This one is valid because it includes one of ex:Pcls{1,2,3}:
>    ex:Child1
>       a ex:Cls1 ;
>       ex:parent ex:Parent1 ;
>    .
>    ex:Parent1
>       a ex:P_cls2, ex:P_clsInvalid_a ;
>    .
>
> The fourth is invalid because only one of ex:Pcls{1, 2, 3} is allowed:
>    ex:Child1
>       a ex:Cls1 ;
>       ex:parent ex:Parent1 ;
> .
>    ex:Parent1
>       a ex:P_cls1, ex:P_cls2;
> .
>
> If I could live with a list of disallowed types (for the sake of
> maintenance I'd rather say allow only ex:P_cls{1, 2, 3}) then the following
> seems to work:
>    my_sh:ParentTypeRestriction
>          a sh:NodeShape ;
>         sh:targetClass ex:Cls1 ;
>         sh:property[
>          s h:path ex:parent ;
>          sh:or (
>             [ sh:class  ex:P_cls1 ]
>             [ sh:class  ex:P_cls2 ]
>             [ sh:class  ex:P_cls3 ]
>             [ sh:not [ sh:class ex:P_clsInvalid_a ] ]
>         ) ;
>      sh:message "parent type for ex:Cls1 is not in x:P_cls{1, 2, 3}" ;
> ] ;
> .
>
> ..where I include an exhaustive list for sh:not.  As stated before, this
> isn't the best when the model changes and I need to find all the places
> where it needs to be excluded.
>
> So any ideas on how to 1) improve this shape, or 2) future-proof it for
> future additions to the rsf:type list?
>
> Thanks a bunch for taking a look.
> -- Scott
>
> Scott Henninger
> scotthenninger@gmail.com
>
>

Received on Wednesday, 15 September 2021 21:30:36 UTC