- From: Sommer, Ashley (L&W, Dutton Park) <Ashley.Sommer@csiro.au>
- Date: Wed, 15 Sep 2021 09:55:58 +0000
- To: Scott Henninger <scotthenninger@gmail.com>, "public-shacl@w3.org" <public-shacl@w3.org>
- Message-ID: <SYCPR01MB46384D2B1708EB679BD6D2058BDB9@SYCPR01MB4638.ausprd01.prod.outlook.com>
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<mailto: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<mailto:scotthenninger@gmail.com>> Sent: Tuesday, 14 September 2021 3:24 PM To: public-shacl@w3.org<mailto:public-shacl@w3.org> <public-shacl@w3.org<mailto: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<mailto:scotthenninger@gmail.com>
Received on Friday, 17 September 2021 08:02:46 UTC