- From: Scott Henninger <scotthenninger@gmail.com>
- Date: Wed, 15 Sep 2021 16:33:57 -0500
- To: "public-shacl@w3.org" <public-shacl@w3.org>
- Message-ID: <CAJ-KvVqLHbrGu00y0Z8w_8WR9VsQ1eW6+_nwZxBbfr=8Ehh9ZQ@mail.gmail.com>
Thank you for your considered ideas, Irene. Unfortunately, while I've
asked for an enhancement for SHACL AF, in the meantime I must rely on SHACL
alone. Also, the system is used by downstream systems and others.
Therefore changing the class structure is not an option (that's a
real-world constraint).
I'll be more careful with my terminology. Thanks for the explanation.
-- Scott
On Wed, Sep 15, 2021 at 12:56 PM Irene Polikoff <irene@topquadrant.com>
wrote:
> 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> 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:34:24 UTC