RDFLib / pySHACL

A Python validator for SHACL
Apache License 2.0
241 stars 63 forks source link

How to ensure subject is not a sh:BlankNode when validating a owl:Class in an ontology? #210

Closed tduval-unifylogic closed 6 months ago

tduval-unifylogic commented 9 months ago

I've got a little brain twister here and can't seem to figure it out and I need some help.

I want to validate classes so I'm specifying the following where sh:targetClass is owl:Class and as you can see, I have an owl:equivalentClass [ owl:oneOf ] in class UnitMultiplier, indicating it's an enumeration of individuals.

I would like to put something on the shape to indicate that that shape is only for those classes whose subject is a sh:IRI (and not a blank node). Can someone show me what I'm missing? The commented code in the SHACL Graph is what I tried, but I'm not sure if it ACTUALLY works.

I am also interested in the inverse as well, I want to create another shape where I want the subjects TO be Blank Nodes where the sh:targetClass is owl:Class, so I am hoping for a hint at that as well.

Any help is GREATLY appreciated!

Data Graph:

@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix model.1.0: <http://www.example.com/ex#> .

model.1.0:UnitSymbol a owl:Class ;
    rdfs:label "UnitSymbol" ;
    dcterms:description """Multi line 
    description.""" ;
    owl:equivalentClass [ a owl:Class ;
            owl:oneOf model.1.0:UnitSymbol.A ] ;
.
model.1.0:UnitSymbol.A a model.1.0:UnitSymbol,
        owl:NamedIndividual ;
    rdfs:label "A" ;
    dcterms:description "Current in amperes." ;
.

SHACL Graph:

@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix owl:   <http://www.w3.org/2002/07/owl#> .
@prefix rdf:   <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs:  <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh:    <http://www.w3.org/ns/shacl#> .
@prefix xsd:   <http://www.w3.org/2001/XMLSchema#> .
@prefix : <http://affirma.xtensible.net/1.0#> .

:Class-axiom 
        a                       sh:NodeShape ;
        sh:closed               true ;
        sh:targetClass          owl:Class ;
        sh:ignoredProperties  (
                                owl:oneOf
                                rdf:type
                            ) ;
        sh:property           :Class-dctermsDescription ,
                                # :Class-rdfSubject ,
                                :Class-rdfsLabel
.
:Class-dctermsDescription
        a                       sh:PropertyShape ;
        sh:datatype             xsd:string ;
        sh:path                 dcterms:description 
. 
:Class-rdfsLabel
        a                       sh:PropertyShape ;
        sh:datatype             xsd:string ;
        sh:path                 rdfs:label 
.
# :Class-rdfSubject
#         a                       sh:PropertyShape ;
#         sh:not                  [ sh:class sh:BlankNode ] ;
#         sh:path                 rdf:subject 
# .

Results:

{'@type': 'ValidationReport',
  'conforms': False,
  'result': {'@type': 'ValidationResult',
   'focusNode': 'http://www.example.com/ex#UnitSymbol',
   'resultMessage': 'Node model.1.0:UnitSymbol is closed. It cannot have value: [ owl:oneOf model.1.0:UnitSymbol.A ; rdf:type owl:Class ]',
   'resultPath': 'http://www.w3.org/2002/07/owl#equivalentClass',
   'resultSeverity': 'sh:Violation',
   'sourceConstraintComponent': 'sh:ClosedConstraintComponent',
   'value': {'@type': 'http://www.w3.org/2002/07/owl#Class',
    'http://www.w3.org/2002/07/owl#oneOf': {'@id': 'http://www.example.com/ex#UnitSymbol.A'}}}}
ajnelson-nist commented 9 months ago

Possibly as an aside, possibly helpful, I have a higher-level question for you: Are you trying to write SHACL rules for validating OWL-2 DL conformance?

I've been contributing to a similar objective, with current status housed in this repository[^1]. That repository is due for some coverage updates, as its scope somewhat reflects usage of a particular ontology community. But it's possible to take it much farther, covering a good deal of the OWL 2 to RDF mapping. I'm working through some logistics in testing its updates.

Back to your question, one shapes development strategy I've been finding is that sometimes you need to focus on classes, and sometimes you need to focus on properties. I've found that sometimes, this particular form is helpful, and I think it'll help you here too.

