././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1737416745.643437 owlrl-7.1.3/LICENSE.txt0000644000000000000000000000333214743560052011441 0ustar00W3C SOFTWARE AND DOCUMENT NOTICE AND LICENSE License By obtaining and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions. Permission to copy, modify, and distribute this work, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the work or portions thereof, including modifications: 1. The full text of this NOTICE in a location viewable to users of the redistributed or derivative work. 2. Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none exist, the W3C Software and Document Short Notice should be included. 3. Notice of any changes or modifications, through a copyright statement on the new code or document such as "This software or document includes material copied from or derived from [title and URI of the W3C document]. Copyright © [YEAR] W3C® (MIT, ERCIM, Keio, Beihang)." Disclaimers THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT. The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the work without specific, written prior permission. Title to copyright in this work will at all times remain with copyright holders. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1737422979.1710331 owlrl-7.1.3/README.rst0000644000000000000000000000550214743574203011311 0ustar00|Original Author DOI| |PyPI badge| |OWL-RL Logo| .. |Original Author DOI| image:: https://zenodo.org/badge/9385/RDFLib/OWL-RL.svg :target: http://dx.doi.org/10.5281/zenodo.14543 .. |PyPI badge| image:: https://badge.fury.io/py/owlrl.svg :target: https://badge.fury.io/py/owlrl .. |OWL-RL Logo| image:: https://raw.githubusercontent.com/RDFLib/OWL-RL/master/OWL-RL.png :width: 250 :target: http://owl-rl.readthedocs.io/ OWL-RL ====== A simple implementation of the OWL2 RL Profile, as well as a basic RDFS inference, on top of RDFLib, based on forward chaining. This package is a Python library that also contains a couple of scripts: * `scripts/RDFConvertService`: a CGI script to invoke the library. It may have to be adapted to the local server setup. * `scripts/owlrl`: a script that can be run locally on to transform a file into RDF (on the standard output). Run the script with `-h` to get the available flags. Installation ------------ This package requires RDFLib 7.1.3 as its only dependency and it can be installed from the Python Package index in the usual way: :: pip install owlrl or :: poetry add owlrl Use --- This package can run inference according to RDFS and/or OWL-RL. For details on RDFS, see the `RDF Semantics Specification`_; for OWL 2 RL, see the `OWL 2 Profile specification`_. .. _RDF Semantics Specification: http://www.w3.org/TR/rdf11-mt/ .. _OWL 2 Profile specification: http://www.w3.org/TR/owl2-profiles/#Reasoning_in_OWL_2_RL_and_RDF_Graphs_using_Rules View the **OWL-RL documentation** online: http://owl-rl.readthedocs.io/ License ------- This software is released under the W3C© SOFTWARE NOTICE AND LICENSE. See `LICENSE.txt `_. Support & Contacts ------------------ For general "how do I..." queries, please use https://stackoverflow.com and tag your question with ``rdflib``. Existing questions: * https://stackoverflow.com/questions/tagged/rdflib If you want to contact the rdflib maintainers, please do so via: * the rdflib-dev mailing list: https://groups.google.com/group/rdflib-dev * the chat, which is available at `gitter `_ or via matrix `#RDFLib_rdflib:gitter.im `_ Development ----------- Changes ~~~~~~~ To view the changelog for this software library, see `CHANGELOG.rst `_. Release Procedure ~~~~~~~~~~~~~~~~~ * update all the version numbers * pyproject.toml * README.rst * remove the current ``dist/`` dir * build the new distribution * test the metadata rendering * test push it to PyPI * actually push it to PyPI :: rm -vf dist/* ç bsdtar -xvf dist/owlrl-*.whl -O '*/METADATA' | view - bsdtar -xvf dist/owlrl-*.tar.gz -O '*/PKG-INFO' | view - poetry publish --dry-run poetry publish -u __token__ -p ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1737416745.6452074 owlrl-7.1.3/owlrl/AxiomaticTriples.py0000755000000000000000000006457114743560052014627 0ustar00# -*- coding: utf-8 -*- # """ Axiomatic triples to be (possibly) added to the final graph. **Requires**: `RDFLib`_, 4.0.0 and higher. .. _RDFLib: https://github.com/RDFLib/rdflib **License**: This software is available for use under the `W3C Software License`_. .. _W3C Software License: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 **Organization**: `World Wide Web Consortium`_ .. _World Wide Web Consortium: http://www.w3.org **Author**: `Ivan Herman`_ .. _Ivan Herman: http://www.w3.org/People/Ivan/ """ __author__ = "Ivan Herman" __contact__ = "Ivan Herman, ivan@w3.org" __license__ = "W3C® SOFTWARE NOTICE AND LICENSE, http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231" from rdflib.namespace import OWL, RDF, RDFS, XSD # Simple RDF axiomatic triples (typing of subject, predicate, first, rest, etc) _Simple_RDF_axiomatic_triples = [ (RDF.type, RDF.type, RDF.Property), (RDF.subject, RDF.type, RDF.Property), (RDF.predicate, RDF.type, RDF.Property), (RDF.object, RDF.type, RDF.Property), (RDF.first, RDF.type, RDF.Property), (RDF.rest, RDF.type, RDF.Property), (RDF.value, RDF.type, RDF.Property), (RDF.nil, RDF.type, RDF.List), ] # RDFS axiomatic triples (domain and range, as well as class setting for a number of RDFS symbols) _RDFS_axiomatic_triples = [ (RDF.type, RDFS.domain, RDFS.Resource), (RDFS.domain, RDFS.domain, RDF.Property), (RDFS.range, RDFS.domain, RDF.Property), (RDFS.subPropertyOf, RDFS.domain, RDF.Property), (RDFS.subClassOf, RDFS.domain, RDFS.Class), (RDF.subject, RDFS.domain, RDF.Statement), (RDF.predicate, RDFS.domain, RDF.Statement), (RDF.object, RDFS.domain, RDF.Statement), (RDFS.member, RDFS.domain, RDFS.Resource), (RDF.first, RDFS.domain, RDF.List), (RDF.rest, RDFS.domain, RDF.List), (RDFS.seeAlso, RDFS.domain, RDFS.Resource), (RDFS.isDefinedBy, RDFS.domain, RDFS.Resource), (RDFS.comment, RDFS.domain, RDFS.Resource), (RDFS.label, RDFS.domain, RDFS.Resource), (RDF.value, RDFS.domain, RDFS.Resource), (RDF.Property, RDF.type, RDFS.Class), (RDF.type, RDFS.range, RDFS.Class), (RDFS.domain, RDFS.range, RDFS.Class), (RDFS.range, RDFS.range, RDFS.Class), (RDFS.subPropertyOf, RDFS.range, RDF.Property), (RDFS.subClassOf, RDFS.range, RDFS.Class), (RDF.subject, RDFS.range, RDFS.Resource), (RDF.predicate, RDFS.range, RDFS.Resource), (RDF.object, RDFS.range, RDFS.Resource), (RDFS.member, RDFS.range, RDFS.Resource), (RDF.first, RDFS.range, RDFS.Resource), (RDF.rest, RDFS.range, RDF.List), (RDFS.seeAlso, RDFS.range, RDFS.Resource), (RDFS.isDefinedBy, RDFS.range, RDFS.Resource), (RDFS.comment, RDFS.range, RDFS.Literal), (RDFS.label, RDFS.range, RDFS.Literal), (RDF.value, RDFS.range, RDFS.Resource), (RDF.Alt, RDFS.subClassOf, RDFS.Container), (RDF.Bag, RDFS.subClassOf, RDFS.Container), (RDF.Seq, RDFS.subClassOf, RDFS.Container), (RDFS.ContainerMembershipProperty, RDFS.subClassOf, RDF.Property), (RDFS.isDefinedBy, RDFS.subPropertyOf, RDFS.seeAlso), (RDF.XMLLiteral, RDF.type, RDFS.Datatype), (RDF.XMLLiteral, RDFS.subClassOf, RDFS.Literal), (RDFS.Datatype, RDFS.subClassOf, RDFS.Class), # rdfs valid triples; these would be inferred by the RDFS expansion, but it may make things # a bit faster to add these upfront (RDFS.Resource, RDF.type, RDFS.Class), (RDFS.Class, RDF.type, RDFS.Class), (RDFS.Literal, RDF.type, RDFS.Class), (RDF.XMLLiteral, RDF.type, RDFS.Class), (RDFS.Datatype, RDF.type, RDFS.Class), (RDF.Seq, RDF.type, RDFS.Class), (RDF.Bag, RDF.type, RDFS.Class), (RDF.Alt, RDF.type, RDFS.Class), (RDFS.Container, RDF.type, RDFS.Class), (RDF.List, RDF.type, RDFS.Class), (RDFS.ContainerMembershipProperty, RDF.type, RDFS.Class), (RDF.Property, RDF.type, RDFS.Class), (RDF.Statement, RDF.type, RDFS.Class), (RDFS.domain, RDF.type, RDF.Property), (RDFS.range, RDF.type, RDF.Property), (RDFS.subPropertyOf, RDF.type, RDF.Property), (RDFS.subClassOf, RDF.type, RDF.Property), (RDFS.member, RDF.type, RDF.Property), (RDFS.seeAlso, RDF.type, RDF.Property), (RDFS.isDefinedBy, RDF.type, RDF.Property), (RDFS.comment, RDF.type, RDF.Property), (RDFS.label, RDF.type, RDF.Property), ] # RDFS Axiomatic Triples all together RDFS_Axiomatic_Triples = _Simple_RDF_axiomatic_triples + _RDFS_axiomatic_triples # RDFS D-entailement triples, ie, possible subclassing of various datatypes RDFS_D_Axiomatic_Triples_subclasses = [ # See http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#built-in-datatypes (XSD.decimal, RDFS.subClassOf, RDFS.Literal), (XSD.integer, RDFS.subClassOf, XSD.decimal), (XSD.long, RDFS.subClassOf, XSD.integer), (XSD.int, RDFS.subClassOf, XSD.long), (XSD.short, RDFS.subClassOf, XSD.int), (XSD.byte, RDFS.subClassOf, XSD.short), (XSD.nonNegativeInteger, RDFS.subClassOf, XSD.integer), (XSD.positiveInteger, RDFS.subClassOf, XSD.nonNegativeInteger), (XSD.unsignedLong, RDFS.subClassOf, XSD.nonNegativeInteger), (XSD.unsignedInt, RDFS.subClassOf, XSD.unsignedLong), (XSD.unsignedShort, RDFS.subClassOf, XSD.unsignedInt), (XSD.unsignedByte, RDFS.subClassOf, XSD.unsignedShort), (XSD.nonPositiveInteger, RDFS.subClassOf, XSD.integer), (XSD.negativeInteger, RDFS.subClassOf, XSD.nonPositiveInteger), (XSD.normalizedString, RDFS.subClassOf, XSD.string), (XSD.token, RDFS.subClassOf, XSD.normalizedString), (XSD.language, RDFS.subClassOf, XSD.token), (XSD.Name, RDFS.subClassOf, XSD.token), (XSD.NMTOKEN, RDFS.subClassOf, XSD.token), (XSD.NCName, RDFS.subClassOf, XSD.Name), (XSD.dateTimeStamp, RDFS.subClassOf, XSD.dateTime), ] # RDFS_D_Axiomatic_Triples_types = [ (XSD.integer, RDF.type, RDFS.Datatype), (XSD.decimal, RDF.type, RDFS.Datatype), (XSD.nonPositiveInteger, RDF.type, RDFS.Datatype), (XSD.nonPositiveInteger, RDF.type, RDFS.Datatype), (XSD.positiveInteger, RDF.type, RDFS.Datatype), (XSD.positiveInteger, RDF.type, RDFS.Datatype), (XSD.long, RDF.type, RDFS.Datatype), (XSD.int, RDF.type, RDFS.Datatype), (XSD.short, RDF.type, RDFS.Datatype), (XSD.byte, RDF.type, RDFS.Datatype), (XSD.unsignedLong, RDF.type, RDFS.Datatype), (XSD.unsignedInt, RDF.type, RDFS.Datatype), (XSD.unsignedShort, RDF.type, RDFS.Datatype), (XSD.unsignedByte, RDF.type, RDFS.Datatype), (XSD.float, RDF.type, RDFS.Datatype), (XSD.double, RDF.type, RDFS.Datatype), (XSD.string, RDF.type, RDFS.Datatype), (XSD.normalizedString, RDF.type, RDFS.Datatype), (XSD.token, RDF.type, RDFS.Datatype), (XSD.language, RDF.type, RDFS.Datatype), (XSD.Name, RDF.type, RDFS.Datatype), (XSD.NCName, RDF.type, RDFS.Datatype), (XSD.NMTOKEN, RDF.type, RDFS.Datatype), (XSD.boolean, RDF.type, RDFS.Datatype), (XSD.hexBinary, RDF.type, RDFS.Datatype), (XSD.base64Binary, RDF.type, RDFS.Datatype), (XSD.anyURI, RDF.type, RDFS.Datatype), (XSD.dateTimeStamp, RDF.type, RDFS.Datatype), (XSD.dateTime, RDF.type, RDFS.Datatype), (RDFS.Literal, RDF.type, RDFS.Datatype), (RDF.XMLLiteral, RDF.type, RDFS.Datatype), ] RDFS_D_Axiomatic_Triples = ( RDFS_D_Axiomatic_Triples_types + RDFS_D_Axiomatic_Triples_subclasses ) # OWL Class axiomatic triples: definition of special classes _OWL_axiomatic_triples_Classes = [ (OWL.AllDifferent, RDF.type, RDFS.Class), (OWL.AllDifferent, RDFS.subClassOf, RDFS.Resource), (OWL.AllDisjointClasses, RDF.type, RDFS.Class), (OWL.AllDisjointClasses, RDFS.subClassOf, RDFS.Resource), (OWL.AllDisjointProperties, RDF.type, RDFS.Class), (OWL.AllDisjointProperties, RDFS.subClassOf, RDFS.Resource), (OWL.Annotation, RDF.type, RDFS.Class), (OWL.Annotation, RDFS.subClassOf, RDFS.Resource), (OWL.AnnotationProperty, RDF.type, RDFS.Class), (OWL.AnnotationProperty, RDFS.subClassOf, RDF.Property), (OWL.AsymmetricProperty, RDF.type, RDFS.Class), (OWL.AsymmetricProperty, RDFS.subClassOf, RDF.Property), (OWL.Class, RDF.type, RDFS.Class), (OWL.Class, OWL.equivalentClass, RDFS.Class), # (OWL.DataRange, RDF.type, RDFS.Class), # (OWL.DataRange, OWL.equivalentClass, RDFS.Datatype), (RDFS.Datatype, RDF.type, RDFS.Class), (OWL.DatatypeProperty, RDF.type, RDFS.Class), (OWL.DatatypeProperty, RDFS.subClassOf, RDF.Property), (OWL.DeprecatedClass, RDF.type, RDFS.Class), (OWL.DeprecatedClass, RDFS.subClassOf, RDFS.Class), (OWL.DeprecatedProperty, RDF.type, RDFS.Class), (OWL.DeprecatedProperty, RDFS.subClassOf, RDF.Property), (OWL.FunctionalProperty, RDF.type, RDFS.Class), (OWL.FunctionalProperty, RDFS.subClassOf, RDF.Property), (OWL.InverseFunctionalProperty, RDF.type, RDFS.Class), (OWL.InverseFunctionalProperty, RDFS.subClassOf, RDF.Property), (OWL.IrreflexiveProperty, RDF.type, RDFS.Class), (OWL.IrreflexiveProperty, RDFS.subClassOf, RDF.Property), (RDFS.Literal, RDF.type, RDFS.Datatype), # (OWL.NamedIndividual, RDF.type, RDFS.Class), # (OWL.NamedIndividual, OWL.equivalentClass, RDFS.Resource), (OWL.NegativePropertyAssertion, RDF.type, RDFS.Class), (OWL.NegativePropertyAssertion, RDFS.subClassOf, RDFS.Resource), (OWL.Nothing, RDF.type, RDFS.Class), (OWL.Nothing, RDFS.subClassOf, OWL.Thing), (OWL.ObjectProperty, RDF.type, RDFS.Class), (OWL.ObjectProperty, OWL.equivalentClass, RDF.Property), (OWL.Ontology, RDF.type, RDFS.Class), (OWL.Ontology, RDFS.subClassOf, RDFS.Resource), (OWL.OntologyProperty, RDF.type, RDFS.Class), (OWL.OntologyProperty, RDFS.subClassOf, RDF.Property), (RDF.Property, RDF.type, RDFS.Class), (OWL.ReflexiveProperty, RDF.type, RDFS.Class), (OWL.ReflexiveProperty, RDFS.subClassOf, RDF.Property), (OWL.Restriction, RDF.type, RDFS.Class), (OWL.Restriction, RDFS.subClassOf, RDFS.Class), (OWL.SymmetricProperty, RDF.type, RDFS.Class), (OWL.SymmetricProperty, RDFS.subClassOf, RDF.Property), (OWL.Thing, RDF.type, RDFS.Class), (OWL.Thing, RDFS.subClassOf, RDFS.Resource), (OWL.TransitiveProperty, RDF.type, RDFS.Class), (OWL.TransitiveProperty, RDFS.subClassOf, RDF.Property), # OWL valid triples; some of these would be inferred by the OWL RL expansion, but it may make things # a bit faster to add these upfront (OWL.AllDisjointProperties, RDF.type, OWL.Class), (OWL.AllDisjointClasses, RDF.type, OWL.Class), (OWL.AllDisjointProperties, RDF.type, OWL.Class), (OWL.Annotation, RDF.type, OWL.Class), (OWL.AsymmetricProperty, RDF.type, OWL.Class), (OWL.Axiom, RDF.type, OWL.Class), (OWL.DataRange, RDF.type, OWL.Class), (RDFS.Datatype, RDF.type, OWL.Class), (OWL.DatatypeProperty, RDF.type, OWL.Class), (OWL.DeprecatedClass, RDF.type, OWL.Class), (OWL.DeprecatedClass, RDFS.subClassOf, OWL.Class), (OWL.DeprecatedProperty, RDF.type, OWL.Class), (OWL.FunctionalProperty, RDF.type, OWL.Class), (OWL.InverseFunctionalProperty, RDF.type, OWL.Class), (OWL.IrreflexiveProperty, RDF.type, OWL.Class), (OWL.NamedIndividual, RDF.type, OWL.Class), (OWL.NegativePropertyAssertion, RDF.type, OWL.Class), (OWL.Nothing, RDF.type, OWL.Class), (OWL.ObjectProperty, RDF.type, OWL.Class), (OWL.Ontology, RDF.type, OWL.Class), (OWL.OntologyProperty, RDF.type, OWL.Class), (RDF.Property, RDF.type, OWL.Class), (OWL.ReflexiveProperty, RDF.type, OWL.Class), (OWL.Restriction, RDF.type, OWL.Class), (OWL.Restriction, RDFS.subClassOf, OWL.Class), # (OWL.SelfRestriction, RDF.type, OWL.Class), (OWL.SymmetricProperty, RDF.type, OWL.Class), (OWL.Thing, RDF.type, OWL.Class), (OWL.TransitiveProperty, RDF.type, OWL.Class), ] # OWL Property axiomatic triples: definition of domains and ranges _OWL_axiomatic_triples_Properties = [ (OWL.allValuesFrom, RDF.type, RDF.Property), (OWL.allValuesFrom, RDFS.domain, OWL.Restriction), (OWL.allValuesFrom, RDFS.range, RDFS.Class), (OWL.assertionProperty, RDF.type, RDF.Property), (OWL.assertionProperty, RDFS.domain, OWL.NegativePropertyAssertion), (OWL.assertionProperty, RDFS.range, RDF.Property), (OWL.backwardCompatibleWith, RDF.type, OWL.OntologyProperty), (OWL.backwardCompatibleWith, RDF.type, OWL.AnnotationProperty), (OWL.backwardCompatibleWith, RDFS.domain, OWL.Ontology), (OWL.backwardCompatibleWith, RDFS.range, OWL.Ontology), # (OWL.bottomDataProperty, RDF.type, RDFS.DatatypeProperty), # (OWL.bottomObjectProperty, RDF.type, OWL.ObjectProperty), # (OWL.cardinality, RDF.type, RDF.Property), # (OWL.cardinality, RDFS.domain, OWL.Restriction), # (OWL.cardinality, RDFS.range, XSD.nonNegativeInteger), (RDFS.comment, RDF.type, OWL.AnnotationProperty), (RDFS.comment, RDFS.domain, RDFS.Resource), (RDFS.comment, RDFS.range, RDFS.Literal), (OWL.complementOf, RDF.type, RDF.Property), (OWL.complementOf, RDFS.domain, RDFS.Class), (OWL.complementOf, RDFS.range, RDFS.Class), # (OWL.datatypeComplementOf, RDF.type, RDF.Property), # (OWL.datatypeComplementOf, RDFS.domain, RDFS.Datatype), # (OWL.datatypeComplementOf, RDFS.range, RDFS.Datatype), (OWL.deprecated, RDF.type, OWL.AnnotationProperty), (OWL.deprecated, RDFS.domain, RDFS.Resource), (OWL.deprecated, RDFS.range, RDFS.Resource), (OWL.differentFrom, RDF.type, RDF.Property), (OWL.differentFrom, RDFS.domain, RDFS.Resource), (OWL.differentFrom, RDFS.range, RDFS.Resource), # (OWL.disjointUnionOf, RDF.type, RDF.Property), # (OWL.disjointUnionOf, RDFS.domain, RDFS.Class), # (OWL.disjointUnionOf, RDFS.range, RDF.List), (OWL.disjointWith, RDF.type, RDF.Property), (OWL.disjointWith, RDFS.domain, RDFS.Class), (OWL.disjointWith, RDFS.range, RDFS.Class), (OWL.distinctMembers, RDF.type, RDF.Property), (OWL.distinctMembers, RDFS.domain, OWL.AllDifferent), (OWL.distinctMembers, RDFS.range, RDF.List), (OWL.equivalentClass, RDF.type, RDF.Property), (OWL.equivalentClass, RDFS.domain, RDFS.Class), (OWL.equivalentClass, RDFS.range, RDFS.Class), (OWL.equivalentProperty, RDF.type, RDF.Property), (OWL.equivalentProperty, RDFS.domain, RDF.Property), (OWL.equivalentProperty, RDFS.range, RDF.Property), (OWL.hasKey, RDF.type, RDF.Property), (OWL.hasKey, RDFS.domain, RDFS.Class), (OWL.hasKey, RDFS.range, RDF.List), (OWL.hasValue, RDF.type, RDF.Property), (OWL.hasValue, RDFS.domain, OWL.Restriction), (OWL.hasValue, RDFS.range, RDFS.Resource), (OWL.imports, RDF.type, OWL.OntologyProperty), (OWL.imports, RDFS.domain, OWL.Ontology), (OWL.imports, RDFS.range, OWL.Ontology), (OWL.incompatibleWith, RDF.type, OWL.OntologyProperty), (OWL.incompatibleWith, RDF.type, OWL.AnnotationProperty), (OWL.incompatibleWith, RDFS.domain, OWL.Ontology), (OWL.incompatibleWith, RDFS.range, OWL.Ontology), (OWL.intersectionOf, RDF.type, RDF.Property), (OWL.intersectionOf, RDFS.domain, RDFS.Class), (OWL.intersectionOf, RDFS.range, RDF.List), (OWL.inverseOf, RDF.type, RDF.Property), (OWL.inverseOf, RDFS.domain, RDF.Property), (OWL.inverseOf, RDFS.range, RDF.Property), (RDFS.isDefinedBy, RDF.type, OWL.AnnotationProperty), (RDFS.isDefinedBy, RDFS.domain, RDFS.Resource), (RDFS.isDefinedBy, RDFS.range, RDFS.Resource), (RDFS.label, RDF.type, OWL.AnnotationProperty), (RDFS.label, RDFS.domain, RDFS.Resource), (RDFS.label, RDFS.range, RDFS.Literal), (OWL.maxCardinality, RDF.type, RDF.Property), (OWL.maxCardinality, RDFS.domain, OWL.Restriction), (OWL.maxCardinality, RDFS.range, XSD.nonNegativeInteger), (OWL.maxQualifiedCardinality, RDF.type, RDF.Property), (OWL.maxQualifiedCardinality, RDFS.domain, OWL.Restriction), (OWL.maxQualifiedCardinality, RDFS.range, XSD.nonNegativeInteger), (OWL.members, RDF.type, RDF.Property), (OWL.members, RDFS.domain, RDFS.Resource), (OWL.members, RDFS.range, RDF.List), # (OWL.minCardinality, RDF.type, RDF.Property), # (OWL.minCardinality, RDFS.domain, OWL.Restriction), # (OWL.minCardinality, RDFS.range, XSD.nonNegativeInteger), # (OWL.minQualifiedCardinality, RDF.type, RDF.Property), # (OWL.minQualifiedCardinality, RDFS.domain, OWL.Restriction), # (OWL.minQualifiedCardinality, RDFS.range, XSD.nonNegativeInteger), # (OWL.annotatedTarget, RDF.type, RDF.Property), # (OWL.annotatedTarget, RDFS.domain, RDFS.Resource), # (OWL.annotatedTarget, RDFS.range, RDFS.Resource), (OWL.onClass, RDF.type, RDF.Property), (OWL.onClass, RDFS.domain, OWL.Restriction), (OWL.onClass, RDFS.range, RDFS.Class), # (OWL.onDataRange, RDF.type, RDF.Property), # (OWL.onDataRange, RDFS.domain, OWL.Restriction), # (OWL.onDataRange, RDFS.range, RDFS.Datatype), (OWL.onDatatype, RDF.type, RDF.Property), (OWL.onDatatype, RDFS.domain, RDFS.Datatype), (OWL.onDatatype, RDFS.range, RDFS.Datatype), (OWL.oneOf, RDF.type, RDF.Property), (OWL.oneOf, RDFS.domain, RDFS.Class), (OWL.oneOf, RDFS.range, RDF.List), (OWL.onProperty, RDF.type, RDF.Property), (OWL.onProperty, RDFS.domain, OWL.Restriction), (OWL.onProperty, RDFS.range, RDF.Property), # (OWL.onProperties, RDF.type, RDF.Property), # (OWL.onProperties, RDFS.domain, OWL.Restriction), # (OWL.onProperties, RDFS.range, RDF.List), # (OWL.annotatedProperty, RDF.type, RDF.Property), # (OWL.annotatedProperty, RDFS.domain, RDFS.Resource), # (OWL.annotatedProperty, RDFS.range, RDF.Property), (OWL.priorVersion, RDF.type, OWL.OntologyProperty), (OWL.priorVersion, RDF.type, OWL.AnnotationProperty), (OWL.priorVersion, RDFS.domain, OWL.Ontology), (OWL.priorVersion, RDFS.range, OWL.Ontology), (OWL.propertyChainAxiom, RDF.type, RDF.Property), (OWL.propertyChainAxiom, RDFS.domain, RDF.Property), (OWL.propertyChainAxiom, RDFS.range, RDF.List), # (OWL.propertyDisjointWith, RDF.type, RDF.Property), # (OWL.propertyDisjointWith, RDFS.domain, RDF.Property), # (OWL.propertyDisjointWith, RDFS.range, RDF.Property), # # (OWL.qualifiedCardinality, RDF.type, RDF.Property), # (OWL.qualifiedCardinality, RDFS.domain, OWL.Restriction), # (OWL.qualifiedCardinality, RDFS.range, XSD.nonNegativeInteger), (OWL.sameAs, RDF.type, RDF.Property), (OWL.sameAs, RDFS.domain, RDFS.Resource), (OWL.sameAs, RDFS.range, RDFS.Resource), (RDFS.seeAlso, RDF.type, OWL.AnnotationProperty), (RDFS.seeAlso, RDFS.domain, RDFS.Resource), (RDFS.seeAlso, RDFS.range, RDFS.Resource), (OWL.someValuesFrom, RDF.type, RDF.Property), (OWL.someValuesFrom, RDFS.domain, OWL.Restriction), (OWL.someValuesFrom, RDFS.range, RDFS.Class), (OWL.sourceIndividual, RDF.type, RDF.Property), (OWL.sourceIndividual, RDFS.domain, OWL.NegativePropertyAssertion), (OWL.sourceIndividual, RDFS.range, RDFS.Resource), # # (OWL.annotatedSource, RDF.type, RDF.Property), # (OWL.annotatedSource, RDFS.domain, RDFS.Resource), # (OWL.annotatedSource, RDFS.range, RDFS.Resource), # (OWL.targetIndividual, RDF.type, RDF.Property), (OWL.targetIndividual, RDFS.domain, OWL.NegativePropertyAssertion), (OWL.targetIndividual, RDFS.range, RDFS.Resource), (OWL.targetValue, RDF.type, RDF.Property), (OWL.targetValue, RDFS.domain, OWL.NegativePropertyAssertion), (OWL.targetValue, RDFS.range, RDFS.Literal), # (OWL.topDataProperty, RDF.type, RDFS.DatatypeProperty), # (OWL.topDataProperty, RDFS.domain, RDFS.Resource), # (OWL.topDataProperty, RDFS.range, RDFS.Literal), # # (OWL.topObjectProperty, RDF.type, OWL.ObjectProperty), # (OWL.topObjectProperty, RDFS.domain, RDFS.Resource), # (OWL.topObjectProperty, RDFS.range, RDFS.Resource), (OWL.unionOf, RDF.type, RDF.Property), (OWL.unionOf, RDFS.domain, RDFS.Class), (OWL.unionOf, RDFS.range, RDF.List), (OWL.versionInfo, RDF.type, OWL.AnnotationProperty), (OWL.versionInfo, RDFS.domain, RDFS.Resource), (OWL.versionInfo, RDFS.range, RDFS.Resource), (OWL.versionIRI, RDF.type, OWL.AnnotationProperty), (OWL.versionIRI, RDFS.domain, RDFS.Resource), (OWL.versionIRI, RDFS.range, RDFS.Resource), (OWL.withRestrictions, RDF.type, RDF.Property), (OWL.withRestrictions, RDFS.domain, RDFS.Datatype), (OWL.withRestrictions, RDFS.range, RDF.List), # some OWL valid triples; these would be inferred by the OWL RL expansion, but it may make things # a bit faster to add these upfront (OWL.allValuesFrom, RDFS.range, OWL.Class), (OWL.complementOf, RDFS.domain, OWL.Class), (OWL.complementOf, RDFS.range, OWL.Class), # (OWL.datatypeComplementOf, domain, OWL.DataRange), # (OWL.datatypeComplementOf, range, OWL.DataRange), (OWL.disjointUnionOf, RDFS.domain, OWL.Class), (OWL.disjointWith, RDFS.domain, OWL.Class), (OWL.disjointWith, RDFS.range, OWL.Class), (OWL.equivalentClass, RDFS.domain, OWL.Class), (OWL.equivalentClass, RDFS.range, OWL.Class), (OWL.hasKey, RDFS.domain, OWL.Class), (OWL.intersectionOf, RDFS.domain, OWL.Class), (OWL.onClass, RDFS.range, OWL.Class), # (OWL.onDataRange, RDFS.range, OWL.DataRange), (OWL.onDatatype, RDFS.domain, OWL.DataRange), (OWL.onDatatype, RDFS.range, OWL.DataRange), (OWL.oneOf, RDFS.domain, OWL.Class), (OWL.someValuesFrom, RDFS.range, OWL.Class), (OWL.unionOf, RDFS.range, OWL.Class), # (OWL.withRestrictions, RDFS.domain, OWL.DataRange) ] # OWL RL axiomatic triples: combination of the RDFS triples plus the OWL specific ones OWLRL_Axiomatic_Triples = ( _OWL_axiomatic_triples_Classes + _OWL_axiomatic_triples_Properties ) # Note that this is not used anywhere. But I encoded it once and I did not want to remove it...:-) _OWL_axiomatic_triples_Facets = [ # langPattern (XSD.length, RDF.type, RDF.Property), (XSD.maxExclusive, RDF.type, RDF.Property), (XSD.maxInclusive, RDF.type, RDF.Property), (XSD.maxLength, RDF.type, RDF.Property), (XSD.minExclusive, RDF.type, RDF.Property), (XSD.minInclusive, RDF.type, RDF.Property), (XSD.minLength, RDF.type, RDF.Property), (XSD.pattern, RDF.type, RDF.Property), (XSD.length, RDFS.domain, RDFS.Resource), (XSD.maxExclusive, RDFS.domain, RDFS.Resource), (XSD.maxInclusive, RDFS.domain, RDFS.Resource), (XSD.maxLength, RDFS.domain, RDFS.Resource), (XSD.minExclusive, RDFS.domain, RDFS.Resource), (XSD.minInclusive, RDFS.domain, RDFS.Resource), (XSD.minLength, RDFS.domain, RDFS.Resource), (XSD.pattern, RDFS.domain, RDFS.Resource), (XSD.length, RDFS.domain, RDFS.Resource), (XSD.maxExclusive, RDFS.range, RDFS.Literal), (XSD.maxInclusive, RDFS.range, RDFS.Literal), (XSD.maxLength, RDFS.range, RDFS.Literal), (XSD.minExclusive, RDFS.range, RDFS.Literal), (XSD.minInclusive, RDFS.range, RDFS.Literal), (XSD.minLength, RDFS.range, RDFS.Literal), (XSD.pattern, RDFS.range, RDFS.Literal), ] # OWL D-entailment triples (additionally to the RDFS ones), ie, possible subclassing of various extra datatypes _OWL_D_Axiomatic_Triples_types = [(RDF.PlainLiteral, RDF.type, RDFS.Datatype)] # OWL_D_Axiomatic_Triples_subclasses = [ (XSD.string, RDFS.subClassOf, RDF.PlainLiteral), (XSD.normalizedString, RDFS.subClassOf, RDF.PlainLiteral), (XSD.token, RDFS.subClassOf, RDF.PlainLiteral), (XSD.Name, RDFS.subClassOf, RDF.PlainLiteral), (XSD.NCName, RDFS.subClassOf, RDF.PlainLiteral), (XSD.NMTOKEN, RDFS.subClassOf, RDF.PlainLiteral), ] # OWLRL_Datatypes_Disjointness = [ (XSD.anyURI, OWL.disjointWith, XSD.base64Binary), (XSD.anyURI, OWL.disjointWith, XSD.boolean), (XSD.anyURI, OWL.disjointWith, XSD.dateTime), (XSD.anyURI, OWL.disjointWith, XSD.decimal), (XSD.anyURI, OWL.disjointWith, XSD.double), (XSD.anyURI, OWL.disjointWith, XSD.float), (XSD.anyURI, OWL.disjointWith, XSD.hexBinary), (XSD.anyURI, OWL.disjointWith, XSD.string), (XSD.anyURI, OWL.disjointWith, RDF.PlainLiteral), (XSD.anyURI, OWL.disjointWith, RDF.XMLLiteral), (XSD.base64Binary, OWL.disjointWith, XSD.boolean), (XSD.base64Binary, OWL.disjointWith, XSD.dateTime), (XSD.base64Binary, OWL.disjointWith, XSD.decimal), (XSD.base64Binary, OWL.disjointWith, XSD.double), (XSD.base64Binary, OWL.disjointWith, XSD.float), (XSD.base64Binary, OWL.disjointWith, XSD.hexBinary), (XSD.base64Binary, OWL.disjointWith, XSD.string), (XSD.base64Binary, OWL.disjointWith, RDF.PlainLiteral), (XSD.base64Binary, OWL.disjointWith, RDF.XMLLiteral), (XSD.boolean, OWL.disjointWith, XSD.dateTime), (XSD.boolean, OWL.disjointWith, XSD.decimal), (XSD.boolean, OWL.disjointWith, XSD.double), (XSD.boolean, OWL.disjointWith, XSD.float), (XSD.boolean, OWL.disjointWith, XSD.hexBinary), (XSD.boolean, OWL.disjointWith, XSD.string), (XSD.boolean, OWL.disjointWith, RDF.PlainLiteral), (XSD.boolean, OWL.disjointWith, RDF.XMLLiteral), (XSD.dateTime, OWL.disjointWith, XSD.decimal), (XSD.dateTime, OWL.disjointWith, XSD.double), (XSD.dateTime, OWL.disjointWith, XSD.float), (XSD.dateTime, OWL.disjointWith, XSD.hexBinary), (XSD.dateTime, OWL.disjointWith, XSD.string), (XSD.dateTime, OWL.disjointWith, RDF.PlainLiteral), (XSD.dateTime, OWL.disjointWith, RDF.XMLLiteral), (XSD.decimal, OWL.disjointWith, XSD.double), (XSD.decimal, OWL.disjointWith, XSD.float), (XSD.decimal, OWL.disjointWith, XSD.hexBinary), (XSD.decimal, OWL.disjointWith, XSD.string), (XSD.decimal, OWL.disjointWith, RDF.PlainLiteral), (XSD.decimal, OWL.disjointWith, RDF.XMLLiteral), (XSD.double, OWL.disjointWith, XSD.float), (XSD.double, OWL.disjointWith, XSD.hexBinary), (XSD.double, OWL.disjointWith, XSD.string), (XSD.double, OWL.disjointWith, RDF.PlainLiteral), (XSD.double, OWL.disjointWith, RDF.XMLLiteral), (XSD.float, OWL.disjointWith, XSD.hexBinary), (XSD.float, OWL.disjointWith, XSD.string), (XSD.float, OWL.disjointWith, RDF.PlainLiteral), (XSD.float, OWL.disjointWith, RDF.XMLLiteral), (XSD.hexBinary, OWL.disjointWith, XSD.string), (XSD.hexBinary, OWL.disjointWith, RDF.PlainLiteral), (XSD.hexBinary, OWL.disjointWith, RDF.XMLLiteral), (XSD.string, OWL.disjointWith, RDF.XMLLiteral), ] # OWL RL D Axiomatic triples: combination of the RDFS ones, plus some extra statements on ranges and domains, plus # some OWL specific datatypes OWLRL_D_Axiomatic_Triples = ( RDFS_D_Axiomatic_Triples + _OWL_D_Axiomatic_Triples_types + OWL_D_Axiomatic_Triples_subclasses + OWLRL_Datatypes_Disjointness ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1737416745.6452832 owlrl-7.1.3/owlrl/Closure.py0000755000000000000000000003062114743560052012747 0ustar00# -*- coding: utf-8 -*- # """ The generic superclasses for various rule based semantics and the possible extensions. **Requires**: `RDFLib`_, 4.0.0 and higher. .. _RDFLib: https://github.com/RDFLib/rdflib **License**: This software is available for use under the `W3C Software License`_. .. _W3C Software License: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 **Organization**: `World Wide Web Consortium`_ .. _World Wide Web Consortium: http://www.w3.org **Author**: `Ivan Herman`_ .. _Ivan Herman: http://www.w3.org/People/Ivan/ """ __author__ = "Ivan Herman" __contact__ = "Ivan Herman, ivan@w3.org" __license__ = "W3C® SOFTWARE NOTICE AND LICENSE, http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231" from typing import Union import rdflib from rdflib.namespace import RDF from rdflib import BNode, Literal, Graph, Dataset from .Namespaces import ERRNS try: from rdflib.graph import ConjunctiveGraph except ImportError: ConjunctiveGraph = Dataset debugGlobal = False offlineGeneration = False ###################################################################################################### # noinspection PyMethodMayBeStatic,PyPep8Naming,PyPep8Naming class Core: """ Core of the semantics management, dealing with the RDFS and other Semantic triples. The only reason to have it in a separate class is for an easier maintainability. This is a common superclass only. In the present module, it is subclassed by a :class:`.RDFSClosure.RDFS_Semantics` class and a :class:`.OWLRL.OWLRL_Semantics` classes. There are some methods that are implemented in the subclasses only, ie, this class cannot be used by itself! :param graph: The RDF graph to be extended. :type graph: :class:`rdflib.graph.Graph` :param axioms: Whether axioms should be added or not. :type axioms: bool :param daxioms: Whether datatype axioms should be added or not. :type daxioms: bool :param rdfs: Whether RDFS inference is also done (used in subclassed only). :type rdfs: bool :param destination: The destination graph to which the results are written. If None, use the source graph. :type destination: :class:`rdflib.graph.Graph` :var IMaxNum: Maximal index of :code:`rdf:_i` occurrence in the graph. :type IMaxNum: int :var graph: The real graph. :type graph: :class:`rdflib.graph.Graph` :var axioms: Whether axioms should be added or not. :type axioms: bool :var daxioms: Whether datatype axioms should be added or not. :type daxioms: bool :var added_triples: Triples added to the graph, conceptually, during one processing cycle. :type added_triples: set of triples :var error_messages: Error messages (typically inconsistency messages in OWL RL) found during processing. These are added to the final graph at the very end as separate BNodes with error messages. :type error_messages: list of str :var rdfs: Whether RDFS inference is also done (used in subclassed only). :type rdfs: bool """ # noinspection PyUnusedLocal def __init__(self, graph: Graph, axioms, daxioms, rdfs: bool = False, destination: Union[None, Graph] = None): """ The parameter descriptions here are from the old documentation. @param graph: the RDF graph to be extended @type graph: Graph @param axioms: whether axioms should be added or not @type axioms: boolean @param daxioms: whether datatype axioms should be added or not @type daxioms: boolean @param rdfs: whether RDFS inference is also done (used in subclassed only) @type rdfs: boolean @param destination: the destination graph to which the results are written. If None, use the source graph. @type destination: Graph """ self._debug = debugGlobal # Calculate the maximum 'n' value for the '_i' type predicates (see Horst's paper) n = 1 maxnum = 0 cont = True while cont: cont = False predicate = RDF[("_%d" % n)] for (s, p, o) in graph.triples((None, predicate, None)): # there is at least one if we got here maxnum = n n += 1 cont = True self.IMaxNum = maxnum self.graph = graph if isinstance(self.graph, (Dataset, ConjunctiveGraph)): self.graph.default_union = True if destination is None: if isinstance(graph, (Dataset, ConjunctiveGraph)): self.destination = graph.default_context else: self.destination = graph else: if isinstance(destination, (str, rdflib.URIRef)): if isinstance(graph, (Dataset, ConjunctiveGraph)): self.destination = graph.get_context(destination) else: raise ValueError("URIRef destinations are only supported for Datasets and ConjunctiveGraphs") else: source_store = self.graph.store dest_store = destination.store if source_store is not dest_store: raise ValueError("The source and destination graphs share the same backing store") self.destination = destination self.axioms = axioms self.daxioms = daxioms self.rdfs = rdfs self.error_messages = [] self.empty_stored_triples() def add_error(self, message): """ Add an error message :param message: Error message. :type message: str """ if message not in self.error_messages: self.error_messages.append(message) def pre_process(self): """ Do some pre-processing step. This method before anything else in the closure. By default, this method is empty, subclasses can add content to it by overriding it. """ pass def post_process(self): """ Do some post-processing step. This method when all processing is done, but before handling possible errors (ie, the method can add its own error messages). By default, this method is empty, subclasses can add content to it by overriding it. """ pass def rules(self, t, cycle_num): """ The core processing cycles through every tuple in the graph and dispatches it to the various methods implementing a specific group of rules. By default, this method raises an exception; indeed, subclasses *must* add content to by overriding it. :param t: One triple on which to apply the rules. :type t: tuple :param cycle_num: Which cycle are we in, starting with 1. This value is forwarded to all local rules; it is also used locally to collect the bnodes in the graph. :type cycle_num: int """ raise Exception( "This method should not be called directly; subclasses should override it" ) def add_axioms(self): """ Add axioms. This is only a placeholder and raises an exception by default; subclasses *must* fill this with real content """ raise Exception( "This method should not be called directly; subclasses should override it" ) def add_d_axioms(self): """ Add d axioms. This is only a placeholder and raises an exception by default; subclasses I{must} fill this with real content """ raise Exception( "This method should not be called directly; subclasses should override it" ) def one_time_rules(self): """ This is only a placeholder; subclasses should fill this with real content. By default, it is just an empty call. This set of rules is invoked only once and not in a cycle. """ pass # noinspection PyAttributeOutsideInit def empty_stored_triples(self): """ Empty the internal store for triples. """ self.added_triples = set() def flush_stored_triples(self): """ Send the stored triples to the graph, and empty the container. """ for t in self.added_triples: self.destination.add(t) self.empty_stored_triples() def store_triple(self, t): """ In contrast to its name, this does not yet add anything to the graph itself, it just stores the tuple in an internal set (:code:`Core.added_triples`). (It is important for this to be a set: some of the rules in the various closures may generate the same tuples several times.) Before adding the tuple to the set, the method checks whether the tuple is in the final graph already (if yes, it is not added to the set). The set itself is emptied at the start of every processing cycle; the triples are then effectively added to the graph at the end of such a cycle. If the set is actually empty at that point, this means that the cycle has not added any new triple, and the full processing can stop. :param t: The triple to be added to the graph, unless it is already there :type t: tuple """ (s, p, o) = t if not isinstance(p, Literal) and not (t in self.destination or t in self.graph): if self._debug or offlineGeneration: print(t) self.added_triples.add(t) # noinspection PyAttributeOutsideInit def closure(self): """ Generate the closure the graph. This is the real 'core'. The processing rules store new triples via the separate method :func:`owlrl.Closure.Core.store_triple` which stores them in the :code:`added_triples` array. If that array is empty at the end of a cycle, it means that the whole process can be stopped. If required, the relevant axiomatic triples are added to the graph before processing in cycles. Similarly the exchange of literals against bnodes is also done in this step (and restored after all cycles are over). """ self.pre_process() # Handling the axiomatic triples. In general, this means adding all tuples in the list that # forwarded, and those include RDF or RDFS. In both cases the relevant parts of the container axioms should also # be added. if self.axioms: self.add_axioms() # Add the datatype axioms, if needed (note that this makes use of the literal proxies, the order of the call # is important! if self.daxioms: self.add_d_axioms() self.flush_stored_triples() # Get first the 'one-time rules', ie, those that do not need an extra round in cycles down the line self.one_time_rules() self.flush_stored_triples() # Go cyclically through all rules until no change happens new_cycle = True cycle_num = 0 while new_cycle: # yes, there was a change, let us go again cycle_num += 1 # DEBUG: print the cycle number out if self._debug: print("----- Cycle #%d" % cycle_num) # go through all rules, and collect the replies (to see whether any change has been done) # the new triples to be added are collected separately not to interfere with # the current graph yet self.empty_stored_triples() # Execute all the rules; these might fill up the added triples array for t in self.graph.triples((None, None, None)): self.rules(t, cycle_num) # Add the tuples to the graph (if necessary, that is). If any new triple has been generated, a new cycle # will be necessary... new_cycle = len(self.added_triples) > 0 for t in self.added_triples: self.destination.add(t) self.post_process() self.flush_stored_triples() # Add possible error messages if self.error_messages: # I am not sure this is the right vocabulary to use for this purpose, but I haven't found anything! # I could, of course, come up with my own, but I am not sure that would be kosher... self.destination.bind("err", "http://www.daml.org/2002/03/agents/agent-ont#") for m in self.error_messages: message = BNode() self.destination.add((message, RDF.type, ERRNS.ErrorMessage)) self.destination.add((message, ERRNS.error, Literal(m))) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1737416745.6453426 owlrl-7.1.3/owlrl/CombinedClosure.py0000755000000000000000000001625614743560052014420 0ustar00# -*- coding: utf-8 -*- # """ The combined closure: performing *both* the OWL 2 RL and RDFS closures. The two are very close but there are some rules in RDFS that are not in OWL 2 RL (eg, the axiomatic triples concerning the container membership properties). Using this closure class the OWL 2 RL implementation becomes a full extension of RDFS. **Requires**: `RDFLib`_, 4.0.0 and higher. .. _RDFLib: https://github.com/RDFLib/rdflib **License**: This software is available for use under the `W3C Software License`_. .. _W3C Software License: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 **Organization**: `World Wide Web Consortium`_ .. _World Wide Web Consortium: http://www.w3.org **Author**: `Ivan Herman`_ .. _Ivan Herman: http://www.w3.org/People/Ivan/ """ __author__ = "Ivan Herman" __contact__ = "Ivan Herman, ivan@w3.org" __license__ = "W3C® SOFTWARE NOTICE AND LICENSE, http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231" from typing import Union from rdflib import Graph from rdflib.namespace import OWL, RDF, RDFS from owlrl.RDFSClosure import RDFS_Semantics from owlrl.OWLRL import OWLRL_Semantics ###################################################################################################### # noinspection PyPep8Naming class RDFS_OWLRL_Semantics(RDFS_Semantics, OWLRL_Semantics): """ Common subclass of the RDFS and OWL 2 RL semantic classes. All methods simply call back to the functions in the superclasses. This may lead to some unnecessary duplication of terms and rules, but it is not so bad. Also, the additional identification defined for OWL Full, ie, Resource being the same as Thing and OWL and RDFS classes being identical are added to the triple store. Note that this class is also a possible user extension point: subclasses can be created that extend the standard functionality by extending this class. This class *always*} performs RDFS inferences. Subclasses have to set the :code:`self.rdfs` flag explicitly to the requested value if that is to be controlled. :param graph: The RDF graph to be extended. :type graph: :class:`rdflib.Graph` :param axioms: Whether (non-datatype) axiomatic triples should be added or not. :type axioms: bool :param daxioms: Whether datatype axiomatic triples should be added or not. :type daxioms: bool :param rdfs: Placeholder flag (used in subclassed only, it is always defaulted to True in this class) :type rdfs: bool :var full_binding_triples: Additional axiom type triples that are added to the combined semantics; these 'bind' the RDFS and the OWL worlds together. :var rdfs: (bool) Whether RDFS inference is to be performed or not. In this class instance the value is *always* :code:`True`, subclasses may explicitly change it at initialization time. :type rdfs: bool """ full_binding_triples = [ (OWL.Thing, OWL.equivalentClass, RDFS.Resource), (RDFS.Class, OWL.equivalentClass, OWL.Class), (OWL.DataRange, OWL.equivalentClass, RDFS.Datatype), ] def __init__(self, graph: Graph, axioms, daxioms, rdfs: bool = True, destination: Union[None, Graph] = None): """ @param graph: the RDF graph to be extended @type graph: rdflib.Graph @param axioms: whether (non-datatype) axiomatic triples should be added or not @type axioms: bool @param daxioms: whether datatype axiomatic triples should be added or not @type daxioms: bool @param rdfs: placeholder flag (used in subclassed only, it is always defaulted to True in this class) @type rdfs: boolean @param destination: the destination graph to which the results are written. If None, use the source graph. @type destination: rdflib.Graph """ OWLRL_Semantics.__init__(self, graph, axioms, daxioms, rdfs=rdfs, destination=destination) RDFS_Semantics.__init__(self, graph, axioms, daxioms, rdfs=rdfs, destination=destination) self.rdfs = True # noinspection PyMethodMayBeStatic @staticmethod def add_new_datatype( uri, conversion_function, datatype_list, subsumption_dict=None, subsumption_key=None, subsumption_list=None, ): """ If an extension wants to add new datatypes, this method should be invoked at initialization time. :param uri: URI for the new datatypes, like owl_ns["Rational"]. :param conversion_function: A function converting the lexical representation of the datatype to a Python value, possibly raising an exception in case of unsuitable lexical form. :param datatype_list: List of datatypes already in use that has to be checked. :type datatype_list: list :param subsumption_dict: Dictionary of subsumption hierarchies (indexed by the datatype URI-s). :type subsumption_dict: dict :param subsumption_key: Key in the dictionary, if None, the uri parameter is used. :type subsumption_key: str :param subsumption_list: List of subsumptions associated to a subsumption key (ie, all datatypes that are superclasses of the new datatype). :type subsumption_list: list """ from .DatatypeHandling import AltXSDToPYTHON, use_Alt_lexical_conversions if datatype_list: datatype_list.append(uri) if subsumption_dict and subsumption_list: if subsumption_key: subsumption_dict[subsumption_key] = subsumption_list else: subsumption_dict[uri] = subsumption_list AltXSDToPYTHON[uri] = conversion_function use_Alt_lexical_conversions() def post_process(self): """ Do some post-processing step. This method when all processing is done, but before handling possible errors (I.e., the method can add its own error messages). By default, this method is empty, subclasses can add content to it by overriding it. """ OWLRL_Semantics.post_process(self) def rules(self, t, cycle_num): """ :param t: A triple (in the form of a tuple). :type t: tuple :param cycle_num: Which cycle are we in, starting with 1. This value is forwarded to all local rules; it is also used locally to collect the bnodes in the graph. :type cycle_num: int """ OWLRL_Semantics.rules(self, t, cycle_num) if self.rdfs: RDFS_Semantics.rules(self, t, cycle_num) def add_axioms(self): if self.rdfs: RDFS_Semantics.add_axioms(self) OWLRL_Semantics.add_axioms(self) def add_d_axioms(self): if self.rdfs: RDFS_Semantics.add_d_axioms(self) OWLRL_Semantics.add_d_axioms(self) def one_time_rules(self): """Adds some extra axioms and calls for the d_axiom part of the OWL Semantics.""" for t in self.full_binding_triples: self.store_triple(t) # Note that the RL one time rules include the management of datatype which is a true superset # of the rules in RDFS. It is therefore unnecessary to add those even self.rdfs is True. OWLRL_Semantics.one_time_rules(self) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1737416745.6453927 owlrl-7.1.3/owlrl/DatatypeHandling.py0000755000000000000000000005703314743560052014561 0ustar00# -*- coding: utf-8 -*- # """ Most of the XSD datatypes are handled directly by RDFLib. However, in some cases, that is not good enough. There are two major reasons for this: #. Some datatypes are missing from RDFLib and required by OWL 2 RL and/or RDFS. #. In other cases, though the datatype is present, RDFLib is fairly lax in checking the lexical value of those datatypes. Typical case is boolean. Some of these deficiencies are handled by this module. All the functions convert the lexical value into a python datatype (or return the original string if this is not possible) which will be used, e.g., for comparisons (equalities). If the lexical value constraints are not met, exceptions are raised. **Requires**: `RDFLib`_, 4.0.0 and higher. .. _RDFLib: https://github.com/RDFLib/rdflib **License**: This software is available for use under the `W3C Software License`_. .. _W3C Software License: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 **Organization**: `World Wide Web Consortium`_ .. _World Wide Web Consortium: http://www.w3.org **Author**: `Ivan Herman`_ .. _Ivan Herman: http://www.w3.org/People/Ivan/ """ __author__ = "Ivan Herman" __contact__ = "Ivan Herman, ivan@w3.org" __license__ = "W3C® SOFTWARE NOTICE AND LICENSE, http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231" # noinspection PyPep8Naming # from owlrl.RDFS import RDFNS as ns_rdf from rdflib.namespace import RDF, XSD from rdflib.term import XSDToPython, Literal, _toPythonMapping import datetime, time, re from decimal import Decimal # noinspection PyMissingConstructor,PyPep8Naming class _namelessTZ(datetime.tzinfo): """ (Nameless) timezone object. The python datetime object requires timezones as a specific object added to the conversion, rather than the explicit hour and minute difference used by XSD. This class is used to wrap around the hour/minute values. :param hours: Hour offset. :param minutes: Minute offset """ def __init__(self, hours, minutes): """ @param hours: hour offset @param minutes: minute offset """ self.__offset = datetime.timedelta(hours=hours, minutes=minutes) self.__name = "nameless" def utcoffset(self, dt): return self.__offset def tzname(self, dt): return self.__name def dst(self, dt): return datetime.timedelta(0) # noinspection PyPep8Naming def _returnTimeZone(incoming_v): """Almost all time/date related methods require the extraction of optional time zone information. @param incoming_v: the time/date string @return (v,timezone) tuple; 'v' is the input string with the timezone info cut off, 'timezone' is a L{_namelessTZ} instance or None """ if incoming_v[-1] == "Z": v = incoming_v[:-1] tzone = _namelessTZ(0, 0) else: pattern = r".*(\+|-)([0-9][0-9]):([0-9][0-9])" match = re.match(pattern, incoming_v) if match is None: v = incoming_v tzone = None else: hours = int(match.groups()[1]) if match.groups()[0] == "-": hours = -hours - 1 minutes = int(match.groups()[2]) v = incoming_v[:-6] tzone = _namelessTZ(hours, minutes) return v, tzone # Booleans ################################################## # noinspection PyPep8Naming def _strToBool(v): """The built-in conversion to boolean is way too lax. The xsd specification requires that only true, false, 1 or 0 should be used... @param v: the literal string defined as boolean @return corresponding boolean value @raise ValueError: invalid boolean values """ if v.lower() == "true" or v.lower() == "1": return True elif v.lower() == "false" or v.lower() == "0": return False else: raise ValueError("Invalid boolean literal value %s" % v) # Decimals ################################################## # noinspection PyPep8Naming def _strToDecimal(v): """The built in datatype handling for RDFLib maps a decimal number to float, but the python version 2.4 and upwards also has a Decimal number. Better make use of that to use very high numbers. However, there is also a big difference between Python's decimal and XSD's decimal, because the latter does not allow for an exponential normal form (why???). This must be filtered out. @param v: the literal string defined as decimal @return Decimal @raise ValueError: invalid decimal value """ # check whether the lexical form of 'v' is o.k. if v.find("E") != -1 or v.find("e") != -1: # this is an invalid lexical form, though would be accepted by Python raise ValueError("Invalid decimal literal value %s" % v) else: return Decimal(v) # ANY URIS ################################################## # set of characters allowed in a hexadecimal number _hexc = ["A", "B", "C", "D", "E", "F", "a", "b", "c", "d", "e", "f"] # set of numerals _numb = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] # noinspection PyPep8Naming def _strToAnyURI(v): """Rudimentary test for the AnyURI value. If it is a relative URI, then some tests are done to filter out mistakes. I am not sure this is the full implementation of the RFC, though, may have to be checked at some point later. @param v: the literal string defined as a URI @return the incoming value @raise ValueError: invalid URI value """ import urllib.parse if len(v) == 0: return v if urllib.parse.urlsplit(v)[0] != "": # this means that there is a proper scheme, the URI should be kosher return v else: # this is meant to be a relative URI. # If I am correct, that cannot begin with one or more "?" or ":" characters # all others are o.k. # if it begins with a % then it should be followed by two hexa characters, # otherwise it is also a bug if v[0] == "%": if ( len(v) >= 3 and (v[1] in _hexc or v[1] in _numb) and (v[2] in _hexc or v[2] in _numb) ): return v else: raise ValueError("Invalid IRI %s" % v) elif v[0] == "?" or v[0] == ":": raise ValueError("Invalid IRI %s" % v) else: return v # Base64Binary ################################################## # noinspection PyPep8Naming def _strToBase64Binary(v): """Rudimentary test for the base64Binary value. The problem is that the built-in b64 module functions ignore the fact that only a certain family of characters are allowed to appear in the lexical value, so this is checked first. @param v: the literal string defined as a base64encoded string @return the decoded (binary) content @raise ValueError: invalid base 64 binary value """ import base64 if v.replace("=", "x").replace("+", "y").replace("/", "z").isalnum(): try: return base64.standard_b64decode(v) except: raise ValueError("Invalid Base64Binary %s" % v) else: raise ValueError("Invalid Base64Binary %s" % v) # Numerical types ################################################## # limits for unsigned bytes _limits_unsignedByte = [-1, 256] # limits for bytes _limits_byte = [-129, 128] # limits for unsigned int _limits_unsignedInt = [-1, 4294967296] # limits for int _limits_int = [-2147483649, 2147483648] # limits for unsigned short _limits_unsignedShort = [-1, 65536] # limits for short _limits_short = [-32769, 32768] # limits for unsigned long _limits_unsignedLong = [-1, 18446744073709551616] # limits for long _limits_long = [-9223372036854775809, 9223372036854775808] # limits for positive integer _limits_positiveInteger = [0, None] # limits for non positive integer _limits_nonPositiveInteger = [None, 1] # limits for non negative integer _limits_nonNegativeInteger = [-1, None] # limits for negative integer _limits_negativeInteger = [None, 0] # noinspection PyPep8Naming,PyBroadException def _strToBoundNumeral(v, interval, conversion): """Test (and convert) a generic numerical type, with a check against a lower and upper limit. @param v: the literal string to be converted @param interval: lower and upper bounds (non inclusive). If the value is None, no comparison should be done @param conversion: conversion function, ie, int, long, etc @raise ValueError: invalid value """ try: i = conversion(v) if (interval[0] is None or interval[0] < i) and ( interval[1] is None or i < interval[1] ): return i except: pass raise ValueError("Invalid numerical value %s" % v) # Double and float ################################################## # noinspection PyPep8Naming def _strToDouble(v): """Test and convert a double value into a Decimal or float. Raises an exception if the number is outside the permitted range, ie, 1.0E+310 and 1.0E-330. To be on the safe side (python does not have double!) Decimals are used if possible. Upper and lower values, as required by xsd, are checked (and these fixed values are the reasons why Decimal is used!) @param v: the literal string defined as a double @return Decimal @raise ValueError: invalid value """ try: value = Decimal(v) upper = Decimal("1.0E+310") lower = Decimal("1.0E-330") if lower < abs(value) < upper: # bingo return value else: raise ValueError("Invalid double %s" % v) except: # there was a problem in creating a decimal... raise ValueError("Invalid double %s" % v) # noinspection PyPep8Naming def _strToFloat(v): """Test and convert a float value into Decimal or (python) float. Raises an exception if the number is outside the permitted range, ie, 1.0E+40 and 1.0E-50. (And these fixed values are the reasons why Decimal is used!) @param v: the literal string defined as a float @return Decimal if the local python version is >= 2.4, float otherwise @raise ValueError: invalid value """ try: value = Decimal(v) upper = Decimal("1.0E+40") lower = Decimal("1.0E-50") if lower < abs(value) < upper: # bingo return value else: raise ValueError("Invalid float %s" % v) except: # there was a problem in creating a decimal... raise ValueError("Invalid float %s" % v) # hexa ################################################## # noinspection PyPep8Naming def _strToHexBinary(v): """Test (and convert) hexa integer values. The number of characters should be even. @param v: the literal string defined as a hexa number @return long value @raise ValueError: invalid value """ # first of all, the number of characters must be even according to the xsd spec: length = len(v) if (length / 2) * 2 != length: raise ValueError("Invalid hex binary number %s" % v) return int(v, 16) # Datetime, date timestamp, etc ################################ # noinspection PyPep8Naming def _strToDateTimeAndStamp(incoming_v, timezone_required=False): """Test (and convert) datetime and date timestamp values. @param incoming_v: the literal string defined as the date and time @param timezone_required: whether the timezone is required (ie, for date timestamp) or not @return datetime @rtype: datetime.datetime @raise ValueError: invalid datetime or date timestamp """ # First, handle the timezone portion, if there is any (v, tzone) = _returnTimeZone(incoming_v) # Check on the timezone. For time date stamp object it is required if timezone_required and tzone is None: raise ValueError("Invalid datetime %s" % incoming_v) # The microseconds should be handled here... final_v = v milliseconds = 0 milpattern = r"(.*)(\.)([0-9]*)" match = re.match(milpattern, v) if match is not None: # we have a millisecond portion... try: final_v = match.groups()[0] milliseconds = int(match.groups()[2]) except: raise ValueError("Invalid datetime %s" % incoming_v) # # By now, the pattern should be clear # This may raise an exception... try: tstr = time.strptime(final_v, "%Y-%m-%dT%H:%M:%S") if tzone is not None: return datetime.datetime( tstr.tm_year, tstr.tm_mon, tstr.tm_mday, tstr.tm_hour, tstr.tm_min, tstr.tm_sec, milliseconds, tzone, ) else: return datetime.datetime( tstr.tm_year, tstr.tm_mon, tstr.tm_mday, tstr.tm_hour, tstr.tm_min, tstr.tm_sec, milliseconds, ) except: raise ValueError("Invalid datetime %s" % incoming_v) # noinspection PyPep8Naming def _strToTime(incoming_v): """Test (and convert) time values. @param incoming_v: the literal string defined as time value @return time @rtype datetime.time @raise ValueError: invalid datetime or date timestamp """ # First, handle the timezone portion, if there is any (v, tzone) = _returnTimeZone(incoming_v) # The microseconds should be handled here... final_v = v milliseconds = 0 milpattern = r"(.*)(\.)([0-9]*)" match = re.match(milpattern, v) if match is not None: # we have a millisecond portion... try: final_v = match.groups()[0] milliseconds = int(match.groups()[2]) except: raise ValueError("Invalid datetime %s" % incoming_v) # # By now, the pattern should be clear # This may raise an exception... try: tstr = time.strptime(final_v, "%H:%M:%S") if tzone is not None: return datetime.time( tstr.tm_hour, tstr.tm_min, tstr.tm_sec, milliseconds, tzone ) else: return datetime.time(tstr.tm_hour, tstr.tm_min, tstr.tm_sec, milliseconds) except: raise ValueError("Invalid time %s" % incoming_v) # noinspection PyPep8Naming def _strToDate(incoming_v): """Test (and convert) date values. @param incoming_v: the literal string defined as date (in iso format) @return date @return datetime.date @raise ValueError: invalid datetime or date timestamp """ # First, handle the timezone portion, if there is any (final_v, tzone) = _returnTimeZone(incoming_v) # This may raise an exception... try: tstr = time.strptime(final_v, "%Y-%m-%d") return datetime.date(tstr.tm_year, tstr.tm_mon, tstr.tm_mday) except: raise ValueError("Invalid date %s" % incoming_v) # The 'g' series for dates ############################ # The 'g' datatypes (eg, gYear) cannot be directly represented as a python datatype # the series of methods below simply check whether the incoming string is o.k., but the # returned value is the same as the original # noinspection PyPep8Naming def _strTogYearMonth(v): """Test gYearMonth value @param v: the literal string @return v @raise ValueError: invalid value """ try: time.strptime(v + "-01", "%Y-%m-%d") return v except: raise ValueError("Invalid gYearMonth %s" % v) # noinspection PyPep8Naming def _strTogYear(v): """Test gYear value @param v: the literal string @return v @raise ValueError: invalid value """ try: time.strptime(v + "-01-01", "%Y-%m-%d") return v except: raise ValueError("Invalid gYear %s" % v) # noinspection PyPep8Naming def _strTogMonthDay(v): """Test gYearMonth value @param v: the literal string @return v @raise ValueError: invalid value """ try: time.strptime("2008-" + v, "%Y-%m-%d") return v except: raise ValueError("Invalid gMonthDay %s" % v) # noinspection PyPep8Naming def _strTogDay(v): """Test gYearMonth value @param v: the literal string @return v @raise ValueError: invalid value """ try: time.strptime("2001-01-" + v, "%Y-%m-%d") return v except: raise ValueError("Invalid gDay %s" % v) # noinspection PyPep8Naming def _strTogMonth(v): """Test gYearMonth value @param v: the literal string @return v @raise ValueError: invalid value """ try: time.strptime("2001-" + v + "-01", "%Y-%m-%d") return v except: raise ValueError("Invalid gMonth %s" % v) # XML Literal ######################################### # noinspection PyPep8Naming def _strToXMLLiteral(v): """Test (and convert) XML Literal values. @param v: the literal string defined as an xml literal @return the canonical version of the same xml text @raise ValueError: incorrect xml string """ import xml.dom.minidom try: dom = xml.dom.minidom.parseString(v) return dom.toxml() except: raise ValueError("Invalid XML Literal %s" % v) # language, NMTOKEN, NAME, etc ######################### # regular expression for a 'language' datatype _re_language = r"[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*" # regexp for NMTOKEN. It must be used with a re.U flag (the '(?U' regexp form did not work. It may depend on the # locale...) _re_NMTOKEN = r"[\w:_.\-]+" # characters not permitted at a starting position for Name (otherwise Name is like NMTOKEN _re_Name_ex = [".", "-"] + _numb # regexp for NCName. It must be used with a re.U flag (the '(?U' regexp form did not work. It may depend on the # locale...) _re_NCName = r"[\w_.\-]+" # characters not permitted at a starting position for NCName _re_NCName_ex = [".", "-"] + _numb # noinspection PyDefaultArgument,PyPep8Naming,PyPep8Naming def _strToVal_Regexp(v, regexp, flag=0, excludeStart=[]): """Test (and convert) a generic string type, with a check against a regular expression. @param v: the literal string to be converted @param regexp: the regular expression to check against @param flag: flags to be used in the regular expression @param excludeStart: array of characters disallowed in the first position @return original string @raise ValueError: invalid value """ match = re.match(regexp, v, flag) if match is None or match.end() != len(v): raise ValueError("Invalid literal %s" % v) else: if len(excludeStart) > 0 and v[0] in excludeStart: raise ValueError("Invalid literal %s" % v) return v # Disallowed characters in a token or a normalized string, as a regexp _re_token = "[^\n\t\r]+" # noinspection PyPep8Naming def _strToToken(v): """Test (and convert) a string to a token. @param v: the literal string to be converted @return original string @raise ValueError: invalid value """ if len(v) == 0: return v # filter out the case when there are new lines and similar (if there is a problem, an exception is raised) _strToVal_Regexp(v, _re_token) v1 = " ".join(v.strip().split()) # normalize the string, and see if the result is the same: if len(v1) == len(v): # no characters lost, ie, no unnecessary spaces return v else: raise ValueError("Invalid literal %s" % v) # plain literal ######################################## # noinspection PyPep8Naming def _strToPlainLiteral(v): """Test (and convert) a plain literal @param v: the literal to be converted @return a new RDFLib Literal with language tag @raise ValueError: invalid value """ reg = "(.*)@([^@]*)" # a plain literal must match this regexp! match = re.match(reg, v) if match is None: raise ValueError("Invalid plain literal %s" % v) else: lit = match.groups()[0] if len(match.groups()) == 1 or match.groups()[1] == "": # no language tag return Literal(lit) else: lang = match.groups()[1] # check if this is a correct language tag. Note that can raise an exception! try: lang = _strToVal_Regexp(lang, _re_language) return Literal(lit, lang=lang.lower()) except: raise ValueError("Invalid plain literal %s" % v) ##################################################################################### # Replacement of RDFLib's conversion function. Each entry assigns a function to an XSD datatype, attempting to convert # a string to a Python datatype (or raise an exception if some problem is found) AltXSDToPYTHON = { XSD.language: lambda v: _strToVal_Regexp(v, _re_language), XSD.NMTOKEN: lambda v: _strToVal_Regexp(v, _re_NMTOKEN, re.U), XSD.Name: lambda v: _strToVal_Regexp(v, _re_NMTOKEN, re.U, _re_Name_ex), XSD.NCName: lambda v: _strToVal_Regexp(v, _re_NCName, re.U, _re_NCName_ex), XSD.token: _strToToken, RDF.PlainLiteral: _strToPlainLiteral, XSD.boolean: _strToBool, XSD.decimal: _strToDecimal, XSD.anyURI: _strToAnyURI, XSD.base64Binary: _strToBase64Binary, XSD.double: _strToDouble, XSD.float: _strToFloat, XSD.byte: lambda v: _strToBoundNumeral(v, _limits_byte, int), XSD.int: lambda v: _strToBoundNumeral(v, _limits_int, int), XSD.long: lambda v: _strToBoundNumeral(v, _limits_long, int), XSD.positiveInteger: lambda v: _strToBoundNumeral(v, _limits_positiveInteger, int), XSD.nonPositiveInteger: lambda v: _strToBoundNumeral( v, _limits_nonPositiveInteger, int ), XSD.negativeInteger: lambda v: _strToBoundNumeral(v, _limits_negativeInteger, int), XSD.nonNegativeInteger: lambda v: _strToBoundNumeral( v, _limits_nonNegativeInteger, int ), XSD.short: lambda v: _strToBoundNumeral(v, _limits_short, int), XSD.unsignedByte: lambda v: _strToBoundNumeral(v, _limits_unsignedByte, int), XSD.unsignedShort: lambda v: _strToBoundNumeral(v, _limits_unsignedShort, int), XSD.unsignedInt: lambda v: _strToBoundNumeral(v, _limits_unsignedInt, int), XSD.unsignedLong: lambda v: _strToBoundNumeral(v, _limits_unsignedLong, int), XSD.hexBinary: _strToHexBinary, XSD.dateTime: lambda v: _strToDateTimeAndStamp(v, False), XSD.dateTimeStamp: lambda v: _strToDateTimeAndStamp(v, True), RDF.XMLLiteral: _strToXMLLiteral, XSD.integer: int, XSD.string: lambda v: v, RDF.HTML: lambda v: v, XSD.normalizedString: lambda v: _strToVal_Regexp(v, _re_token), # These are RDFS specific... XSD.time: _strToTime, XSD.date: _strToDate, XSD.gYearMonth: _strTogYearMonth, XSD.gYear: _strTogYear, XSD.gMonthDay: _strTogMonthDay, XSD.gDay: _strTogDay, XSD.gMonth: _strTogMonth, } def use_Alt_lexical_conversions(): """ Registering the datatypes item for RDFLib, ie, bind the dictionary values. The 'bind' method of RDFLib adds extra datatypes to the registered ones in RDFLib, though the table used here (I.e., :code:`AltXSDToPYTHON`) actually overrides all of the default conversion routines. The method also add a Decimal entry to the :code:`PythonToXSD` list of RDFLib. """ _toPythonMapping.update(AltXSDToPYTHON) def use_RDFLib_lexical_conversions(): """ Restore the original (ie, RDFLib) set of lexical conversion routines. """ _toPythonMapping.update(XSDToPython) ####################################################################################### # This module can pretty much tested individually... if __name__ == "__main__": import sys dtype = sys.argv[1] string = sys.argv[2] datatype = XSD[dtype] result = AltXSDToPYTHON[datatype](string) print(type(result)) print(result) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1737416745.645551 owlrl-7.1.3/owlrl/Namespaces.py0000644000000000000000000000020314743560052013400 0ustar00from rdflib import Namespace ERRNS = Namespace("http://www.daml.org/2002/03/agents/agent-ont#") T = Namespace("http://test.org/") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1737416745.6456444 owlrl-7.1.3/owlrl/OWLRL.py0000755000000000000000000012772014743560052012241 0ustar00# -*- coding: utf-8 -*- # """ This module is a **brute force** implementation of the OWL 2 RL profile. RDFLib works with 'generalized' RDF, meaning that triples with a BNode predicate are *allowed*. This is good because, e.g., some of the triples generated for RDF from an OWL 2 functional syntax might look like :code:`[ owl:inverseOf p ]`, and the RL rules would then operate on such generalized triple. However, as a last, post processing steps, these triples are removed from the graph before serialization to produce 'standard' RDF (which is o.k. for RL, too, because the consequent triples are all right, generalized triples might have had a role in the deduction steps only). **Requires**: `RDFLib`_, 4.0.0 and higher. .. _RDFLib: https://github.com/RDFLib/rdflib **License**: This software is available for use under the `W3C Software License`_. .. _W3C Software License: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 **Organization**: `World Wide Web Consortium`_ .. _World Wide Web Consortium: http://www.w3.org **Author**: `Ivan Herman`_ .. _Ivan Herman: http://www.w3.org/People/Ivan/ """ __author__ = "Ivan Herman" __contact__ = "Ivan Herman, ivan@w3.org" __license__ = "W3C® SOFTWARE NOTICE AND LICENSE, http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231" from collections import defaultdict from typing import Union import rdflib from rdflib import BNode, Graph from rdflib.namespace import OWL, RDF, RDFS from owlrl.Closure import Core from owlrl.AxiomaticTriples import OWLRL_Axiomatic_Triples, OWLRL_D_Axiomatic_Triples from owlrl.AxiomaticTriples import OWLRL_Datatypes_Disjointness OWLRL_Annotation_properties = [ RDFS.label, RDFS.comment, RDFS.seeAlso, RDFS.isDefinedBy, OWL.deprecated, OWL.versionInfo, OWL.priorVersion, OWL.backwardCompatibleWith, OWL.incompatibleWith, ] from .XsdDatatypes import OWL_RL_Datatypes, OWL_Datatype_Subsumptions from .DatatypeHandling import AltXSDToPYTHON identity = lambda v: v ####################################################################################################################### # OWL-R Semantics class # # # As an editing help: each rule is prefixed by RULE XXXX where XXXX is the acronym given in the profile document. # This helps in referring back to the spec... # noinspection PyPep8Naming, PyPep8Naming, PyBroadException class OWLRL_Semantics(Core): """ OWL 2 RL Semantics class, i.e., implementation of the OWL 2 RL closure graph. .. note:: Note that the module does *not* implement the so called Datatype entailment rules, simply because the underlying RDFLib does not implement the datatypes (i.e., RDFLib will not make the literal "1.00" and "1.00000" identical, although even with all the ambiguities on datatypes, this *should* be made equal...). Also, the so-called extensional entailment rules (Section 7.3.1 in the RDF Semantics document) have not been implemented either. The comments and references to the various rule follow the names as used in the `OWL 2 RL document`_. .. _OWL 2 RL document: http://www.w3.org/TR/owl2-profiles/#Reasoning_in_OWL_2_RL_and_RDF_Graphs_using_Rules :param graph: The RDF graph to be extended. :type graph: :class:`rdflib.Graph` :param axioms: Whether (non-datatype) axiomatic triples should be added or not. :type axioms: bool :param daxioms: Whether datatype axiomatic triples should be added or not. :type daxioms: bool :param rdfs: Whether RDFS inference is also done (used in subclassed only). :type rdfs: bool """ def __init__(self, graph: Graph, axioms, daxioms, rdfs: bool = False, destination: Union[None, Graph] = None): """ @param graph: the RDF graph to be extended @type graph: rdflib.Graph @param axioms: whether (non-datatype) axiomatic triples should be added or not @type axioms: bool @param daxioms: whether datatype axiomatic triples should be added or not @type daxioms: bool @param rdfs: whether RDFS inference is also done (used in subclassed only) @type rdfs: boolean @param destination: the destination graph to which the results are written. If None, use the source graph. @type destination: rdflib.Graph """ Core.__init__(self, graph, axioms, daxioms, rdfs=rdfs, destination=destination) self.bnodes = [] def _list(self, l): """ Shorthand to get a list of values (ie, from an rdf:List structure) starting at a head @param l: RDFLib resource, should be the head of an rdf:List @return: array of resources """ return [ch for ch in self.graph.items(l)] def post_process(self): """ Remove triples with Bnode predicates. The Bnodes in the graph are collected in the first cycle run. """ to_be_removed = [] for b in self.bnodes: for t in self.graph.triples((None, b, None)): if t not in to_be_removed: to_be_removed.append(t) for t in to_be_removed: self.destination.remove(t) self.graph.remove(t) def add_axioms(self): """ Add axioms """ for t in OWLRL_Axiomatic_Triples: self.destination.add(t) def add_d_axioms(self): """ Add the datatype axioms """ for t in OWLRL_D_Axiomatic_Triples: self.destination.add(t) def restriction_typing_check(self, v, t): """ Helping method to check whether a type statement is in line with a possible restriction. This method is invoked by rule "cls-avf" before setting a type on an allValuesFrom restriction. The method is a placeholder at this level. It is typically implemented by subclasses for extra checks, e.g., for datatype facet checks. :param v: The resource that is to be 'typed'. :param t: The targeted type (ie, Class). :return: Boolean. :rtype: bool """ return True def _one_time_rules_datatypes(self): """ Some of the rules in the rule set are axiomatic in nature, meaning that they really have to be added only once, there is no reason to add these in a cycle. These are performed by this method that is invoked only once at the beginning of the process. These are: cls-thing, cls-nothing1, prp-ap, dt-types1, dt-types2, dt-eq, dt-diff. .. note:: Note, however, that the dt-* are executed only partially, limited by the possibilities offered by RDFLib. These may not cover all the edge cases of OWL RL. Especially, dt-not-type has not (yet?) been implemented (I wonder whether RDFLib should not raise exception for those anyway... """ # noinspection PyShadowingNames def _add_to_explicit(s, o): explicit[s].add(o) # noinspection PyShadowingNames def _append_to_explicit(s, o): explicit[s].add(o) # noinspection PyShadowingNames def _handle_subsumptions(r, dt): if dt in OWL_Datatype_Subsumptions: for new_dt in OWL_Datatype_Subsumptions[dt]: self.store_triple((r, RDF.type, new_dt)) self.store_triple((new_dt, RDF.type, RDFS.Datatype)) used_datatypes.add(new_dt) # explicit object->datatype relationships: those that came from an object being typed as a datatype # or a sameAs. The values are arrays of datatypes to which the resource belong explicit = defaultdict(set) # For processing later: # implicit object->datatype relationships: these come from real # literals which are present in the graph implicit = { o: o.datatype for s, p, o in self.graph.triples((None, None, None)) if isinstance(o, rdflib.Literal) and o.datatype in OWL_RL_Datatypes } # datatypes in use by the graph (directly or indirectly). This will be used at the end to add the # necessary disjointness statements (but not more) used_datatypes = set(implicit.values()) # RULE dt-type2: for all explicit literals the corresponding bnode should get the right type # definition. The 'implicit' dictionary is also filled on the fly # RULE dt-not-type: see whether an explicit literal is valid in terms of the defined datatype for lt in implicit: # note that all non-RL datatypes are ignored # add the explicit typing triple self.store_triple((lt, RDF.type, lt.datatype)) # for dt-not-type # This is a dirty trick: rdflib's Literal includes a method that raises an exception if the # lexical value cannot be mapped on the value space. converter = AltXSDToPYTHON.get(lt.datatype, identity) try: converter(str(lt)) except ValueError: self.add_error( "Lexical value of the literal '%s' does not match" " its datatype (%s)" % (lt, lt.datatype) ) # RULE dt-diff # RULE dt-eq # Compare literals whether they are different or not. This rules # are skipped on purpose at the moment. # Other datatype definitions can come from explicitly defining some nodes as datatypes (though rarely used, # it is perfectly possible... # there may be explicit relationships set in the graph, too! for (s, p, o) in self.graph.triples((None, RDF.type, None)): if o in OWL_RL_Datatypes: used_datatypes.add(o) if s not in implicit: _add_to_explicit(s, o) # Finally, there may be sameAs statements that bind nodes to some of the existing ones. This does not introduce # new datatypes, so the used_datatypes array does not get extended for (s, p, o) in self.graph.triples((None, OWL.sameAs, None)): if o in implicit: _add_to_explicit(s, implicit[o]) # note that s in implicit would mean that the original graph has # a literal in subject position which is not allowed at the moment, so I do not bother if o in explicit: _append_to_explicit(s, o) if s in explicit: _append_to_explicit(o, s) # what we have now: # explicit+implicit contains all the resources of type datatype; # implicit contains those that are given by an explicit literal # explicit contains those that are given by general resources, and there can be a whole array for each entry # RULE dt-type1: add a Datatype typing for all those # Note: the strict interpretation of OWL RL is to do that for all allowed datatypes, but this is # under discussion right now. The optimized version uses only what is really in use for dt in OWL_RL_Datatypes: self.store_triple((dt, RDF.type, RDFS.Datatype)) for dts in explicit.values(): for dt in dts: self.store_triple((dt, RDF.type, RDFS.Datatype)) # Datatype reasoning means that certain datatypes are subtypes of one another. # I could simply generate the extra subclass relationships into the graph and let the generic # process work its way, but it seems to be an overkill. Instead, I prefer to add the explicit typing # into the graph 'manually' for r in explicit: # these are the datatypes that this resource has dtypes = explicit[r] for dt in dtypes: _handle_subsumptions(r, dt) for r, dt in implicit.items(): _handle_subsumptions(r, dt) # Last step: add the datatype disjointness relationships. This is done only for # - 'top' level datatypes # - used in the graph for t in OWLRL_Datatypes_Disjointness: (l, pred, r) = t if l in used_datatypes and r in used_datatypes: self.store_triple(t) def _one_time_rules_misc(self): """ Rules executed: cls-thing, cls-nothing, prp-ap. """ # RULE cls-thing self.store_triple((OWL.Thing, RDF.type, OWL.Class)) # RULE cls-nothing self.store_triple((OWL.Nothing, RDF.type, OWL.Class)) # RULE prp-ap for an in OWLRL_Annotation_properties: self.store_triple((an, RDF.type, OWL.AnnotationProperty)) def one_time_rules(self): """ Some of the rules in the rule set are axiomatic in nature, meaning that they really have to be added only once, there is no reason to add these in a cycle. These are performed by this method that is invoked only once at the beginning of the process. These are: cls-thing, cls-nothing1, prp-ap, dt-types1, dt-types2, dt-eq, dt-diff. """ self._one_time_rules_misc() self._one_time_rules_datatypes() def rules(self, t, cycle_num): """ Go through the various rule groups, as defined in the OWL-RL profile text and implemented via local methods. (The calling cycle takes every tuple in the graph.) :param t: A triple (in the form of a tuple). :param cycle_num: Which cycle are we in, starting with 1. This value is forwarded to all local rules; it is also used locally to collect the bnodes in the graph. """ if cycle_num == 1: for r in t: if isinstance(r, BNode) and r not in self.bnodes: self.bnodes.append(r) self._properties(t, cycle_num) self._equality(t, cycle_num) self._classes(t, cycle_num) self._class_axioms(t, cycle_num) self._schema_vocabulary(t, cycle_num) def _property_chain(self, p, x): """ Implementation of the property chain axiom, invoked from inside the property axiom handler. This is the implementation of rule prp-spo2, taken aside for an easier readability of the code.""" chain = self._list(x) # The complication is that, at each step of the chain, there may be spawns, leading to a multitude # of 'sub' chains:-( if len(chain) > 0: for (u1, _y, _z) in self.graph.triples((None, chain[0], None)): # At least the chain can be started, because the leftmost property has at least # one element in its extension finalList = [(u1, _z)] chainExists = True for pi in chain[1:]: newList = [] for (_u, ui) in finalList: for u in self.graph.objects(ui, pi): # what is stored is only last entry with u1, the intermediate results # are not of interest newList.append((u1, u)) # I have now, in new list, all the intermediate results # until pi # if new list is empty, that means that is a blind alley if len(newList) == 0: chainExists = False break else: finalList = newList if chainExists: for (_u, un) in finalList: self.store_triple((u1, p, un)) def _equality(self, triple, cycle_num): """ Table 4: Semantics of equality. Essentially, the eq-* rules. @param triple: triple to work on @param cycle_num: which cycle are we in, starting with 1. Can be used for some optimization. """ # In many of the 'if' branches, corresponding to rules in the document, # the branch begins by a renaming of variables (eg, pp, c = s, o). # There is no programming reasons for doing that, but by renaming the # variables it becomes easier to compare the declarative rules # in the document with the implementation s, p, o = triple # RULE eq-ref self.store_triple((s, OWL.sameAs, s)) self.store_triple((o, OWL.sameAs, o)) self.store_triple((p, OWL.sameAs, p)) if p == OWL.sameAs: x, y = s, o # RULE eq-sym self.store_triple((y, OWL.sameAs, x)) # RULE eq-trans for z in self.graph.objects(y, OWL.sameAs): self.store_triple((x, OWL.sameAs, z)) # RULE eq-rep-s for pp, oo in self.graph.predicate_objects(s): self.store_triple((o, pp, oo)) # RULE eq-rep-p for ss, oo in self.graph.subject_objects(s): self.store_triple((ss, o, oo)) # RULE eq-rep-o for ss, pp in self.graph.subject_predicates(o): self.store_triple((ss, pp, s)) # RULE eq-diff1 if (s, OWL.differentFrom, o) in self.graph or ( o, OWL.differentFrom, s, ) in self.graph: self.add_error( "'sameAs' and 'differentFrom' cannot be used on the same subject-object pair: (%s, %s)" % (s, o) ) # RULES eq-diff2 and eq-diff3 if p == RDF.type and o == OWL.AllDifferent: x = s # the objects method are generators, we cannot simply concatenate them. So we turn the results # into lists first. (Otherwise the body of the for loops should be repeated verbatim, which # is silly and error prone... m1 = [i for i in self.graph.objects(x, OWL.members)] m2 = [i for i in self.graph.objects(x, OWL.distinctMembers)] for y in m1 + m2: zis = self._list(y) for i in range(0, len(zis) - 1): zi = zis[i] for j in range(i + 1, len(zis) - 1): zj = zis[j] if ( (zi, OWL.sameAs, zj) in self.graph or (zj, OWL.sameAs, zi) in self.graph ) and zi != zj: self.add_error( "'sameAs' and 'AllDifferent' cannot be used on the same subject-object " "pair: (%s, %s)" % (zi, zj) ) def _properties(self, triple, cycle_num): """ Table 5: The Semantics of Axioms about Properties. Essentially, the prp-* rules. @param triple: triple to work on @param cycle_num: which cycle are we in, starting with 1. Can be used for some optimization. """ # In many of the 'if' branches, corresponding to rules in the document, # the branch begins by a renaming of variables (eg, pp, c = s, o). # There is no programming reasons for doing that, but by renaming the # variables it becomes easier to compare the declarative rules # in the document with the implementation p, t, o = triple # RULE prp-ap if cycle_num == 1 and t in OWLRL_Annotation_properties: self.store_triple((t, RDF.type, OWL.AnnotationProperty)) # RULE prp-dom if t == RDFS.domain: for x, y in self.graph.subject_objects(p): self.store_triple((x, RDF.type, o)) # RULE prp-rng elif t == RDFS.range: for x, y in self.graph.subject_objects(p): self.store_triple((y, RDF.type, o)) elif t == RDF.type: # RULE prp-fp if o == OWL.FunctionalProperty: # Property axiom #3 for x, y1 in self.graph.subject_objects(p): for y2 in self.graph.objects(x, p): # Optimization: if the two resources are identical, the samAs is already # taken place somewhere else, unnecessary to add it here if y1 != y2: self.store_triple((y1, OWL.sameAs, y2)) # RULE prp-ifp elif o == OWL.InverseFunctionalProperty: for x1, y in self.graph.subject_objects(p): for x2 in self.graph.subjects(p, y): # Optimization: if the two resources are identical, the samAs is already # taken place somewhere else, unnecessary to add it here if x1 != x2: self.store_triple((x1, OWL.sameAs, x2)) # RULE prp-irp elif o == OWL.IrreflexiveProperty: for x, y in self.graph.subject_objects(p): if x == y: self.add_error( "Irreflexive property used on %s with %s" % (x, p) ) # RULE prp-symp elif o == OWL.SymmetricProperty: for x, y in self.graph.subject_objects(p): self.store_triple((y, p, x)) # RULE prp-asyp elif o == OWL.AsymmetricProperty: for x, y in self.graph.subject_objects(p): if (y, p, x) in self.graph: self.add_error( "Erroneous usage of asymmetric property %s on %s and %s" % (p, x, y) ) # RULE prp-trp elif o == OWL.TransitiveProperty: for x, y in self.graph.subject_objects(p): for z in self.graph.objects(y, p): self.store_triple((x, p, z)) # # Breaking the order here, I take some additional rules here to the branch checking the type... # # RULE prp-adp elif o == OWL.AllDisjointProperties: x = p for y in self.graph.objects(x, OWL.members): pis = self._list(y) for i in range(0, len(pis) - 1): pi = pis[i] for j in range(i + 1, len(pis) - 1): pj = pis[j] for x, y in self.graph.subject_objects(pi): if (x, pj, y) in self.graph: self.add_error( "Disjoint properties in an 'AllDisjointProperties' are not really " "disjoint: (%s, %s,%s) and (%s,%s,%s)" % (x, pi, y, x, pj, y) ) # RULE prp-spo1 elif t == RDFS.subPropertyOf: p1, p2 = p, o for x, y in self.graph.subject_objects(p1): self.store_triple((x, p2, y)) # RULE prp-spo2 elif t == OWL.propertyChainAxiom: self._property_chain(p, o) # RULES prp-eqp1 and prp-eqp2 elif t == OWL.equivalentProperty: p1, p2 = p, o # Optimization: it clearly does not make sense to run these # if the two properties are identical (a separate axiom # does create an equivalent property relations among identical # properties, too...) if p1 != p2: # RULE prp-eqp1 for x, y in self.graph.subject_objects(p1): self.store_triple((x, p2, y)) # RULE prp-eqp2 for x, y in self.graph.subject_objects(p2): self.store_triple((x, p1, y)) # RULE prp-pdw elif t == OWL.propertyDisjointWith: p1, p2 = p, o for x, y in self.graph.subject_objects(p1): if (x, p2, y) in self.graph: self.add_error( "Erroneous usage of disjoint properties %s and %s on %s and %s" % (p1, p2, x, y) ) # RULES prp-inv1 and prp-inv2 elif t == OWL.inverseOf: p1, p2 = p, o # RULE prp-inv1 for x, y in self.graph.subject_objects(p1): self.store_triple((y, p2, x)) # RULE prp-inv2 for x, y in self.graph.subject_objects(p2): self.store_triple((y, p1, x)) # RULE prp-key elif t == OWL.hasKey: c, u = p, o pis = self._list(u) if len(pis) > 0: for x in self.graph.subjects(RDF.type, c): # "Calculate" the keys for 'x'. The complication is that there can be various combinations # of the keys, and that is the structure one has to build up here... # # The final list will be a list of lists, with each constituents being the possible combinations # of the key values. # startup the list finalList = [[zi] for zi in self.graph.objects(x, pis[0])] for pi in pis[1:]: newList = [] for zi in self.graph.objects(x, pi): newList = newList + [l + [zi] for l in finalList] finalList = newList # I am not sure this can happen, but better safe then sorry... ruling out # the value lists whose length are not kosher # (To be checked whether this is necessary in the first place) valueList = [l for l in finalList if len(l) == len(pis)] # Now we can look for the y-s, to see if they have the same key values for y in self.graph.subjects(RDF.type, c): # rule out the existing equivalences if not ( y == x or (y, OWL.sameAs, x) in self.graph or (x, OWL.sameAs, y) in self.graph ): # 'calculate' the keys for the y values and see if there is a match for vals in valueList: same = True for i in range(0, len(pis) - 1): if (y, pis[i], vals[i]) not in self.graph: same = False # No use going with this property line break if same: self.store_triple((x, OWL.sameAs, y)) # Look for the next 'y', this branch is finished, no reason to continue break # RULES prp-npa1 and prp-npa2 elif t == OWL.sourceIndividual: x, i1 = p, o for p1 in self.graph.objects(x, OWL.assertionProperty): for i2 in self.graph.objects(x, OWL.targetIndividual): if (i1, p1, i2) in self.graph: self.add_error( "Negative (object) property assertion violated for: (%s, %s, %s)" % (i1, p1, i2) ) for i2 in self.graph.objects(x, OWL.targetValue): if (i1, p1, i2) in self.graph: self.add_error( "Negative (datatype) property assertion violated for: (%s, %s, %s)" % (i1, p1, i2) ) def _classes(self, triple, cycle_num): """ Table 6: The Semantics of Classes. Essentially, the cls-* rules @param triple: triple to work on @param cycle_num: which cycle are we in, starting with 1. Can be used for some optimization. """ # In many of the 'if' branches, corresponding to rules in the document, # the branch begins by a renaming of variables (eg, pp, c = s, o). # There is no programming reasons for doing that, but by renaming the # variables it becomes easier to compare the declarative rules # in the document with the implementation c, p, x = triple # RULE cls-nothing2 if p == RDF.type and x == OWL.Nothing: self.add_error("%s is defined of type 'Nothing'" % c) # RULES cls-int1 and cls-int2 if p == OWL.intersectionOf: classes = self._list(x) # RULE cls-int1 # Optimization: by looking at the members of class[0] right away one # reduces the search spaces a bit. Individuals not in that class # are without interest anyway # I am not sure how empty lists are sanctioned, so having an extra check # on that does not hurt.. if len(classes) > 0: for y in self.graph.subjects(RDF.type, classes[0]): if False not in [ (y, RDF.type, cl) in self.graph for cl in classes[1:] ]: self.store_triple((y, RDF.type, c)) # RULE cls-int2 for y in self.graph.subjects(RDF.type, c): for cl in classes: self.store_triple((y, RDF.type, cl)) # RULE cls-uni elif p == OWL.unionOf: for cl in self._list(x): for y in self.graph.subjects(RDF.type, cl): self.store_triple((y, RDF.type, c)) # RULE cls-comm elif p == OWL.complementOf: c1, c2 = c, x for x1 in self.graph.subjects(RDF.type, c1): if (x1, RDF.type, c2) in self.graph: self.add_error( "Violation of complementarity for classes %s and %s on element %s" % (c1, c2, x) ) # RULES cls-svf1 and cls=svf2 elif p == OWL.someValuesFrom: xx, y = c, x # RULE cls-svf1 # RULE cls-svf2 for pp in self.graph.objects(xx, OWL.onProperty): for u, v in self.graph.subject_objects(pp): if y == OWL.Thing or (v, RDF.type, y) in self.graph: self.store_triple((u, RDF.type, xx)) # RULE cls-avf elif p == OWL.allValuesFrom: xx, y = c, x for pp in self.graph.objects(xx, OWL.onProperty): for u in self.graph.subjects(RDF.type, xx): for v in self.graph.objects(u, pp): if self.restriction_typing_check(v, y): self.store_triple((v, RDF.type, y)) else: self.add_error( "Violation of type restriction for allValuesFrom in %s for datatype %s on " "value %s" % (pp, y, v) ) # RULES cls-hv1 and cls-hv2 elif p == OWL.hasValue: xx, y = c, x for pp in self.graph.objects(xx, OWL.onProperty): # RULE cls-hv1 for u in self.graph.subjects(RDF.type, xx): self.store_triple((u, pp, y)) # RULE cls-hv2 for u in self.graph.subjects(pp, y): self.store_triple((u, RDF.type, xx)) # RULES cls-maxc1 and cls-maxc1 elif p == OWL.maxCardinality: # This one is a bit complicated, because the literals have been # exchanged against bnodes... # # The construct should lead to an integer. Something may go wrong along the line # leading to an exception... xx = c if x.value == 0: # RULE cls-maxc1 for pp in self.graph.objects(xx, OWL.onProperty): for u, y in self.graph.subject_objects(pp): # This should not occur: if (u, RDF.type, xx) in self.graph: self.add_error( "Erroneous usage of maximum cardinality with %s and %s" % (xx, y) ) elif x.value == 1: # RULE cls-maxc2 for pp in self.graph.objects(xx, OWL.onProperty): for u, y1 in self.graph.subject_objects(pp): if (u, RDF.type, xx) in self.graph: for y2 in self.graph.objects(u, pp): if y1 != y2: self.store_triple((y1, OWL.sameAs, y2)) # RULES cls-maxqc1, cls-maxqc2, cls-maxqc3, cls-maxqc4 elif p == OWL.maxQualifiedCardinality: # This one is a bit complicated, because the literals have been # exchanged against bnodes... # # The construct should lead to an integer. Something may go wrong along the line # leading to an exception... xx = c if x.value == 0: # RULES cls-maxqc1 and cls-maxqc2 folded in one for pp in self.graph.objects(xx, OWL.onProperty): for cc in self.graph.objects(xx, OWL.onClass): for u, y in self.graph.subject_objects(pp): # This should not occur: if ( (y, RDF.type, cc) in self.graph or cc == OWL.Thing ) and (u, RDF.type, xx) in self.graph: self.add_error( "Erroneous usage of maximum qualified cardinality with %s, %s and %s" % (xx, cc, y) ) elif x.value == 1: # RULE cls-maxqc3 and cls-maxqc4 folded in one for pp in self.graph.objects(xx, OWL.onProperty): for cc in self.graph.objects(xx, OWL.onClass): for u, y1 in self.graph.subject_objects(pp): if (u, RDF.type, xx) in self.graph: if cc == OWL.Thing: for y2 in self.graph.objects(u, pp): if y1 != y2: self.store_triple((y1, OWL.sameAs, y2)) else: if (y1, RDF.type, cc) in self.graph: for y2 in self.graph.objects(u, pp): if ( y1 != y2 and (y2, RDF.type, cc) in self.graph ): self.store_triple((y1, OWL.sameAs, y2)) # TODO: what if x.value not in (0, 1)? according to the spec # the cardinality shall be no more than 1, so add an # error? # RULE cls-oo elif p == OWL.oneOf: for y in self._list(x): self.store_triple((y, RDF.type, c)) def _class_axioms(self, triple, cycle_num): """ Table 7: Class Axioms. Essentially, the cax-* rules. @param triple: triple to work on @param cycle_num: which cycle are we in, starting with 1. Can be used for some optimization. """ # In many of the 'if' branches, corresponding to rules in the document, # the branch begins by a renaming of variables (eg, pp, c = s, o). # There is no programming reasons for doing that, but by renaming the # variables it becomes easier to compare the declarative rules # in the document with the implementation c1, p, c2 = triple # RULE cax-sco if p == RDFS.subClassOf: # Other axioms sets classes to be subclasses of themselves, to one can optimize the trivial case if c1 != c2: for x in self.graph.subjects(RDF.type, c1): self.store_triple((x, RDF.type, c2)) # RULES cax-eqc1 and cax-eqc1 # Other axioms set classes to be equivalent to themselves, one can optimize the trivial case elif p == OWL.equivalentClass and c1 != c2: # RULE cax-eqc1 for x in self.graph.subjects(RDF.type, c1): self.store_triple((x, RDF.type, c2)) # RULE cax-eqc1 for x in self.graph.subjects(RDF.type, c2): self.store_triple((x, RDF.type, c1)) # RULE cax-dw elif p == OWL.disjointWith: for x in self.graph.subjects(RDF.type, c1): if (x, RDF.type, c2) in self.graph: self.add_error( "Disjoint classes %s and %s have a common individual %s" % (c1, c2, x) ) # RULE cax-adc elif p == RDF.type and c2 == OWL.AllDisjointClasses: x = c1 for y in self.graph.objects(x, OWL.members): classes = self._list(y) if len(classes) > 0: for i in range(0, len(classes) - 1): cl1 = classes[i] for z in self.graph.subjects(RDF.type, cl1): for cl2 in classes[(i + 1) :]: if (z, RDF.type, cl2) in self.graph: self.add_error( "Disjoint classes %s and %s have a common individual %s" % (cl1, cl2, z) ) def _schema_vocabulary(self, triple, cycle_num): """ Table 9: The Semantics of Schema Vocabulary. Essentially, the scm-* rules @param triple: triple to work on @param cycle_num: which cycle are we in, starting with 1. Can be used for some optimization. """ # In many of the 'if' branches, corresponding to rules in the document, # the branch begins by a renaming of variables (eg, pp, c = s, o). # There is no programming reasons for doing that, but by renaming the # variables it becomes easier to compare the declarative rules # in the document with the implementation s, p, o = triple # RULE scm-cls if p == RDF.type and o == OWL.Class: c = s self.store_triple((c, RDFS.subClassOf, c)) self.store_triple((c, OWL.equivalentClass, c)) self.store_triple((c, RDFS.subClassOf, OWL.Thing)) self.store_triple((OWL.Nothing, RDFS.subClassOf, c)) # RULE scm-sco # Rule scm-eqc2 elif p == RDFS.subClassOf: c1, c2 = s, o # RULE scm-sco # Optimize out the trivial identity case (set elsewhere already) if c1 != c2: for c3 in self.graph.objects(c2, RDFS.subClassOf): # Another axiom already sets that... if c1 != c3: self.store_triple((c1, RDFS.subClassOf, c3)) # RULE scm-eqc2 if (c2, RDFS.subClassOf, c1) in self.graph: self.store_triple((c1, OWL.equivalentClass, c2)) # RULE scm-eqc elif p == OWL.equivalentClass and s != o: c1, c2 = s, o self.store_triple((c1, RDFS.subClassOf, c2)) self.store_triple((c2, RDFS.subClassOf, c1)) # RULE scm-op and RULE scm-dp folded together # There is a bit of a cheating here: 'Property' is not, strictly speaking, in the rule set! elif p == RDF.type and ( o == OWL.ObjectProperty or o == OWL.DatatypeProperty or o == RDF.Property ): pp = s self.store_triple((pp, RDFS.subPropertyOf, pp)) self.store_triple((pp, OWL.equivalentProperty, pp)) # RULE scm-spo # RULE scm-eqp2 elif p == RDFS.subPropertyOf and s != o: p1, p2 = s, o # Optimize out the trivial identity case (set elsewhere already) # RULE scm-spo if p1 != p2: for p3 in self.graph.objects(p2, RDFS.subPropertyOf): if p1 != p3: self.store_triple((p1, RDFS.subPropertyOf, p3)) # RULE scm-eqp2 if (p2, RDFS.subPropertyOf, p1) in self.graph: self.store_triple((p1, OWL.equivalentProperty, p2)) # RULE scm-eqp # Optimize out the trivial identity case (set elsewhere already) elif p == OWL.equivalentProperty and s != o: p1, p2 = s, o self.store_triple((p1, RDFS.subPropertyOf, p2)) self.store_triple((p2, RDFS.subPropertyOf, p1)) # RULES scm-dom1 and scm-dom2 elif p == RDFS.domain: # RULE scm-dom1 pp, c1 = s, o for (_x, _y, c2) in self.graph.triples((c1, RDFS.subClassOf, None)): if c1 != c2: self.store_triple((pp, RDFS.domain, c2)) # RULE scm-dom1 p2, c = s, o for (p1, _x, _y) in self.graph.triples((None, RDFS.subPropertyOf, p2)): if p1 != p2: self.store_triple((p1, RDFS.domain, c)) # RULES scm-rng1 and scm-rng2 elif p == RDFS.range: # RULE scm-rng1 pp, c1 = s, o for (_x, _y, c2) in self.graph.triples((c1, RDFS.subClassOf, None)): if c1 != c2: self.store_triple((pp, RDFS.range, c2)) # RULE scm-rng1 p2, c = s, o for (p1, _x, _y) in self.graph.triples((None, RDFS.subPropertyOf, p2)): if p1 != p2: self.store_triple((p1, RDFS.range, c)) # RULE scm-hv elif p == OWL.hasValue: c1, i = s, o for p1 in self.graph.objects(c1, OWL.onProperty): for c2 in self.graph.subjects(OWL.hasValue, i): for p2 in self.graph.objects(c2, OWL.onProperty): if (p1, RDFS.subPropertyOf, p2) in self.graph: self.store_triple((c1, RDFS.subClassOf, c2)) # RULES scm-svf1 and scm-svf2 elif p == OWL.someValuesFrom: # RULE scm-svf1 c1, y1 = s, o for pp in self.graph.objects(c1, OWL.onProperty): for c2 in self.graph.subjects(OWL.onProperty, pp): for y2 in self.graph.objects(c2, OWL.someValuesFrom): if (y1, RDFS.subClassOf, y2) in self.graph: self.store_triple((c1, RDFS.subClassOf, c2)) # RULE scm-svf2 c1, y = s, o for p1 in self.graph.objects(c1, OWL.onProperty): for c2 in self.graph.subjects(OWL.someValuesFrom, y): for p2 in self.graph.objects(c2, OWL.onProperty): if (p1, RDFS.subPropertyOf, p2) in self.graph: self.store_triple((c1, RDFS.subClassOf, c2)) # RULES scm-avf1 and scm-avf2 elif p == OWL.allValuesFrom: # RULE scm-avf1 c1, y1 = s, o for pp in self.graph.objects(c1, OWL.onProperty): for c2 in self.graph.subjects(OWL.onProperty, pp): for y2 in self.graph.objects(c2, OWL.allValuesFrom): if (y1, RDFS.subClassOf, y2) in self.graph: self.store_triple((c1, RDFS.subClassOf, c2)) # RULE scm-avf2 c1, y = s, o for p1 in self.graph.objects(c1, OWL.onProperty): for c2 in self.graph.subjects(OWL.allValuesFrom, y): for p2 in self.graph.objects(c2, OWL.onProperty): if (p1, RDFS.subPropertyOf, p2) in self.graph: self.store_triple((c2, RDFS.subClassOf, c1)) # RULE scm-int elif p == OWL.intersectionOf: c, x = s, o for ci in self._list(x): self.store_triple((c, RDFS.subClassOf, ci)) # RULE scm-uni elif p == OWL.unionOf: c, x = s, o for ci in self._list(x): self.store_triple((ci, RDFS.subClassOf, c)) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1737416745.6457307 owlrl-7.1.3/owlrl/OWLRLExtras.py0000755000000000000000000003724614743560052013433 0ustar00# -*- coding: utf-8 -*- # """ Extension to OWL 2 RL, ie, some additional rules added to the system from OWL 2 Full. It is implemented through the :class:`.OWLRL_Extension` class, whose reference has to be passed to the relevant semantic class (i.e., either the OWL 2 RL or the combined closure class) as an 'extension'. The added rules and features are: - self restriction - owl:rational datatype - datatype restrictions via facets In more details, the rules that are added: 1. self restriction 1: :code:`?z owl:hasSelf ?x. ?x owl:onProperty ?p. ?y rdf:type ?z. => ?y ?p ?y.` 2. self restriction 2: :code:`?z owl:hasSelf ?x. ?x owl:onProperty ?p. ?y ?p ?y. => ?y rdf:type ?z.` **Requires**: `RDFLib`_, 4.0.0 and higher. .. _RDFLib: https://github.com/RDFLib/rdflib **License**: This software is available for use under the `W3C Software License`_. .. _W3C Software License: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 **Organization**: `World Wide Web Consortium`_ .. _World Wide Web Consortium: http://www.w3.org **Author**: `Ivan Herman`_ .. _Ivan Herman: http://www.w3.org/People/Ivan/ """ __author__ = "Ivan Herman" __contact__ = "Ivan Herman, ivan@w3.org" __license__ = "W3C® SOFTWARE NOTICE AND LICENSE, http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231" from typing import Union import rdflib from rdflib import Graph from rdflib.namespace import RDF, RDFS, OWL, XSD from fractions import Fraction as Rational from .DatatypeHandling import AltXSDToPYTHON # noinspection PyPep8Naming from .CombinedClosure import RDFS_OWLRL_Semantics from .OWLRL import OWLRL_Annotation_properties from .XsdDatatypes import OWL_RL_Datatypes, OWL_Datatype_Subsumptions from .RestrictedDatatype import extract_faceted_datatypes ####################################################################################################################### # Rational datatype # noinspection PyPep8Naming def _strToRational(v): """Converting a string to a rational. According to the OWL spec: numerator must be an integer, denominator a positive integer (ie, xsd['integer'] type), and the denominator should not have a '+' sign. @param v: the literal string defined as boolean @return corresponding Rational value @rtype: Rational @raise ValueError: invalid rational string literal """ try: r = v.split("/") if len(r) == 2: n_str = r[0] d_str = r[1] else: n_str = r[0] d_str = "1" if d_str.strip()[0] == "+": raise ValueError("Invalid Rational literal value %s" % v) else: return Rational( AltXSDToPYTHON[XSD.integer](n_str), AltXSDToPYTHON[XSD.positiveInteger](d_str), ) except: raise ValueError("Invalid Rational literal value %s" % v) ####################################################################################################################### # noinspection PyPep8Naming,PyBroadException class OWLRL_Extension(RDFS_OWLRL_Semantics): """ Additional rules to OWL 2 RL. The initialization method also adds the :code:`owl:rational` datatype to the set of allowed datatypes with the :func:`owlrl.OWLRLExtras._strToRational` function as a conversion between the literal form and a Rational. The :code:`xsd:decimal` datatype is also set to be a subclass of :code:`owl:rational`. Furthermore, the restricted datatypes are extracted from the graph using a separate method in a different module (:py:func:`.RestrictedDatatype.extract_faceted_datatypes`), and all those datatypes are also added to the set of allowed datatypes. In the case of the restricted datatypes and extra subsumption relationship is set up between the restricted and the base datatypes. :cvar extra_axioms: Additional axioms that have to be added to the deductive closure (in case the axiomatic triples are required). :var restricted_datatypes: list of the datatype restriction from :class:`.RestrictedDatatype`. :type restricted_datatypes: list of L{restricted datatype} instances """ extra_axioms = [ (OWL.hasSelf, RDF.type, RDF.Property), (OWL.hasSelf, RDFS.domain, RDF.Property), ] def __init__(self, graph: Graph, axioms, daxioms, rdfs: bool = False, destination: Union[None, Graph] = None): """ @param graph: the RDF graph to be extended @type graph: rdflib.Graph @param axioms: whether (non-datatype) axiomatic triples should be added or not @type axioms: Boolean @param daxioms: whether datatype axiomatic triples should be added or not @type daxioms: Boolean @param rdfs: whether RDFS extension is done @type rdfs: boolean @param destination: the destination graph to which the results are written. If None, use the source graph. @type destination: rdflib.Graph """ RDFS_OWLRL_Semantics.__init__(self, graph, axioms, daxioms, rdfs=rdfs, destination=destination) self.rdfs = rdfs self.add_new_datatype( OWL.rational, _strToRational, OWL_RL_Datatypes, subsumption_dict=OWL_Datatype_Subsumptions, subsumption_key=XSD.decimal, subsumption_list=[OWL.rational], ) self.restricted_datatypes = extract_faceted_datatypes(self, graph) for dt in self.restricted_datatypes: self.add_new_datatype( dt.datatype, dt.toPython, OWL_RL_Datatypes, subsumption_dict=OWL_Datatype_Subsumptions, subsumption_key=dt.datatype, subsumption_list=[dt.base_type], ) # noinspection PyShadowingNames def _subsume_restricted_datatypes(self): """ A one-time-rule: all the literals are checked whether they are (a) of type restricted by a faceted (restricted) datatype and (b) whether the corresponding value abides to the restrictions. If true, then the literal gets an extra tag as being of type of the restricted datatype, too. """ literals = self._literals() for rt in self.restricted_datatypes: # This is a recorded restriction. The base type is: base_type = rt.base_type # Look through all the literals for lt in literals: # check if the type of that literal matches. Note that this also takes # into account the subsumption datatypes, that have been taken # care of by the 'regular' OWL RL process if (lt, RDF.type, base_type) in self.graph: try: # the conversion or the check may go wrong and raise an exception; then simply move on if rt.checkValue(lt.toPython()): # yep, this is also of type 'rt' self.store_triple((lt, RDF.type, rt.datatype)) except: continue def restriction_typing_check(self, v, t): """ Helping method to check whether a type statement is in line with a possible restriction. This method is invoked by rule "cls-avf" before setting a type on an allValuesFrom restriction. The method is a placeholder at this level. It is typically implemented by subclasses for extra checks, e.g., for datatype facet checks. :param v: the resource that is to be 'typed'. :param t: the targeted type (i.e., Class). :return: Boolean. :rtype: bool """ # Look through the restricted datatypes to see if 't' corresponds to one of those... # There are a bunch of possible exceptions here with datatypes, but they can all # be ignored... try: for rt in self.restricted_datatypes: if rt.datatype == t: # bingo if v in self.literal_proxies.bnode_to_lit: return rt.checkValue( self.literal_proxies.bnode_to_lit[v].lit.toPython() ) else: return True # if we got here, no restriction applies return True except: return True def one_time_rules(self): """ This method is invoked only once at the beginning, and prior of, the forward chaining process. At present, only the L{subsumption} of restricted datatypes<_subsume_restricted_datatypes>} is performed. """ RDFS_OWLRL_Semantics.one_time_rules(self) # it is important to flush the triples at this point, because # the handling of the restriction datatypes rely on the datatype # subsumption triples added by the superclass self.flush_stored_triples() self._subsume_restricted_datatypes() def add_axioms(self): """ Add the :class:`owlrl.OWLRLExtras.OWLRL_Extension.extra_axioms`, related to the self restrictions. This method is invoked only once at the beginning, and prior of, the forward chaining process. """ RDFS_OWLRL_Semantics.add_axioms(self) for t in self.extra_axioms: self.destination.add(t) def rules(self, t, cycle_num): """ Go through the additional rules implemented by this module. :param t: A triple (in the form of a tuple). :type t: tuple :param cycle_num: Which cycle are we in, starting with 1. This value is forwarded to all local rules; it is also used locally to collect the bnodes in the graph. :type cycle_num: int """ RDFS_OWLRL_Semantics.rules(self, t, cycle_num) z, q, x = t if q == OWL.hasSelf: for p in self.graph.objects(z, OWL.onProperty): for y in self.graph.subjects(RDF.type, z): self.store_triple((y, p, y)) for y1, y2 in self.graph.subject_objects(p): if y1 == y2: self.store_triple((y1, RDF.type, z)) # noinspection PyPep8Naming class OWLRL_Extension_Trimming(OWLRL_Extension): """ This Class adds only one feature to :class:`.OWLRL_Extension`: to initialize with a trimming flag set to :code:`True` by default. This is pretty experimental and probably contentious: this class *removes* a number of triples from the Graph at the very end of the processing steps. These triples are either the by-products of the deductive closure calculation or are axiom like triples that are added following the rules of OWL 2 RL. While these triples *are necessary* for the correct inference of really 'useful' triples, they may not be of interest for the application for the end result. The triples that are removed are of the form (following a SPARQL-like notation): - :code:`?x owl:sameAs ?x`, :code:`?x rdfs:subClassOf ?x`, :code:`?x rdfs:subPropertyOf ?x`, :code:`?x owl:equivalentClass ?x` type triples. - :code:`?x rdfs:subClassOf rdf:Resource`, :code:`?x rdfs:subClassOf owl:Thing`, :code:`?x rdf:type rdf:Resource`, :code:`owl:Nothing rdfs:subClassOf ?x` type triples. - For a datatype that does *not* appear explicitly in a type assignments (ie, in a :code:`?x rdf:type dt`) the corresponding :code:`dt rdf:type owl:Datatype` and :code:`dt rdf:type owl:DataRange` triples, as well as the disjointness statements with other datatypes. - annotation property axioms. - a number of axiomatic triples on :code:`owl:Thing`, :code:`owl:Nothing` and :code:`rdf:Resource` (eg, :code:`owl:Nothing rdf:type owl:Class`, :code:`owl:Thing owl:equivalentClass rdf:Resource`, etc). Trimming is the only feature of this class, done in the :py:meth:`.post_process` step. If users extend :class:`OWLRL_Extension`, this class can be safely mixed in via multiple inheritance. :param graph: The RDF graph to be extended. :type graph: :class:`rdflib.Graph` :param axioms: Whether (non-datatype) axiomatic triples should be added or not. :type axioms: bool :param daxioms: Whether datatype axiomatic triples should be added or not. :type daxioms: bool :param rdfs: Whether RDFS extension is done. :type rdfs: bool """ def __init__(self, graph: Graph, axioms, daxioms, rdfs: bool = False, destination: Union[None, Graph] = None): """ @param graph: the RDF graph to be extended @type graph: rdflib.Graph @param axioms: whether (non-datatype) axiomatic triples should be added or not @type axioms: Boolean @param daxioms: whether datatype axiomatic triples should be added or not @type daxioms: Boolean @param rdfs: whether RDFS extension is done @type rdfs: boolean @param destination: the destination graph to which the results are written. If None, use the source graph. @type destination: rdflib.Graph """ OWLRL_Extension.__init__(self, graph, axioms, daxioms, rdfs=rdfs, destination=destination) def post_process(self): """ Do some post-processing step performing the trimming of the graph. See the :class:`.OWLRL_Extension_Trimming` class for further details. """ OWLRL_Extension.post_process(self) self.flush_stored_triples() to_be_removed = set() for t in self.graph.triples((None, None, None)): s, p, o = t if s == o: if ( p == OWL.sameAs or p == OWL.equivalentClass or p == RDFS.subClassOf or p == RDFS.subPropertyOf ): to_be_removed.add(t) if ( (p == RDFS.subClassOf and (o == OWL.Thing or o == RDFS.Resource)) or (p == RDF.type and o == RDFS.Resource) or (s == OWL.Nothing and p == RDFS.subClassOf) ): to_be_removed.add(t) for dt in OWL_RL_Datatypes: # see if this datatype appears explicitly in the graph as the type of a symbol if len([s for s in self.graph.subjects(RDF.type, dt)]) == 0: to_be_removed.add((dt, RDF.type, RDFS.Datatype)) to_be_removed.add((dt, RDF.type, OWL.DataRange)) for t in self.graph.triples((dt, OWL.disjointWith, None)): to_be_removed.add(t) for t in self.graph.triples((None, OWL.disjointWith, dt)): to_be_removed.add(t) for an in OWLRL_Annotation_properties: self.destination.remove((an, RDF.type, OWL.AnnotationProperty)) to_be_removed.add((OWL.Nothing, RDF.type, OWL.Class)) to_be_removed.add((OWL.Nothing, RDF.type, RDFS.Class)) to_be_removed.add((OWL.Thing, RDF.type, OWL.Class)) to_be_removed.add((OWL.Thing, RDF.type, RDFS.Class)) to_be_removed.add((OWL.Thing, OWL.equivalentClass, RDFS.Resource)) to_be_removed.add((RDFS.Resource, OWL.equivalentClass, OWL.Thing)) to_be_removed.add((OWL.Class, OWL.equivalentClass, RDFS.Class)) to_be_removed.add((OWL.Class, RDFS.subClassOf, RDFS.Class)) to_be_removed.add((RDFS.Class, OWL.equivalentClass, OWL.Class)) to_be_removed.add((RDFS.Class, RDFS.subClassOf, OWL.Class)) to_be_removed.add((RDFS.Datatype, RDFS.subClassOf, OWL.DataRange)) to_be_removed.add((RDFS.Datatype, OWL.equivalentClass, OWL.DataRange)) to_be_removed.add((OWL.DataRange, RDFS.subClassOf, OWL.DatatypeProperty)) to_be_removed.add((OWL.DataRange, OWL.equivalentClass, OWL.DatatypeProperty)) for t in to_be_removed: self.destination.remove(t) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1737416745.6477797 owlrl-7.1.3/owlrl/RDFSClosure.py0000755000000000000000000002010014743560052013415 0ustar00# -*- coding: utf-8 -*- # """ This module is brute force implementation of the RDFS semantics on the top of RDFLib (with some caveats, see in the introductory text). **Requires**: `RDFLib`_, 4.0.0 and higher. .. _RDFLib: https://github.com/RDFLib/rdflib **License**: This software is available for use under the `W3C Software License`_. .. _W3C Software License: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 **Organization**: `World Wide Web Consortium`_ .. _World Wide Web Consortium: http://www.w3.org **Author**: `Ivan Herman`_ .. _Ivan Herman: http://www.w3.org/People/Ivan/ """ __author__ = "Ivan Herman" __contact__ = "Ivan Herman, ivan@w3.org" __license__ = "W3C® SOFTWARE NOTICE AND LICENSE, http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231" from typing import Union import rdflib from rdflib import Literal, Graph from rdflib.namespace import RDF, RDFS from itertools import product from owlrl.Closure import Core from owlrl.AxiomaticTriples import RDFS_Axiomatic_Triples, RDFS_D_Axiomatic_Triples ###################################################################################################### # RDFS Semantics class # noinspection PyPep8Naming class RDFS_Semantics(Core): """ RDFS Semantics class, ie, implementation of the RDFS closure graph. .. note:: Note that the module does *not* implement the so called Datatype entailment rules, simply because the underlying RDFLib does not implement the datatypes (ie, RDFLib will not make the literal "1.00" and "1.00000" identical, although even with all the ambiguities on datatypes, this I{should} be made equal...). Also, the so-called extensional entailment rules (Section 7.3.1 in the RDF Semantics document) have not been implemented either. The comments and references to the various rule follow the names as used in the `RDF Semantics document`_. .. _RDF Semantics document: http://www.w3.org/TR/rdf-mt/ :param graph: The RDF graph to be extended. :type graph: :class:`rdflib.Graph` :param axioms: Whether (non-datatype) axiomatic triples should be added or not. :type axioms: bool :param daxioms: Whether datatype axiomatic triples should be added or not. :type daxioms: bool :param rdfs: Whether RDFS inference is also done (used in subclassed only). :type rdfs: bool """ def __init__(self, graph: Graph, axioms, daxioms, rdfs: bool = False, destination: Union[None, Graph] = None): """ @param graph: the RDF graph to be extended @type graph: rdflib.Graph @param axioms: whether (non-datatype) axiomatic triples should be added or not @type axioms: bool @param daxioms: whether datatype axiomatic triples should be added or not @type daxioms: bool @param rdfs: whether RDFS inference is also done (used in subclassed only) @type rdfs: boolean @param destination: the destination graph to which the results are written. If None, use the source graph. @type destination: rdflib.Graph """ Core.__init__(self, graph, axioms, daxioms, rdfs=rdfs, destination=destination) def add_axioms(self): """ Add axioms """ for t in RDFS_Axiomatic_Triples: self.destination.add(t) for i in range(1, self.IMaxNum + 1): ci = RDF[("_%d" % i)] self.destination.add((ci, RDF.type, RDF.Property)) self.destination.add((ci, RDFS.domain, RDFS.Resource)) self.destination.add((ci, RDFS.range, RDFS.Resource)) self.destination.add((ci, RDF.type, RDFS.ContainerMembershipProperty)) def add_d_axioms(self): """ This is not really complete, because it just uses the comparison possibilities that RDFLib provides. """ # #1 literals = (lt for lt in self._literals() if lt.datatype is not None) for lt in literals: self.destination.add((lt, RDF.type, lt.datatype)) for t in RDFS_D_Axiomatic_Triples: self.destination.add(t) @staticmethod def _literals_same_as(lt1, lt2): if lt1.value is not None and lt2.value is not None: return lt1.value == lt2.value elif lt1.datatype is not None and lt2.datatype is not None: return lt1.__eq__(lt2) return False # noinspection PyBroadException def one_time_rules(self): """ Some of the rules in the rule set are axiomatic in nature, meaning that they really have to be added only once, there is no reason to add these in a cycle. These are performed by this method that is invoked only once at the beginning of the process. In this case this is related to a 'hidden' same as rules on literals with identical values (though different lexical values). """ # There is also a hidden sameAs rule in RDF Semantics: if a literal appears in a triple, and another one has # the same value, then the triple should be duplicated with the other value. literals = self._literals() items = ( (lt1, lt2) for lt1, lt2 in product(literals, literals) if (lt1 is lt2) or self._literals_same_as(lt1, lt2) ) for lt1, lt2 in items: # In OWL, this line is simply stating a sameAs for the # corresponding literals, and then let the usual rules take # effect. In RDFS this is not possible, so the sameAs rule is, # essentially replicated... for (s, p, o) in self.graph.triples((None, None, lt1)): self.destination.add((s, p, lt2)) def rules(self, t, cycle_num): """ Go through the RDFS entailment rules rdf1, rdfs4-rdfs12, by extending the graph. :param t: A triple (in the form of a tuple). :type t: tuple :param cycle_num: Which cycle are we in, starting with 1. Can be used for some (though minor) optimization. :type cycle_num: int """ s, p, o = t # rdf1 self.store_triple((p, RDF.type, RDF.Property)) # rdfs4a if cycle_num == 1: self.store_triple((s, RDF.type, RDFS.Resource)) # rdfs4b if cycle_num == 1: self.store_triple((o, RDF.type, RDFS.Resource)) if p == RDFS.domain: # rdfs2 for uuu, Y, yyy in self.graph.triples((None, s, None)): self.store_triple((uuu, RDF.type, o)) if p == RDFS.range: # rdfs3 for uuu, Y, vvv in self.graph.triples((None, s, None)): self.store_triple((vvv, RDF.type, o)) if p == RDFS.subPropertyOf: # rdfs5 for Z, Y, xxx in self.graph.triples((o, RDFS.subPropertyOf, None)): self.store_triple((s, RDFS.subPropertyOf, xxx)) # rdfs7 for zzz, Z, www in self.graph.triples((None, s, None)): self.store_triple((zzz, o, www)) if p == RDF.type and o == RDF.Property: # rdfs6 self.store_triple((s, RDFS.subPropertyOf, s)) if p == RDF.type and o == RDFS.Class: # rdfs8 self.store_triple((s, RDFS.subClassOf, RDFS.Resource)) # rdfs10 self.store_triple((s, RDFS.subClassOf, s)) if p == RDFS.subClassOf: # rdfs9 for vvv, Y, Z in self.graph.triples((None, RDF.type, s)): self.store_triple((vvv, RDF.type, o)) # rdfs11 for Z, Y, xxx in self.graph.triples((o, RDFS.subClassOf, None)): self.store_triple((s, RDFS.subClassOf, xxx)) if p == RDF.type and o == RDFS.ContainerMembershipProperty: # rdfs12 self.store_triple((s, RDFS.subPropertyOf, RDFS.member)) if p == RDF.type and o == RDFS.Datatype: self.store_triple((s, RDFS.subClassOf, RDFS.Literal)) def _literals(self): """ Get all literals defined in the graph. """ return set( o for s, p, o in self.graph.triples((None, None, None)) if isinstance(o, Literal) ) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1737416745.647953 owlrl-7.1.3/owlrl/RestrictedDatatype.py0000644000000000000000000005066114743560052015142 0ustar00# -*- coding: utf-8 -*- # """ Module to datatype restrictions, i.e., data ranges. The module implements the following aspects of datatype restrictions: - a new datatype is created run-time and added to the allowed and accepted datatypes; literals are checked whether they abide to the restrictions - the new datatype is defined to be a 'subClass' of the restricted datatype - literals of the restricted datatype and that abide to the restrictions defined by the facets are also assigned to be of the new type The last item is important to handle the following structures:: ex:RE a owl:Restriction ; owl:onProperty ex:p ; owl:someValuesFrom [ a rdfs:Datatype ; owl:onDatatype xsd:string ; owl:withRestrictions ( [ xsd:minLength "3"^^xsd:integer ] [ xsd:maxLength "6"^^xsd:integer ] ) ] . ex:q ex:p "abcd"^^xsd:string. In the case above the system can then infer that :code:`ex:q` is also of type :code:`ex:RE`. Datatype restrictions are used by the :class:`.OWLRLExtras.OWLRL_Extension` extension class. The implementation is **not** 100% complete. Some things that an ideal implementation should do are not done yet like: - checking whether a facet is of a datatype that is allowed for that facet - handling of non-literals in the facets (ie, if the resource is defined to be of type literal, but whose value is defined via a separate :code:`owl:sameAs` somewhere else) **Requires**: `RDFLib`_, 4.0.0 and higher. .. _RDFLib: https://github.com/RDFLib/rdflib **License**: This software is available for use under the `W3C Software License`_. .. _W3C Software License: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 **Organization**: `World Wide Web Consortium`_ .. _World Wide Web Consortium: http://www.w3.org **Author**: `Ivan Herman`_ .. _Ivan Herman: http://www.w3.org/People/Ivan/ """ __author__ = "Ivan Herman" __contact__ = "Ivan Herman, ivan@w3.org" __license__ = "W3C® SOFTWARE NOTICE AND LICENSE, http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231" import re from rdflib.namespace import OWL, RDF, RDFS, XSD from rdflib import Literal as rdflibLiteral, Graph from .DatatypeHandling import AltXSDToPYTHON from functools import reduce # Constant for datatypes using min, max (inclusive and exclusive): MIN_MAX = 0 # Constant for datatypes using length, minLength, and maxLength (and nothing else) LENGTH = 1 # Constant for datatypes using length, minLength, maxLength, and pattern LENGTH_AND_PATTERN = 2 # Constant for datatypes using length, minLength, maxLength, pattern, and lang range LENGTH_PATTERN_LRANGE = 3 # Dictionary of all the datatypes, keyed by category Datatypes_per_facets = { MIN_MAX: [ # OWL.rational, # OWL.real, XSD.decimal, XSD.integer, XSD.nonNegativeInteger, XSD.nonPositiveInteger, XSD.positiveInteger, XSD.negativeInteger, XSD.long, XSD.short, XSD.byte, XSD.unsignedLong, XSD.unsignedInt, XSD.unsignedShort, XSD.unsignedByte, XSD.double, XSD.float, XSD.dateTime, XSD.dateTimeStamp, XSD.time, XSD.date, ], LENGTH: [XSD.hexBinary, XSD.base64Binary], LENGTH_AND_PATTERN: [ XSD.anyURI, XSD.string, XSD.NMTOKEN, XSD.Name, XSD.NCName, XSD.language, XSD.normalizedString, ], LENGTH_PATTERN_LRANGE: [RDF.PlainLiteral], } # a simple list containing C{all} datatypes that may have a facet facetable_datatypes = reduce(lambda x, y: x + y, list(Datatypes_per_facets.values())) ####################################################################################################### def _lit_to_value(dt, v): """ This method is used to convert a string to a value with facet checking. RDF Literals are converted to Python values using this method; if there is a problem, an exception is raised (and caught higher up to generate an error message). The method is the equivalent of all the methods in the :mod:`.DatatypeHandling` module, and is registered to the system run time, as new restricted datatypes are discovered. (Technically, the registration is done via a :code:`lambda v: _lit_to_value(self,v)` setting from within a :class:`.RestrictedDatatype` instance). :param dt: Faceted datatype. :type dt: :class:`RestrictedDatatype` :param v: Literal to be converted and checked. :raise ValueError: Invalid literal value. """ # This may raise an exception... value = dt.converter(v) # look at the different facet categories and try to find which is # is, if any, the one that is of relevant for this literal for cat in Datatypes_per_facets: if dt.base_type in Datatypes_per_facets[cat]: # yep, this is to be checked. if not dt.checkValue(value): raise ValueError( "Literal value %s does not fit the faceted datatype %s" % (v, dt) ) # got here, everything should be fine return value # noinspection PyPep8Naming,PyShadowingBuiltins def _lang_range_check(range, lang): """ Implementation of the extended filtering algorithm, as defined in point 3.3.2, of U{RFC 4647}, on matching language ranges and language tags. Needed to handle the C{rdf:PlainLiteral} datatype. @param range: language range @param lang: language tag @rtype: boolean """ def _match(r, l): """Matching of a range and language item: either range is a wildcard or the two are equal @param r: language range item @param l: language tag item @rtype: boolean """ return r == "*" or r == l rangeList = range.strip().lower().split("-") langList = lang.strip().lower().split("-") if not _match(rangeList[0], langList[0]): return False rI = 1 rL = 1 while rI < len(rangeList): if rangeList[rI] == "*": rI += 1 continue if rL >= len(langList): return False if _match(rangeList[rI], langList[rL]): rI += 1 rL += 1 continue if len(langList[rL]) == 1: return False else: rL += 1 continue return True ####################################################################################################### def extract_faceted_datatypes(core, graph: Graph): """ Extractions of restricted (i.e., faceted) datatypes from the graph. :param core: The core closure instance that is being handled. :type core: :class:`.Closure.Core` :param graph: RDFLib graph. :type graph: :class:`rdflib.graph.Graph` :return: List of :class:`.RestrictedDatatype` instances. :rtype: list """ retval = [] for dtype in graph.subjects(RDF.type, RDFS.Datatype): base_type = None facets = [] try: base_types = [x for x in graph.objects(dtype, OWL.onDatatype)] if len(base_types) > 0: if len(base_types) > 1: raise Exception( "Several base datatype for the same restriction %s" % dtype ) else: base_type = base_types[0] if base_type in facetable_datatypes: rlists = [x for x in graph.objects(dtype, OWL.withRestrictions)] if len(rlists) > 1: raise Exception( "More than one facet lists for the same restriction %s" % dtype ) elif len(rlists) > 0: final_facets = [] for r in graph.items(rlists[0]): for (facet, lit) in graph.predicate_objects(r): if isinstance(lit, rdflibLiteral): # the python value of the literal should be extracted # note that this call may lead to an exception, but that is fine, # it is caught some lines below anyway... try: if ( lit.datatype is None or lit.datatype == XSD.string ): final_facets.append((facet, str(lit))) else: final_facets.append( ( facet, AltXSDToPYTHON[lit.datatype]( str(lit) ), ) ) except Exception as msg: core.add_error(msg) continue # We do have everything we need: new_datatype = RestrictedDatatype( dtype, base_type, final_facets ) retval.append(new_datatype) except Exception as msg: # import sys # print sys.exc_info() # print sys.exc_type # print sys.exc_value # print sys.exc_traceback core.add_error(msg) continue return retval # noinspection PyPep8Naming class RestrictedDatatypeCore: """ An 'abstract' superclass for datatype restrictions. The instance variables listed here are used in general, without the specificities of the concrete restricted datatype. This module defines the :class:`.RestrictedDatatype` class that corresponds to the datatypes and their restrictions defined in the OWL 2 standard. Other modules may subclass this class to define new datatypes with restrictions. :ivar type_uri: The URI for this datatype. :ivar base_type: URI of the datatype that is restricted. :ivar toPython: Function to convert a Literal of the specified type to a Python value. """ def __init__(self, type_uri, base_type): self.datatype = type_uri self.base_type = base_type self.toPython = None def checkValue(self, value): """ Check whether the (Python) value abides to the constraints defined by the current facets. :param value: The value to be checked. :rtype: bool """ raise Exception( "This class should not be used by itself, only via its subclasses!" ) # noinspection PyPep8Naming class RestrictedDatatype(RestrictedDatatypeCore): """ Implementation of a datatype with facets, ie, datatype with restrictions. :param type_uri: URI of the datatype being defined. :param base_type: URI of the base datatype, ie, the one being restricted. :param facets: List of :code:`(facetURI, value)` pairs. :ivar datatype : The URI for this datatype. :ivar base_type: URI of the datatype that is restricted. :ivar converter: Method to convert a literal of the base type to a Python value (:code:`DatatypeHandling.AltXSDToPYTHON`). :ivar minExclusive: Value for the :code`xsd:minExclusive` facet, initialized to :code:`None` and set to the right value if a facet is around. :ivar minInclusive: Value for the :code:`xsd:minInclusive` facet, initialized to :code:`None` and set to the right value if a facet is around. :ivar maxExclusive: Value for the :code:`xsd:maxExclusive` facet, initialized to :code:`None` and set to the right value if a facet is around. :ivar maxInclusive: Value for the :code:`xsd:maxInclusive` facet, initialized to :code:`None` and set to the right value if a facet is around. :ivar minLength: Value for the :code:`xsd:minLength` facet, initialized to :code:`None` and set to the right value if a facet is around. :ivar maxLength: Value for the :code:`xsd:maxLength` facet, initialized to :code:`None` and set to the right value if a facet is around. :ivar length: Value for the :code:`xsd:length` facet, initialized to :code:`None` and set to the right value if a facet is around. :ivar pattern: Array of patterns for the :code:`xsd:pattern` facet, initialized to :code:`[]` and set to the right value if a facet is around. :ivar langRange: Array of language ranges for the :code:`rdf:langRange` facet, initialized to :code:`[]` and set to the right value if a facet is around. :ivar check_methods: List of class methods that are relevant for the given :code:`base_type`. :ivar toPython: Function to convert a Literal of the specified type to a Python value. Is defined by :code:`lambda v: _lit_to_value(self, v)`, see :py:func:`._lit_to_value`. """ def __init__(self, type_uri, base_type, facets): """ @param type_uri: URI of the datatype being defined @param base_type: URI of the base datatype, ie, the one being restricted @param facets: array of C{(facetURI, value)} pairs """ RestrictedDatatypeCore.__init__(self, type_uri, base_type) if self.base_type not in AltXSDToPYTHON: raise Exception("No facet is implemented for datatype %s" % self.base_type) self.converter = AltXSDToPYTHON[self.base_type] self.minExclusive = None self.maxExclusive = None self.minInclusive = None self.maxInclusive = None self.length = None self.maxLength = None self.minLength = None self.pattern = [] self.langRange = [] for (facet, value) in facets: if facet == XSD.minInclusive and ( self.minInclusive is None or self.minInclusive < value ): self.minInclusive = value elif facet == XSD.minExclusive and ( self.minExclusive is None or self.minExclusive < value ): self.minExclusive = value elif facet == XSD.maxInclusive and ( self.maxInclusive is None or value < self.maxInclusive ): self.maxInclusive = value elif facet == XSD.maxExclusive and ( self.maxExclusive is None or value < self.maxExclusive ): self.maxExclusive = value elif facet == RDF.langRange: self.langRange.append(value) elif facet == XSD.length: self.length = value elif facet == XSD.maxLength and ( self.maxLength is None or value < self.maxLength ): self.maxLength = value elif facet == XSD.minLength and ( self.minLength is None or value > self.minLength ): self.minLength = value elif facet == XSD.pattern: self.pattern.append(re.compile(value)) # Choose the methods that are relevant for this datatype, based on the base type facet_to_method = { MIN_MAX: [ RestrictedDatatype._check_max_exclusive, RestrictedDatatype._check_min_exclusive, RestrictedDatatype._check_max_inclusive, RestrictedDatatype._check_min_inclusive, ], LENGTH: [ RestrictedDatatype._check_min_length, RestrictedDatatype._check_max_length, RestrictedDatatype._check_length, ], LENGTH_AND_PATTERN: [ RestrictedDatatype._check_min_length, RestrictedDatatype._check_max_length, RestrictedDatatype._check_length, RestrictedDatatype._check_pattern, ], LENGTH_PATTERN_LRANGE: [ RestrictedDatatype._check_min_length, RestrictedDatatype._check_max_length, RestrictedDatatype._check_length, RestrictedDatatype._check_lang_range, ], } self.check_methods = [] for cat in Datatypes_per_facets: if self.base_type in Datatypes_per_facets[cat]: self.check_methods = facet_to_method[cat] break self.toPython = lambda v: _lit_to_value(self, v) def checkValue(self, value): """ Check whether the (Python) value abides to the constraints defined by the current facets. :param value: The value to be checked. :rtype: bool """ for method in self.check_methods: if not method(self, value): return False return True def _check_min_exclusive(self, value): """ Check the (python) value against min exclusive facet. @param value: the value to be checked @rtype: boolean """ if self.minExclusive is not None: return self.minExclusive < value else: return True def _check_min_inclusive(self, value): """ Check the (python) value against min inclusive facet. @param value: the value to be checked @rtype: boolean """ if self.minInclusive is not None: return self.minInclusive <= value else: return True def _check_max_exclusive(self, value): """ Check the (python) value against max exclusive facet. @param value: the value to be checked @rtype: boolean """ if self.maxExclusive is not None: return value < self.maxExclusive else: return True def _check_max_inclusive(self, value): """ Check the (python) value against max inclusive facet. @param value: the value to be checked @rtype: boolean """ if self.maxInclusive is not None: return value <= self.maxInclusive else: return True def _check_min_length(self, value): """ Check the (python) value against minimum length facet. @param value: the value to be checked @rtype: boolean """ if isinstance(value, rdflibLiteral): val = str(value) else: val = value if self.minLength is not None: return self.minLength <= len(val) else: return True def _check_max_length(self, value): """ Check the (python) value against maximum length facet. @param value: the value to be checked @rtype: boolean """ if isinstance(value, rdflibLiteral): val = str(value) else: val = value if self.maxLength is not None: return self.maxLength >= len(val) else: return True def _check_length(self, value): """ Check the (python) value against exact length facet. @param value: the value to be checked @rtype: boolean """ if isinstance(value, rdflibLiteral): val = str(value) else: val = value if self.length is not None: return self.length == len(val) else: return True def _check_pattern(self, value): """ Check the (python) value against array of regular expressions. @param value: the value to be checked @rtype: boolean """ if isinstance(value, rdflibLiteral): val = str(value) else: val = value for p in self.pattern: if p.match(val) is None: return False return True def _check_lang_range(self, value): """ Check the (python) value against array of language ranges. @param value: the value to be checked @rtype: boolean """ if isinstance(value, rdflibLiteral): lang = value.language else: return False for r in self.langRange: if not _lang_range_check(r, lang): return False return True ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1737416745.6480517 owlrl-7.1.3/owlrl/XsdDatatypes.py0000755000000000000000000000750414743560052013754 0ustar00# -*- coding: utf-8 -*- # """ Lists of XSD datatypes and their mutual relationships **Requires**: `RDFLib`_, 4.0.0 and higher. .. _RDFLib: https://github.com/RDFLib/rdflib **License**: This software is available for use under the `W3C Software License`_. .. _W3C Software License: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 **Organization**: `World Wide Web Consortium`_ .. _World Wide Web Consortium: http://www.w3.org **Author**: `Ivan Herman`_ .. _Ivan Herman: http://www.w3.org/People/Ivan/ """ __author__ = "Ivan Herman" __contact__ = "Ivan Herman, ivan@w3.org" __license__ = "W3C® SOFTWARE NOTICE AND LICENSE, http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231" from rdflib.namespace import RDF, RDFS, XSD # The basic XSD types used everywhere; this means not the complete set of day/month types _Common_XSD_Datatypes = [ XSD.integer, XSD.decimal, XSD.nonNegativeInteger, XSD.nonPositiveInteger, XSD.negativeInteger, XSD.positiveInteger, XSD.long, XSD.int, XSD.short, XSD.byte, XSD.unsignedLong, XSD.unsignedInt, XSD.unsignedShort, XSD.unsignedByte, XSD.float, XSD.double, XSD.string, XSD.normalizedString, XSD.token, XSD.language, XSD.Name, XSD.NCName, XSD.NMTOKEN, XSD.boolean, XSD.hexBinary, XSD.base64Binary, XSD.anyURI, XSD.dateTimeStamp, XSD.dateTime, XSD.time, XSD.date, RDFS.Literal, RDF.XMLLiteral, RDF.HTML, RDF.langString, ] # RDFS Datatypes: the basic ones plus the complete set of day/month ones RDFS_Datatypes = _Common_XSD_Datatypes + [ XSD.gYearMonth, XSD.gMonthDay, XSD.gYear, XSD.gDay, XSD.gMonth, ] # OWL RL Datatypes: the basic ones plus plain literal OWL_RL_Datatypes = _Common_XSD_Datatypes + [RDF.PlainLiteral] # XSD Datatype subsumptions _Common_Datatype_Subsumptions = { XSD.dateTimeStamp: [XSD.dateTime], XSD.integer: [XSD.decimal], XSD.long: [XSD.integer, XSD.decimal], XSD.int: [XSD.long, XSD.integer, XSD.decimal], XSD.short: [ XSD.int, XSD.long, XSD.integer, XSD.decimal, ], XSD.byte: [ XSD.short, XSD.int, XSD.long, XSD.integer, XSD.decimal, ], XSD.nonNegativeInteger: [XSD.integer, XSD.decimal], XSD.positiveInteger: [ XSD.nonNegativeInteger, XSD.integer, XSD.decimal, ], XSD.unsignedLong: [ XSD.nonNegativeInteger, XSD.integer, XSD.decimal, ], XSD.unsignedInt: [ XSD.unsignedLong, XSD.nonNegativeInteger, XSD.integer, XSD.decimal, ], XSD.unsignedShort: [ XSD.unsignedInt, XSD.unsignedLong, XSD.nonNegativeInteger, XSD.integer, XSD.decimal, ], XSD.unsignedByte: [ XSD.unsignedShort, XSD.unsignedInt, XSD.unsignedLong, XSD.nonNegativeInteger, XSD.integer, XSD.decimal, ], XSD.nonPositiveInteger: [XSD.integer, XSD.decimal], XSD.negativeInteger: [ XSD.nonPositiveInteger, XSD.integer, XSD.decimal, ], XSD.normalizedString: [XSD.string], XSD.token: [XSD.normalizedString, XSD.string], XSD.language: [XSD.token, XSD.normalizedString, XSD.string], XSD.Name: [XSD.token, XSD.normalizedString, XSD.string], XSD.NCName: [ XSD.Name, XSD.token, XSD.normalizedString, XSD.string, ], XSD.NMTOKEN: [ XSD.Name, XSD.token, XSD.normalizedString, XSD.string, ], } # RDFS Datatype subsumptions: at the moment, there is no extra to XSD RDFS_Datatype_Subsumptions = _Common_Datatype_Subsumptions # OWL Datatype subsumptions: at the moment, there is no extra to XSD OWL_Datatype_Subsumptions = _Common_Datatype_Subsumptions ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1737416745.6481538 owlrl-7.1.3/owlrl/__init__.py0000755000000000000000000006412514743560052013100 0ustar00# -*- coding: utf-8 -*- # """ This module is a brute force implementation of the 'finite' version of `RDFS semantics`_ and of `OWL 2 RL`_ on the top of RDFLib (with some caveats, see below). Some extensions to these are also implemented. .. _RDFS semantics: http://www.w3.org/TR/rdf-mt/ .. _OWL 2 RL: http://www.w3.org/TR/owl2-profiles/#Reasoning_in_OWL_2_RL_and_RDF_Graphs_using_Rules Brute force means that, in all cases, simple forward chaining rules are used to extend (recursively) the incoming graph with all triples that the rule sets permit (ie, the "deductive closure" of the graph is computed). There is an extra options whether the axiomatic triples are added to the graph (prior to the forward chaining step). These, typically set the domain and range for properties or define some core classes. In the case of RDFS, the implementation uses a 'finite' version of the axiomatic triples only (as proposed, for example, by Herman ter Horst). This means that it adds only those :code:`rdf:_i` type predicates that do appear in the original graph, thereby keeping this step finite. For OWL 2 RL, OWL 2 does not define axiomatic triples formally; but they can be deduced from the `OWL 2 RDF Based Semantics`_ document and are listed in Appendix 6 (though informally). .. _OWL 2 RDF Based Semantics: http://www.w3.org/TR/owl2-rdf-based-semantics/ .. note:: This implementation adds only those triples that refer to OWL terms that are meaningful for the OWL 2 RL case. Package Entry Points ==================== The main entry point to the package is via the :class:`.DeductiveClosure` class. This class should be initialized to control the parameters of the deductive closure; the forward chaining is done via the L{expand} method. The simplest way to use the package from an RDFLib application is as follows:: graph = Graph() # creation of an RDFLib graph ... ... # normal RDFLib application, eg, parsing RDF data ... DeductiveClosure(OWLRL_Semantics).expand(graph) # calculate an OWL 2 RL deductive closure of graph # without axiomatic triples The first argument of the :class:`.DeductiveClosure` initialization can be replaced by other classes, providing different types of deductive closure; other arguments are also possible. For example:: DeductiveClosure(OWLRL_Extension, rdfs_closure = True, axiomatic_triples = True, datatype_axioms = True).expand(graph) This will calculate the deductive closure including RDFS and some extensions to OWL 2 RL, and with all possible axiomatic triples added to the graph (this is about the maximum the package can do…) The same instance of :class:`.DeductiveClosure` can be used for several graph expansions. In other words, the expand function does not change any state. For convenience, a second entry point to the package is provided in the form of a function called :func:`owlrl.convert_graph`, that expects a directory with various options, including a file name. The function parses the file, creates the expanded graph, and serializes the result into RDF/XML or Turtle. This function is particularly useful as an entry point for a CGI call (where the HTML form parameters are in a directory) and is easy to use with a command line interface. The package distribution contains an example for both. There are major closure type (ie, semantic closure possibilities); these can be controlled through the appropriate parameters of the :class:`.DeductiveClosure` class: * using the :class:`.RDFS_Semantics` class, implementing the `RDFS semantics`_. .. _RDFS semantics: http://www.w3.org/TR/rdf-mt/ * using the :class:`.OWLRL.OWLRL_Semantics` class, implementing the `OWL 2 RL`_. .. _OWL 2 RL: http://www.w3.org/TR/owl2-profiles/#Reasoning_in_OWL_2_RL_and_RDF_Graphs_using_Rules * using :class:`.CombinedClosure.RDFS_OWLRL_Semantics` class, implementing a combined semantics of `RDFS semantics`_ and `OWL 2 RL`_. .. _RDFS semantics: http://www.w3.org/TR/rdf-mt/ .. _OWL 2 RL: http://www.w3.org/TR/owl2-profiles/#Reasoning_in_OWL_2_RL_and_RDF_Graphs_using_Rules In all three cases there are other dimensions that can control the exact closure being generated: * for convenience, the so called axiomatic triples (see, eg, the `axiomatic triples in RDFS`_ are, by default, I{not} added to the graph closure to reduce the number of generated triples. These can be controlled through a separate initialization argument. .. _axiomatic triples in RDFS: http://www.w3.org/TR/rdf-mt/#rdfs_interp * similarly, the axiomatic triples for D-entailment are separated. Some Technical/implementation aspects ===================================== The core processing is done in the in the :class:`.Closure.Core` class, which is subclassed by the :class:`.RDFSClosure.RDFS_Semantics` and the :class:`.OWLRL.OWLRL_Semantics` classes (these two are then, on their turn, subclassed by the :class:`.CombinedClosure.RDFS_OWLRL_Semantics` class). The core implements the core functionality of cycling through the rules, whereas the rules themselves are defined and implemented in the subclasses. There are also methods that are executed only once either at the beginning or at the end of the full processing cycle. Adding axiomatic triples is handled separately, which allows a finer user control over these features. The OWL specification includes references to datatypes that are not in the core RDFS specification, consequently not directly implemented by RDFLib. These are added in a separate module of the package. Problems with Literals with datatypes ------------------------------------- The current distribution of RDFLib is fairly poor in handling datatypes, particularly in checking whether a lexical form of a literal is "proper" as for its declared datatype. A typical example is :: "-1234"^^xsd:nonNegativeInteger which should not be accepted as valid literal. Because the requirements of OWL 2 RL are much stricter in this respect, an alternative set of datatype handling (essentially, conversions) had to be implemented (see the :py:mod:`.XsdDatatypes` module). The :class:`.DeductiveClosure` class has an additional instance variable whether the default RDFLib conversion routines should be exchanged against the new ones. If this flag is set to True and instance creation (this is the default), then the conversion routines are set back to the originals once the expansion is complete, thereby avoiding to influence older application that may not work properly with the new set of conversion routines. If the user wants to use these alternative lexical conversions everywhere in the application, then the :py:meth:`.DeductiveClosure.use_improved_datatypes_conversions` method can be invoked. That method changes the conversion routines and, from that point on, all usage of :class:`.DeductiveClosure` instances will use the improved conversion methods without resetting them. Ie, the code structure can be something like:: DeductiveClosure().use_improved_datatypes_conversions() ... RDFLib application DeductiveClosure().expand(graph) ... The default situation can be set back using the :py:meth:`.DeductiveClosure.use_rdflib_datatypes_conversions` call. It is, however, not *required* to use these methods at all. I.e., the user can use:: DeductiveClosure(improved_datatypes=False).expand(graph) which will result in a proper graph expansion except for the datatype specific comparisons which will be incomplete. **Requires**: * `RDFLib`_, 4.0.0 and higher. .. _RDFLib: https://github.com/RDFLib/rdflib * `rdflib_jsonld`_ .. _rdflib_jsonld: https://github.com/RDFLib/rdflib-jsonld **License**: This software is available for use under the `W3C Software License`_ .. _W3C Software License: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 **Organization**: `World Wide Web Consortium`_ .. _World Wide Web Consortium: http://www.w3.org **Author**: `Ivan Herman`_ .. _Ivan Herman: http://www.w3.org/People/Ivan/ """ # Examples: LangString is disjoint from String __version__ = "7.1.2" __author__ = "Ivan Herman" __contact__ = "Ivan Herman, ivan@w3.org" __license__ = "W3C® SOFTWARE NOTICE AND LICENSE, http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231" from typing import Union # noinspection PyPackageRequirements,PyPackageRequirements,PyPackageRequirements import rdflib from rdflib import Graph, Literal from . import DatatypeHandling, Closure from .OWLRLExtras import OWLRL_Extension, OWLRL_Extension_Trimming from .OWLRL import OWLRL_Semantics from .RDFSClosure import RDFS_Semantics from .CombinedClosure import RDFS_OWLRL_Semantics from rdflib.namespace import OWL RDFXML = "xml" TURTLE = "turtle" JSON = "json" AUTO = "auto" RDFA = "rdfa" # noinspection PyShadowingBuiltins def __parse_input(iformat, inp, graph): """Parse the input into the graph, possibly checking the suffix for the format. @param iformat: input format; can be one of L{AUTO}, L{TURTLE}, or L{RDFXML}. L{AUTO} means that the suffix of the file name or URI will decide: '.ttl' means Turtle, RDF/XML otherwise. @param inp: input file; anything that RDFLib accepts in that position (URI, file name, file object). If '-', standard input is used. @param graph: the RDFLib Graph instance to parse into. """ if iformat == AUTO: if inp == "-": format = "turtle" else: if inp.endswith(".ttl") or inp.endswith(".n3"): format = "turtle" elif inp.endswith(".json") or inp.endswith(".jsonld"): format = "json-ld" elif inp.endswith(".html"): format = "rdfa1.1" else: format = "xml" elif iformat == TURTLE: format = "turtle" elif iformat == RDFA: format = "rdfa1.1" elif iformat == RDFXML: format = "xml" elif iformat == JSON: format = "json-ld" else: raise Exception("Unknown input syntax") if inp == "-": # standard input is used import sys source = sys.stdin else: source = inp graph.parse(source, format=format) def interpret_owl_imports(iformat, graph): """ Interpret the owl import statements. Essentially, recursively merge with all the objects in the owl import statement, and remove the corresponding triples from the graph. This method can be used by an application prior to expansion. It is *not* done by the :class:`.DeductiveClosure` class. :param iformat: Input format; can be one of :code:`AUTO`, :code:`TURTLE`, or :code:`RDFXML`. :code:`AUTO` means that the suffix of the file name or URI will decide: '.ttl' means Turtle, RDF/XML otherwise. :type iformat: str :param graph: The RDFLib Graph instance to parse into. :type graph: :class:`rdflib.graph.Graph` """ while True: # 1. collect the import statements: all_imports = [t for t in graph.triples((None, OWL.imports, None))] if len(all_imports) == 0: # no import statement whatsoever, we can go on... return # 2. remove all the import statements from the graph for t in all_imports: graph.remove(t) # 3. get all the imported vocabularies and import them for (s, p, uri) in all_imports: # this is not 100% kosher. The expected object for an import statement is a URI. However, # on local usage, a string would also make sense, so I do that one, too if isinstance(uri, Literal): __parse_input(iformat, str(uri), graph) else: __parse_input(iformat, uri, graph) # 4. start all over again to see if import statements have been imported def return_closure_class(owl_closure, rdfs_closure, owl_extras, trimming=False): """ Return the right semantic extension class based on three possible choices (this method is here to help potential users, the result can be fed into a :class:`DeductiveClosure` instance at initialization). :param owl_closure: Whether OWL 2 RL deductive closure should be calculated. :type owl_closure: bool :param rdfs_closure: Whether RDFS deductive closure should be calculated. In case :code:`owl_closure==True`, this parameter should also be used in the initialization of :class:`DeductiveClosure`. :type rdfs_closure: bool :param owl_extras: Whether the extra possibilities (rational datatype, etc) should be added to an OWL 2 RL deductive closure. This parameter has no effect in case :code:`owl_closure==False`. :type owl_extras: bool :param trimming: Whether extra trimming is done on the OWL RL + Extension output. :type trimming: bool :return: Deductive class reference or None. :rtype: :class:`.DeductiveClosure` or None """ if owl_closure: if owl_extras: if trimming: return OWLRL_Extension_Trimming else: return OWLRL_Extension else: if rdfs_closure: return RDFS_OWLRL_Semantics else: return OWLRL_Semantics elif rdfs_closure: return RDFS_Semantics else: return None # noinspection PyCallingNonCallable class DeductiveClosure: """ Entry point to generate the deductive closure of a graph. The exact choice deductive closure is controlled by a class reference. The important initialization parameter is the :code:`closure_class`, a Class object referring to a subclass of :class:`.Closure.Core`. Although this package includes a number of such subclasses :class:`.OWLRL_Semantics`, :class:`.RDFS_Semantics`, :class:`.RDFS_OWLRL_Semantics`, and :class:`.OWLRL_Extension`, the user can use his/her own if additional rules are implemented. Note that :code:`owl:imports` statements are *not* interpreted in this class, that has to be done beforehand on the graph that is to be expanded. :param closure_class: A closure class reference. :type closure_class: subclass of :class:`.Closure.Core` :param improved_datatypes: Whether the improved set of lexical-to-Python conversions should be used for datatype handling. See the introduction for more details. Default: True. :type improved_datatypes: bool :param rdfs_closure: Whether the RDFS closure should also be executed. Default: False. :type rdfs_closure: bool :param axiomatic_triples: Whether relevant axiomatic triples are added before chaining, except for datatype axiomatic triples. Default: False. :type axiomatic_triples: bool :param datatype_axioms: Whether further datatype axiomatic triples are added to the output. Default: false. :type datatype_axioms: bool :var improved_datatype_generic: Whether the improved set of lexical-to-Python conversions should be used for datatype handling *in general*, I.e., not only for a particular instance and not only for inference purposes. Default: False. :type improved_datatype_generic: bool """ # This is the original set of param definitions in the class definition # # @ivar rdfs_closure: Whether the RDFS closure should also be executed. Default: False. # @type rdfs_closure: boolean # @ivar axiomatic_triples: Whether relevant axiomatic triples are added before chaining, except for datatype axiomatic # triples. Default: False. # @type axiomatic_triples: boolean # @ivar datatype_axioms: Whether further datatype axiomatic triples are added to the output. Default: false. # @type datatype_axioms: boolean # @ivar closure_class: the class instance used to expand the graph # @type closure_class: L{Closure.Core} # @cvar improved_datatype_generic: Whether the improved set of lexical-to-Python conversions should be used for # datatype handling I{in general}, ie, not only for a particular instance and not only for inference purposes. # Default: False. # @type improved_datatype_generic: boolean improved_datatype_generic = False def __init__( self, closure_class, improved_datatypes=True, rdfs_closure=False, axiomatic_triples=False, datatype_axioms=False, ): # This is the original set of param definitions in the __init__ # # @param closure_class: a closure class reference. # @type closure_class: subclass of L{Closure.Core} # @param rdfs_closure: whether RDFS rules are executed or not # @type rdfs_closure: boolean # @param axiomatic_triples: Whether relevant axiomatic triples are added before chaining, except for datatype # axiomatic triples. Default: False. # @type axiomatic_triples: boolean # @param datatype_axioms: Whether further datatype axiomatic triples are added to the output. Default: false. # @type datatype_axioms: boolean # @param improved_datatypes: Whether the improved set of lexical-to-Python conversions should be used for # datatype handling. See the introduction for more details. Default: True. # @type improved_datatypes: boolean if closure_class is None: self.closure_class = None else: if not isinstance(closure_class, type): raise ValueError("The closure type argument must be a class reference") else: self.closure_class = closure_class self.axiomatic_triples = axiomatic_triples self.datatype_axioms = datatype_axioms self.rdfs_closure = rdfs_closure self.improved_datatypes = improved_datatypes def expand(self, graph: Graph, destination: Union[None, Graph] = None): """ Expand the graph using forward chaining, and with the relevant closure type. :param graph: The RDF graph. :type graph: :class:`rdflib.Graph` :param destination: The RDF graph to which the results are written. If not specified, the graph is modified in-place. :type destination: :class:`rdflib.Graph` """ if (not DeductiveClosure.improved_datatype_generic) and self.improved_datatypes: DatatypeHandling.use_Alt_lexical_conversions() if self.closure_class is not None: self.closure_class( graph, self.axiomatic_triples, self.datatype_axioms, rdfs=self.rdfs_closure, destination=destination ).closure() if (not DeductiveClosure.improved_datatype_generic) and self.improved_datatypes: DatatypeHandling.use_RDFLib_lexical_conversions() @staticmethod def use_improved_datatypes_conversions(): """ Switch the system to use the improved datatype conversion routines. """ DeductiveClosure.improved_datatype_generic = True DatatypeHandling.use_Alt_lexical_conversions() @staticmethod def use_rdflib_datatypes_conversions(): """ Switch the system to use the generic (RDFLib) datatype conversion routines """ DeductiveClosure.improved_datatype_generic = False DatatypeHandling.use_RDFLib_lexical_conversions() ############################################################################################################### # noinspection PyPep8Naming,PyBroadException,PyBroadException,PyBroadException def convert_graph(options, closureClass=None): """ Entry point for external scripts (CGI or command line) to parse an RDF file(s), possibly execute OWL and/or RDFS closures, and serialize back the result in some format. Note that this entry point can be used requiring no entailment at all; because both the input and the output format for the package can be RDF/XML or Turtle, such usage would simply mean a format conversion. If OWL 2 RL processing is required, that also means that the :code:`owl:imports` statements are interpreted. I.e., ontologies can be spread over several files. Note, however, that the output of the process would then include all imported ontologies, too. :param options: Object with specific attributes. :type options: object :param options.sources: List of uris or file names for the source data; for each one if the name ends with 'ttl', it is considered to be turtle, RDF/XML otherwise (this can be overwritten by the options.iformat, though) :type options.sources: list :param options.text: Direct Turtle encoding of a graph as a text string (useful, eg, for a CGI call using a text field). :type options.text: str :param options.owlClosure: Can be yes or no. :type options.owlClosure: bool :param options.rdfsClosure: Can be yes or no. :type options.rdfsClosure: bool :param options.owlExtras: Can be yes or no; whether the extra rules beyond OWL 2 RL are used or not. :type options.owlExtras: bool :param options.axioms: Whether relevant axiomatic triples are added before chaining (can be a boolean, or the strings "yes" or "no"). :type options.axioms: bool :param options.daxioms: Further datatype axiomatic triples are added to the output (can be a boolean, or the strings "yes" or "no"). :type options.daxioms: bool :param options.format: Output format, can be "turtle" or "rdfxml". :type options.format: str :param options.iformat: Input format, can be "turtle", "rdfa", "json", "rdfxml", or "auto". "auto" means that the suffix of the file is considered: '.ttl'. '.html', 'json' or '.jsonld' respectively with 'xml' as a fallback. :type options.iformat: str :param options.trimming: Whether the extension to OWLRL should also include trimming. :type options.trimming: bool :param closureClass: Explicit class reference. If set, this overrides the various different other options to be used as an extension. :type closureClass: TODO(edmond.chuc@csiro.au): What class is this supposed to be? """ # Original parameter definitions from old documentation. # # @param options: object with specific attributes, namely: # - options.sources: list of uris or file names for the source data; for each one if the name ends with 'ttl', it is # considered to be turtle, RDF/XML otherwise (this can be overwritten by the options.iformat, though) # - options.text: direct Turtle encoding of a graph as a text string (useful, eg, for a CGI call using a text field) # - options.owlClosure: can be yes or no # - options.rdfsClosure: can be yes or no # - options.owlExtras: can be yes or no; whether the extra rules beyond OWL 2 RL are used or not. # - options.axioms: whether relevant axiomatic triples are added before chaining (can be a boolean, or the strings # "yes" or "no") # - options.daxioms: further datatype axiomatic triples are added to the output (can be a boolean, or the strings # "yes" or "no") # - options.format: output format, can be "turtle" or "rdfxml" # - options.iformat: input format, can be "turtle", "rdfa", "json", "rdfxml", or "auto". "auto" means that the # suffix of the file is considered: '.ttl'. '.html', 'json' or '.jsonld' respectively with 'xml' as a fallback # - options.trimming: whether the extension to OWLRL should also include trimming # @param closureClass: explicit class reference. If set, this overrides the various different other options to be # used as an extension. def __check_yes_or_true(opt): return ( opt is True or opt == "yes" or opt == "Yes" or opt == "True" or opt == "true" ) import warnings warnings.filterwarnings("ignore") if len(options.sources) == 0 and ( options.text is None or len(options.text.strip()) == 0 ): raise Exception("No graph specified either via a URI or text") graph = Graph() # Just to be sure that this attribute does not create issues with older versions of the service... # the try statement should be removed, eventually... iformat = "auto" try: iformat = options.iformat except: # exception can be raised if that attribute is not used at all, true for older versions pass # similar measure with the possible usage of the 'source' options try: if options.source is not None: options.sources.append(options.source) except: # exception can be raised if that attribute is not used at all, true for newer versions pass # Get the sources first. Note that a possible error is filtered out, namely to process the same file twice. This is # done by turning the input arguments into a set... for inp in set(options.sources): __parse_input(iformat, inp, graph) # add the possible extra text (ie, the text input on the HTML page) if options.text is not None: graph.parse(data=options.text, format="n3") # Get all the options right # noinspection PyPep8Naming owlClosure = __check_yes_or_true(options.owlClosure) # noinspection PyPep8Naming rdfsClosure = __check_yes_or_true(options.rdfsClosure) # noinspection PyPep8Naming owlExtras = __check_yes_or_true(options.owlExtras) try: trimming = __check_yes_or_true(options.trimming) except: trimming = False axioms = __check_yes_or_true(options.axioms) daxioms = __check_yes_or_true(options.daxioms) if owlClosure: interpret_owl_imports(iformat, graph) # @@@@ some smarter choice should be used later to decide what the closure class is!!! That should # also control the import management. Eg, if the superclass includes OWL... if closureClass is not None: closure_class = closureClass else: closure_class = return_closure_class( owlClosure, rdfsClosure, owlExtras, trimming ) DeductiveClosure( closure_class, improved_datatypes=True, rdfs_closure=rdfsClosure, axiomatic_triples=axioms, datatype_axioms=daxioms, ).expand(graph) if options.format == "rdfxml": return graph.serialize(format="pretty-xml") elif options.format == "json": return graph.serialize(format="json-ld") else: return graph.serialize(format="turtle") ././@PaxHeader0000000000000000000000000000003200000000000010210 xustar0026 mtime=1737417466.72186 owlrl-7.1.3/pyproject.toml0000644000000000000000000000114514743561373012541 0ustar00[tool.poetry] name = "owlrl" version = "7.1.3" description = "A simple implementation of the OWL2 RL Profile, as well as a basic RDFS inference, on top of RDFLib. Based mechanical forward chaining." authors = ["Nicholas Car "] license = "W3C software and Document Notice License" readme = "README.rst" packages = [{include = "owlrl"}] [tool.poetry.dependencies] python = ">=3.8.1,<4.0" rdflib = ">=7.1.3" [tool.poetry.group.dev.dependencies] black = "24.4.2" sphinx = ">=7.1.2,<8" pytest = ">=7.1.3,<9.0.0" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" owlrl-7.1.3/PKG-INFO0000644000000000000000000000705700000000000010656 0ustar00Metadata-Version: 2.1 Name: owlrl Version: 7.1.3 Summary: A simple implementation of the OWL2 RL Profile, as well as a basic RDFS inference, on top of RDFLib. Based mechanical forward chaining. License: W3C software and Document Notice License Author: Nicholas Car Author-email: nick@kurrawong.ai Requires-Python: >=3.8.1,<4.0 Classifier: License :: Other/Proprietary License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Requires-Dist: rdflib (>=7.1.3) Description-Content-Type: text/x-rst |Original Author DOI| |PyPI badge| |OWL-RL Logo| .. |Original Author DOI| image:: https://zenodo.org/badge/9385/RDFLib/OWL-RL.svg :target: http://dx.doi.org/10.5281/zenodo.14543 .. |PyPI badge| image:: https://badge.fury.io/py/owlrl.svg :target: https://badge.fury.io/py/owlrl .. |OWL-RL Logo| image:: https://raw.githubusercontent.com/RDFLib/OWL-RL/master/OWL-RL.png :width: 250 :target: http://owl-rl.readthedocs.io/ OWL-RL ====== A simple implementation of the OWL2 RL Profile, as well as a basic RDFS inference, on top of RDFLib, based on forward chaining. This package is a Python library that also contains a couple of scripts: * `scripts/RDFConvertService`: a CGI script to invoke the library. It may have to be adapted to the local server setup. * `scripts/owlrl`: a script that can be run locally on to transform a file into RDF (on the standard output). Run the script with `-h` to get the available flags. Installation ------------ This package requires RDFLib 7.1.3 as its only dependency and it can be installed from the Python Package index in the usual way: :: pip install owlrl or :: poetry add owlrl Use --- This package can run inference according to RDFS and/or OWL-RL. For details on RDFS, see the `RDF Semantics Specification`_; for OWL 2 RL, see the `OWL 2 Profile specification`_. .. _RDF Semantics Specification: http://www.w3.org/TR/rdf11-mt/ .. _OWL 2 Profile specification: http://www.w3.org/TR/owl2-profiles/#Reasoning_in_OWL_2_RL_and_RDF_Graphs_using_Rules View the **OWL-RL documentation** online: http://owl-rl.readthedocs.io/ License ------- This software is released under the W3C© SOFTWARE NOTICE AND LICENSE. See `LICENSE.txt `_. Support & Contacts ------------------ For general "how do I..." queries, please use https://stackoverflow.com and tag your question with ``rdflib``. Existing questions: * https://stackoverflow.com/questions/tagged/rdflib If you want to contact the rdflib maintainers, please do so via: * the rdflib-dev mailing list: https://groups.google.com/group/rdflib-dev * the chat, which is available at `gitter `_ or via matrix `#RDFLib_rdflib:gitter.im `_ Development ----------- Changes ~~~~~~~ To view the changelog for this software library, see `CHANGELOG.rst `_. Release Procedure ~~~~~~~~~~~~~~~~~ * update all the version numbers * pyproject.toml * README.rst * remove the current ``dist/`` dir * build the new distribution * test the metadata rendering * test push it to PyPI * actually push it to PyPI :: rm -vf dist/* ç bsdtar -xvf dist/owlrl-*.whl -O '*/METADATA' | view - bsdtar -xvf dist/owlrl-*.tar.gz -O '*/PKG-INFO' | view - poetry publish --dry-run poetry publish -u __token__ -p