- From: Irene Polikoff <irene@topquadrant.com>
- Date: Wed, 15 Sep 2021 13:56:00 -0400
- To: Scott Henninger <scotthenninger@gmail.com>
- Cc: "public-shacl@w3.org" <public-shacl@w3.org>
- Message-Id: <A577F588-5E6A-43D7-BCA3-5D6DAD87DD0F@topquadrant.com>
Hi Scott, If I understand this correctly (and I am not sure I do), there are the following options: 1. Use sh:or to say that either values of ex:parent come from members of below classes and the additional constraints apply OR values of ex:parent do not come from members of below classes. To make the expressions easier to understand and more maintainable, you may consider creating a parent class ex:Cls and make ex:Cls1, ex:Cls2 and ex:Cls3 its subclasses. Then, you would simply add or remove subclasses if your model evolves. You could also create two untargeted node shapes - just to make it simpler to understand e.g., > my_sh:ClassShape > a sh:NodeShape ; > sh:targetClass ex:Cls1 ; . > sh:or ( ex:ParentsCls > ex:ParentsNotCls) ; ex:ParentsCls and ex:ParentsNotCls would be the two untargeted NodeShapes 2. Use SPARQL-based constraint components option to create a new constraint component that would encapsulate your conditions and take as parameters ex:Cls1, ex:Cls2 and ex:Cls3. 3. Use SPARQL-based targets to target only some members of ex:Cls1 - those with only certain values in the ex:parent property This option is available only in the Advanced SHACL Features specification. As an aside, I wanted to comment on the terminology used because using the right terminology is helpful in articulating requirements and solutions: 1. Everything in SHACL is either a target, shape or constraint > 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. Not quite. The fact that a value of ex:property is a member of a certain class is a constraint 2. > 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 is not a shape. It is data. I understand that you mean “this data is of valid shape” or "this data would conform to the SHACL shapes I am needing to create”. However, in SHACL, when you say “the following is a shape”, you are expected to be referring to either a SHACL Node Shape or a SHACL Property Shape. The same applies to the word “target”. Data does not target anything. A target can only be defined in a shape. A shape targets some data. Hope this helps, Irene > On Sep 14, 2021, at 11:44 PM, Scott Henninger <scotthenninger@gmail.com> wrote: > > 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 Wednesday, 15 September 2021 17:56:16 UTC