Some OWL predicates are syntactically restricted to only work on anonymous classes. E.g. owl:inverseOf, which if you look in the OWL 2 to RDF mapping, is only ever used in triples where the subject is a blank node. So a shape validating subjects of owl:inverseOf being blank would be:

ex:owl-inverseOf-subjects-shape
  a sh:NodeShape ;
  sh:description "See Table 1, Row 21 of OWL--RDF document"@en ;
  rdfs:seeAlso <https://www.w3.org/TR/2012/REC-owl2-mapping-to-rdf-20121211/> ;
  sh:nodeKind sh:BlankNode ;
  sh:targetSubjectsOf owl:inverseOf ;
  .

(This is newer thinking than I was using when I posted #172 . Since 172, I've found ample use cases for sh:NodeShapes using sh:targetSubjectsOf and sh:targetObjectsOf.)

[^1]: Disclaimer: Participation by NIST in the creation of the documentation of mentioned software is not intended to imply a recommendation or endorsement by the National Institute of Standards and Technology, nor is it intended to imply that any specific software is necessarily the best available for the purpose.

I hope that helps.

tduval-unifylogic commented 9 months ago

Thanks for your reply(ies)!

The motivation here is to aid transaction management and Referential Integrity of a Conjunctive Graph where I have multiple named graphs with references both to themselves and each other. Am creating the ability during a transaction to identify if fractures are created when CRUD'ing and rollback if necessary.

I'm looking to create one shape to validate normal classes and another for validating enumeration type classes (those that contain owl:equivalentClass)

I tried this shape and it does not seem to have desired result. I get back a validation report stating that the subject is not a uri (which means is working). I need this to be more of a filter than a validation error.

How do i 'filter' in the shape to only look for one condition or the other? e.g., a shape that only validates Classes whose subject is an IRI vs another shape that validates Classes where the subject is a blank node?

Thanks!

tduval-unifylogic commented 9 months ago

should i be using a sh:rule?

tduval-unifylogic commented 9 months ago

I think I figured it out:

@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix owl:   <http://www.w3.org/2002/07/owl#> .
@prefix rdf:   <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs:  <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh:    <http://www.w3.org/ns/shacl#> .
@prefix xsd:   <http://www.w3.org/2001/XMLSchema#> .
@prefix : <http://affirma.xtensible.net/1.0#> .

:Base-Description-Shape
    a sh:PropertyShape ;
    sh:path dcterms:description ;
    sh:nodeKind sh:Literal ;
    sh:datatype xsd:string ;
    sh:minCount 1 ;
.
:Base-Label-Shape
    a sh:PropertyShape ;
    sh:path rdfs:label ;
    sh:nodeKind sh:Literal ;
    sh:datatype xsd:string ;
    sh:minCount 1 ;
.
:Class-Shape
    a sh:NodeShape ;
    sh:closed true ;
    sh:ignoredProperties ( rdf:type ) ;
    sh:target [
        a sh:SPARQLTarget ;
        sh:select """
        SELECT ?this 
        WHERE {
            ?this a owl:Class, sh:NodeShape .
        }
        """ ;
    ] ;
    sh:property :Base-Description-Shape ,
                    :Base-Label-Shape ;
.
:Enum-Shape
    a sh:NodeShape ;
    sh:closed true ;
    sh:ignoredProperties ( rdf:type owl:equivalentClass) ;
    sh:target [
        a sh:SPARQLTarget ;
        sh:select """
        SELECT ?this 
        WHERE {
            ?this a owl:Class ;
            owl:equivalentClass ?equiv .
            filter (isBlank(?equiv) && !isBlank(?this))
        }
        """ ;
    ] ;
    sh:property :Base-Description-Shape ,
                    :Base-Label-Shape ;
.
:oneOf-Shape
    a sh:NodeShape ;
    sh:closed true ;
    sh:ignoredProperties ( rdf:type owl:equivalentClass) ;
    sh:target [
        a sh:SPARQLTarget ;
        sh:select """
        SELECT ?this 
        WHERE {
            ?this a owl:Class ;
            owl:oneOf ?oneOf .
            filter (!isBlank(?oneOf) && isBlank(?this))
        }
        """ ;
    ] ;
    sh:property [
        sh:path owl:oneOf ;
        sh:nodeKind sh:IRI ;
        sh:class owl:NamedIndividual ;
    ]
.