RDFClosure.OWLRL

Package RDFClosure :: Module OWLRL |
[hide private]
[frames] | no frames]

# Source Code for Module RDFClosure.OWLRL

      1  #!/d/Bin/Python/python.exe
      2  # -*- coding: utf-8 -*-
      3  #
      4  """
      5  This module is a C{brute force} implementation of the OWL 2 RL profile.
      6
      7  RDFLib works with 'generalized' RDF, meaning that triples with a BNode predicate are I{allowed}. This is good because, eg, some of the
      8  triples generated for RDF from an OWL 2 functional syntax might look like '[ owl:inverseOf p]', and the RL rules would then operate
      9  on such generalized triple. However, as a last, post processing steps, these triples are removed from the graph before serialization
     10  to produce 'standard' RDF (which is o.k. for RL, too, because the consequent triples are all right, generalized triples might
     11  have had a role in the deduction steps only).
     12
     13  @requires: U{RDFLib<https://github.com/RDFLib/rdflib>}, 4.0.0 and higher
     14  @license: This software is available for use under the U{W3C Software License<http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231>}
     15  @organization: U{World Wide Web Consortium<http://www.w3.org>}
     16  @author: U{Ivan Herman<a href="http://www.w3.org/People/Ivan/">}
     17
     18  """
     19
     20  __author__  = 'Ivan Herman'
     21  __contact__ = 'Ivan Herman, ivan@w3.org'
     22  __license__ = u'W3C® SOFTWARE NOTICE AND LICENSE, http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231'
     23
     24  import rdflib
     25  from rdflib import BNode
     26
     27  from RDFClosure.RDFS import Property, type
     28  from RDFClosure.RDFS import subClassOf, subPropertyOf, comment, label, domain, range
     29  from RDFClosure.RDFS import seeAlso, isDefinedBy, Datatype
     30
     31  from RDFClosure.OWL                             import *
     32  from RDFClosure.Closure                         import Core
     33  from RDFClosure.AxiomaticTriples        import OWLRL_Axiomatic_Triples, OWLRL_D_Axiomatic_Triples
     34  from RDFClosure.AxiomaticTriples        import OWLRL_Datatypes_Disjointness
     35
     36  OWLRL_Annotation_properties = [label, comment, seeAlso, isDefinedBy, deprecated, versionInfo, priorVersion, backwardCompatibleWith, incompatibleWith]
     37
     38  from .XsdDatatypes import OWL_RL_Datatypes, OWL_Datatype_Subsumptions
     39
     40  ###########################################################################################################################
     41
     42
     43  ## OWL-R Semantics class
     44  #
     45  #
     46  # As an editing help: each rule is prefixed by RULE XXXX where XXXX is the acronym given in the profile document.
     47  # This helps in referring back to the spec...
     48  # noinspection PyPep8Naming,PyPep8Naming,PyBroadException
     49 -class OWLRL_Semantics(Core):
     50          """OWL 2 RL Semantics class, ie, implementation of the OWL 2 RL closure graph.
     51
     52          Note that the module does I{not} implement the so called Datatype entailment rules, simply because the underlying RDFLib does
     53          not implement the datatypes (ie, RDFLib will not make the literal "1.00" and "1.00000" identical, although
     54          even with all the ambiguities on datatypes, this I{should} be made equal...). Also, the so-called extensional entailment rules
     55          (Section 7.3.1 in the RDF Semantics document) have not been implemented either.
     56
     57          The comments and references to the various rule follow the names as used in the U{OWL 2 RL document<http://www.w3.org/TR/owl2-profiles/#Reasoning_in_OWL_2_RL_and_RDF_Graphs_using_Rules>}.
     58          """
     59 -        def __init__(self, graph, axioms, daxioms, rdfs=None):
     60                  """
     61                  @param graph: the RDF graph to be extended
     62                  @type graph: rdflib.Graph
     63                  @param axioms: whether (non-datatype) axiomatic triples should be added or not
     64                  @type axioms: bool
     65                  @param daxioms: whether datatype axiomatic triples should be added or not
     66                  @type daxioms: bool
     67                  @param rdfs: whether RDFS inference is also done (used in subclassed only)
     68                  @type rdfs: boolean
     69                  """
     70                  Core.__init__(self, graph, axioms, daxioms, rdfs)
     71                  self.bnodes = []
     72
     73 -        def _list(self, l) :
     74                  """
     75                  Shorthand to get a list of values (ie, from an rdf:List structure) starting at a head
     76
     77                  @param l: RDFLib resource, should be the head of an rdf:List
     78                  @return: array of resources
     79                  """
     80                  return [ch for ch in self.graph.items(l)]
     81
     82 -        def _get_resource_or_literal(self, node):
     83                  if node in self.literal_proxies.bnode_to_lit:
     84                          return "'" + self.literal_proxies.bnode_to_lit[node].lex + "'"
     85                  else :
     86                          return node
     87
     88 -        def post_process(self):
     89                  """
     90                  Remove triples with bnode predicates. The Bnodes in the graph are collected in the first cycle run.
     91                  """
     92                  to_be_removed = []
     93                  for b in self.bnodes:
     94                          for t in self.graph.triples((None, b, None)):
     95                                  if t not in to_be_removed:
     96                                          to_be_removed.append(t)
     97                  for t in to_be_removed:
     98                          self.graph.remove(t)
     99
    100 -        def add_axioms(self):
    101                  """
    102                  Add axioms
    103                  """
    104                  for t in OWLRL_Axiomatic_Triples:
    105                          self.graph.add(t)
    106
    107 -        def add_d_axioms(self) :
    108                  """
    109                  Add the datatype axioms
    110                  """
    111                  for t in OWLRL_D_Axiomatic_Triples:
    112                          self.graph.add(t)
    113
    114 -        def restriction_typing_check(self, v, t) :
    115                  """Helping method to check whether a type statement is in line with a possible
    116                  restriction. This method is invoked by rule "cls-avf" before setting a type
    117                  on an allValuesFrom restriction.
    118
    119                  The method is a placeholder at this level. It is typically implemented by subclasses for
    120                  extra checks, eg, for datatype facet checks.
    121                  @param v: the resource that is to be 'typed'
    122                  @param t: the targeted type (ie, Class)
    123                  @return: boolean
    124                  """
    125                  return True
    126
    127 -        def _one_time_rules_datatypes(self):
    128                  """
    129                  Some of the rules in the rule set are axiomatic in nature, meaning that they really have to be added only
    130                  once, there is no reason to add these in a cycle. These are performed by this method that is invoked only once
    131                  at the beginning of the process.
    132
    133                  These are: cls-thing, cls-nothing1, prp-ap, dt-types1, dt-types2, dt-eq, dt-diff.
    134
    135                  Note, however, that the dt-* are executed only partially, limited by the possibilities offered by RDFLib. These may not
    136                  cover all the edge cases of OWL RL. Especially, dt-not-type has not (yet?) been implemented (I wonder whether RDFLib should not raise
    137                  exception for those anyway...
    138                  """
    139                  # noinspection PyShadowingNames
    140                  def _add_to_explicit(s, o):
    141                          if s not in explicit:
    142                                  explicit[s] = []
    143                          if o not in explicit[s]:
    144                                  explicit[s].append(o)
    145
    146                  # noinspection PyShadowingNames
    147                  def _append_to_explicit(s, o):
    148                          if s not in explicit:
    149                                  explicit[s] = []
    150                          for d in explicit[o]:
    151                                  if d not in explicit[s]:
    152                                          explicit[s].append(d)
    153
    154                  def _add_to_used_datatypes(d):
    155                          used_datatypes.add(d)
    156
    157                  # noinspection PyShadowingNames
    158                  def _handle_subsumptions(r, dt):
    159                          if dt in OWL_Datatype_Subsumptions:
    160                                  for new_dt in OWL_Datatype_Subsumptions[dt]:
    161                                          self.store_triple((r, type, new_dt))
    162                                          self.store_triple((new_dt, type, Datatype))
    163                                          _add_to_used_datatypes(new_dt)
    164
    165
    166                  # For processing later:
    167                  # implicit object->datatype relationships: these come from real literals which are represented by
    168                  # an internal bnode
    169                  implicit = {}
    170
    171                  # explicit object->datatype relationships: those that came from an object being typed as a datatype
    172                  # or a sameAs. The values are arrays of datatypes to which the resource belong
    173                  explicit = {}
    174
    175                  # datatypes in use by the graph (directly or indirectly). This will be used at the end to add the
    176                  # necessary disjointness statements (but not more
    177                  used_datatypes = set()
    178
    179                  # the real literals from the original graph:
    180                  # literals = self.literal_proxies.lit_to_bnode.keys()
    181
    182                  # RULE dt-type2: for all explicit literals the corresponding bnode should get the right type
    183                  # definition. The 'implicit' dictionary is also filled on the fly
    184                  # RULE dt-not-type: see whether an explicit literal is valid in terms of the defined datatype
    185                  for lt in self.literal_proxies.lit_to_bnode.keys():
    186                          # note that all non-RL datatypes are ignored
    187                          if lt.dt is not None and lt.dt in OWL_RL_Datatypes:
    188                                  bn = self.literal_proxies.lit_to_bnode[lt]
    189                                  # add the explicit typing triple
    190                                  self.store_triple((bn, type, lt.dt))
    191                                  if bn not in implicit:
    192                                          implicit[bn] = lt.dt
    193                                  _add_to_used_datatypes(lt.dt)
    194
    195                                  # for dt-not-type
    196                                  # This is a dirty trick: rdflib's Literal includes a method that raises an exception if the
    197                                  # lexical value cannot be mapped on the value space.
    198                                  try :
    199                                          val = lt.lit.toPython()
    200                                  except:
    201                                          self.add_error("Literal's lexical value and datatype do not match: (%s,%s)" % (lt.lex, lt.dt))
    202
    203                  # RULE dt-diff
    204                  # RULE dt-eq
    205                  # Try to compare literals whether they are different or not. If they are different, then an explicit
    206                  # different from statement should be added, if they are identical, then an equality should be added
    207                  for lt1 in self.literal_proxies.lit_to_bnode.keys():
    208                          for lt2 in self.literal_proxies.lit_to_bnode.keys() :
    209                                  if lt1 != lt2:
    210                                          try :
    211                                                  lt1_d = lt1.lit.toPython()
    212                                                  lt2_d = lt2.lit.toPython()
    213                                                  #if lt1_d != lt2_d :
    214                                                  #       self.store_triple((self.literal_proxies.lit_to_bnode[lt1], differentFrom, self.literal_proxies.lit_to_bnode[lt2]))
    215                                                  #else :
    216                                                  #       self.store_triple((self.literal_proxies.lit_to_bnode[lt1], sameAs, self.literal_proxies.lit_to_bnode[lt2]))
    217                                          except:
    218                                                  # there may be a problem with one of the python conversion, but that should have been taken
    219                                                  # care of already
    220                                                  pass
    221
    222                  # Other datatype definitions can come from explicitly defining some nodes as datatypes (though rarely used,
    223                  # it is perfectly possible...
    224                  # there may be explicit relationships set in the graph, too!
    225                  for (s,p,o) in self.graph.triples((None, type, None)):
    226                          if o in OWL_RL_Datatypes:
    227                                  _add_to_used_datatypes(o)
    228                                  if s not in implicit:
    229                                          _add_to_explicit(s, o)
    230
    231                  # Finally, there may be sameAs statements that bind nodes to some of the existing ones. This does not introduce
    232                  # new datatypes, so the used_datatypes array does not get extended
    233                  for (s,p,o) in self.graph.triples((None, sameAs, None)):
    234                          if o in implicit:
    235                                  _add_to_explicit(s, implicit[o])
    236                          # note that s in implicit would mean that the original graph has
    237                          # a literal in subject position which is not allowed at the moment, so I do not bother
    238                          if o in explicit :
    239                                  _append_to_explicit(s, o)
    240                          if s in explicit :
    241                                  _append_to_explicit(o, s)
    242
    243                  # what we have now:
    244                  # explicit+implicit contains all the resources of type datatype;
    245                  # implicit contains those that are given by an explicit literal
    246                  # explicit contains those that are given by general resources, and there can be a whole array for each entry
    247
    248                  # RULE dt-type1: add a Datatype typing for all those
    249                  # Note: the strict interpretation of OWL RL is to do that for all allowed datatypes, but this is
    250                  # under discussion right now. The optimized version uses only what is really in use
    251                  for dt in OWL_RL_Datatypes:
    252                          self.store_triple((dt,type,Datatype))
    253                  for dts in explicit.values():
    254                          for dt in dts:
    255                                  self.store_triple((dt, type, Datatype))
    256
    257                  # Datatype reasoning means that certain datatypes are subtypes of one another.
    258                  # I could simply generate the extra subclass relationships into the graph and let the generic
    259                  # process work its way, but it seems to be an overkill. Instead, I prefer to add the explicit typing
    260                  # into the graph 'manually'
    261                  for r in explicit:
    262                          # these are the datatypes that this resource has
    263                          dtypes = explicit[r]
    264                          for dt in dtypes:
    265                                  _handle_subsumptions(r, dt)
    266
    267                  for r in implicit:
    268                          dt = implicit[r]
    269                          _handle_subsumptions(r, dt)
    270
    271                  # Last step: add the datatype disjointness relationships. This is done only for
    272                  #  - 'top' level datatypes
    273                  #  - used in the graph
    274                  for t in OWLRL_Datatypes_Disjointness:
    275                          (l, pred, r) = t
    276                          if l in used_datatypes and r in used_datatypes:
    277                                  self.store_triple(t)
    278
    279 -        def _one_time_rules_misc(self):
    280                  """
    281                  Rules executed: cls-thing, cls-nothing, prp-ap.
    282                  """
    283                  # RULE cls-thing
    284                  self.store_triple((Thing, type, OWLClass))
    285
    286                  # RULE cls-nothing
    287                  self.store_triple((Nothing, type, OWLClass))
    288
    289                  # RULE prp-ap
    290                  for an in OWLRL_Annotation_properties:
    291                          self.store_triple((an, type, AnnotationProperty))
    292
    293 -        def one_time_rules(self):
    294                  """
    295                  Some of the rules in the rule set are axiomatic in nature, meaning that they really have to be added only
    296                  once, there is no reason to add these in a cycle. These are performed by this method that is invoked only once
    297                  at the beginning of the process.
    298
    299                  These are: cls-thing, cls-nothing1, prp-ap, dt-types1, dt-types2, dt-eq, dt-diff.
    300                  """
    301                  self._one_time_rules_misc()
    302                  self._one_time_rules_datatypes()
    303
    304 -        def rules(self, t, cycle_num):
    305                  """
    306                  Go through the various rule groups, as defined in the OWL-RL profile text and implemented via
    307                  local methods. (The calling cycle takes every tuple in the graph.)
    308                  @param t: a triple (in the form of a tuple)
    309                  @param cycle_num: which cycle are we in, starting with 1. This value is forwarded to all local rules; it is also used
    310                  locally to collect the bnodes in the graph.
    311                  """
    312                  if cycle_num == 1:
    313                          for r in t:
    314                                  if isinstance(r, BNode) and r not in self.bnodes:
    315                                          self.bnodes.append(r)
    316
    317                  self._properties(t,cycle_num)
    318                  self._equality(t,cycle_num)
    319                  self._classes(t,cycle_num)
    320                  self._class_axioms(t,cycle_num)
    321                  self._schema_vocabulary(t,cycle_num)
    322
    323 -        def _property_chain(self, p, x):
    324                  """
    325                  Implementation of the property chain axiom, invoked from inside the property axiom handler. This is the
    326                  implementation of rule prp-spo2, taken aside for an easier readability of the code. """
    327                  chain = self._list(x)
    328                  # The complication is that, at each step of the chain, there may be spawns, leading to a multitude
    329                  # of 'sub' chains:-(
    330                  if len(chain) > 0:
    331                          for (u1, _y, _z) in self.graph.triples((None, chain[0], None)):
    332                                  # At least the chain can be started, because the leftmost property has at least
    333                                  # one element in its extension
    334                                  finalList   = [(u1, _z)]
    335                                  chainExists = True
    336                                  for pi in chain[1:]:
    337                                          newList = []
    338                                          for (_u,ui) in finalList:
    339                                                  for u in self.graph.objects(ui,pi):
    340                                                          # what is stored is only last entry with u1, the intermediate results
    341                                                          # are not of interest
    342                                                          newList.append((u1, u))
    343                                          # I have now, in new list, all the intermediate results
    344                                          # until pi
    345                                          # if new list is empty, that means that is a blind alley
    346                                          if len(newList) == 0:
    347                                                  chainExists = False
    348                                                  break
    349                                          else :
    350                                                  finalList = newList
    351                                  if chainExists:
    352                                          for (_u, un) in finalList :
    353                                                  self.store_triple((u1, p, un))
    354
    355 -        def _equality(self, triple, cycle_num):
    356                  """
    357                  Table 4: Semantics of equality. Essentially, the eq-* rules.
    358                  @param triple: triple to work on
    359                  @param cycle_num: which cycle are we in, starting with 1. Can be used for some optimization.
    360                  """
    361                  # In many of the 'if' branches, corresponding to rules in the document,
    362                  # the branch begins by a renaming of variables (eg, pp,c = s,o).
    363                  # There is no programming reasons for doing that, but by renaming the
    364                  # variables it becomes easier to compare the declarative rules
    365                  # in the document with the implementation
    366                  s,p,o = triple
    367                  # RULE eq-ref
    368                  self.store_triple((s, sameAs, s))
    369                  self.store_triple((o, sameAs, o))
    370                  self.store_triple((p, sameAs, p))
    371                  if p == sameAs:
    372                          x, y = s, o
    373                          # RULE eq-sym
    374                          self.store_triple((y, sameAs, x))
    375                          # RULE eq-trans
    376                          for z in self.graph.objects(y, sameAs):
    377                                  self.store_triple((x, sameAs, z))
    378                          # RULE eq-rep-s
    379                          for pp,oo in self.graph.predicate_objects(s):
    380                                  self.store_triple((o, pp, oo))
    381                          # RULE eq-rep-p
    382                          for ss,oo in self.graph.subject_objects(s):
    383                                  self.store_triple((ss, o, oo))
    384                          # RULE eq-rep-o
    385                          for ss,pp in self.graph.subject_predicates(o):
    386                                  self.store_triple((ss, pp, s))
    387                          # RULE eq-diff1
    388                          if (s,differentFrom,o) in self.graph or (o,differentFrom,s) in self.graph:
    389                                  s_e = self._get_resource_or_literal(s)
    390                                  o_e = self._get_resource_or_literal(o)
    391                                  self.add_error("'sameAs' and 'differentFrom' cannot be used on the same subject-object pair: (%s, %s)" % (s_e, o_e))
    392
    393                  # RULES eq-diff2 and eq-diff3
    394                  if p == type and o == AllDifferent:
    395                          x = s
    396                          # the objects method are generators, we cannot simply concatenate them. So we turn the results
    397                          # into lists first. (Otherwise the body of the for loops should be repeated verbatim, which
    398                          # is silly and error prone...
    399                          m1 = [i for i in self.graph.objects(x, members)]
    400                          m2 = [i for i in self.graph.objects(x, distinctMembers)]
    401                          for y in m1 + m2:
    402                                  zis = self._list(y)
    403                                  for i in xrange(0, len(zis) - 1):
    404                                          zi = zis[i]
    405                                          for j in xrange(i+1, len(zis) - 1):
    406                                                  zj = zis[j]
    407                                                  if ((zi, sameAs, zj) in self.graph or (zj, sameAs, zi) in self.graph) and zi != zj:
    408                                                          self.add_error("'sameAs' and 'AllDifferent' cannot be used on the same subject-object pair: (%s, %s)" % (zi,zj))
    409
    410 -        def _properties(self, triple, cycle_num):
    411                  """
    412                  Table 5: The Semantics of Axioms about Properties. Essentially, the prp-* rules.
    413                  @param triple: triple to work on
    414                  @param cycle_num: which cycle are we in, starting with 1. Can be used for some optimization.
    415                  """
    416                  # In many of the 'if' branches, corresponding to rules in the document,
    417                  # the branch begins by a renaming of variables (eg, pp,c = s,o).
    418                  # There is no programming reasons for doing that, but by renaming the
    419                  # variables it becomes easier to compare the declarative rules
    420                  # in the document with the implementation
    421                  p, t, o = triple
    422
    423                  # RULE prp-ap
    424                  if cycle_num == 1 and t in OWLRL_Annotation_properties:
    425                          self.store_triple((t, type, AnnotationProperty))
    426
    427                  # RULE prp-dom
    428                  if t == domain:
    429                          for x, y in self.graph.subject_objects(p):
    430                                  self.store_triple((x, type, o))
    431
    432                  # RULE prp-rng
    433                  elif t == range:
    434                          for x, y in self.graph.subject_objects(p):
    435                                  self.store_triple((y, type, o))
    436
    437                  elif t == type:
    438                          # RULE prp-fp
    439                          if o == FunctionalProperty:
    440                                  # Property axiom #3
    441                                  for x, y1 in self.graph.subject_objects(p):
    442                                          for y2 in self.graph.objects(x,p):
    443                                                  # Optimization: if the two resources are identical, the samAs is already
    444                                                  # taken place somewhere else, unnecessary to add it here
    445                                                  if y1 != y2:
    446                                                          self.store_triple((y1, sameAs, y2))
    447
    448                          # RULE prp-ifp
    449                          elif o == InverseFunctionalProperty:
    450                                  for x1, y in self.graph.subject_objects(p):
    451                                          for x2 in self.graph.subjects(p,y):
    452                                                  # Optimization: if the two resources are identical, the samAs is already
    453                                                  # taken place somewhere else, unnecessary to add it here
    454                                                  if x1 != x2:
    455                                                          self.store_triple((x1, sameAs, x2))
    456
    457                          # RULE prp-irp
    458                          elif o == IrreflexiveProperty:
    459                                  for x, y in self.graph.subject_objects(p):
    460                                          if x == y:
    461                                                  self.add_error("Irreflexive property used on %s with %s" % (x,p))
    462
    463                          # RULE prp-symp
    464                          elif o == SymmetricProperty:
    465                                  for x, y in self.graph.subject_objects(p):
    466                                          self.store_triple((y, p, x))
    467
    468                          # RULE prp-asyp
    469                          elif o == AsymmetricProperty:
    470                                  for x, y in self.graph.subject_objects(p):
    471                                          if (y, p, x) in self.graph :
    472                                                  self.add_error("Erroneous usage of asymmetric property %s on %s and %s" % (p, x, y))
    473
    474                          # RULE prp-trp
    475                          elif o == TransitiveProperty:
    476                                  for x, y in self.graph.subject_objects(p):
    477                                          for z in self.graph.objects(y, p):
    478                                                  self.store_triple((x, p, z))
    479
    480                          #
    481                          # Breaking the order here, I take some additional rules here to the branch checking the type...
    482                          #
    483                          # RULE prp-adp
    484                          elif o == AllDisjointProperties:
    485                                  x = p
    486                                  for y in self.graph.objects(x, members):
    487                                          pis = self._list(y)
    488                                          for i in xrange(0,len(pis) - 1):
    489                                                  pi = pis[i]
    490                                                  for j in xrange(i+1,len(pis) - 1):
    491                                                          pj = pis[j]
    492                                                          for x,y in self.graph.subject_objects(pi):
    493                                                                  if (x, pj, y) in self.graph :
    494                                                                          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))
    495
    496                  # RULE prp-spo1
    497                  elif t == subPropertyOf:
    498                          p1, p2 = p, o
    499                          for x, y in self.graph.subject_objects(p1):
    500                                  self.store_triple((x, p2, y))
    501
    502                  # RULE prp-spo2
    503                  elif t == propertyChainAxiom:
    504                          self._property_chain(p, o)
    505
    506                  # RULES prp-eqp1 and prp-eqp2
    507                  elif t == equivalentProperty:
    508                          p1, p2 = p, o
    509                          # Optimization: it clearly does not make sense to run these
    510                          # if the two properties are identical (a separate axiom
    511                          # does create an equivalent property relations among identical
    512                          # properties, too...)
    513                          if p1 != p2:
    514                                  # RULE prp-eqp1
    515                                  for x, y in self.graph.subject_objects(p1):
    516                                          self.store_triple((x, p2, y))
    517                                  # RULE prp-eqp2
    518                                  for x, y in self.graph.subject_objects(p2):
    519                                          self.store_triple((x, p1, y))
    520
    521                  # RULE prp-pdw
    522                  elif t == propertyDisjointWith:
    523                          p1, p2 = p, o
    524                          for x, y in self.graph.subject_objects(p1):
    525                                  if (x, p2, y) in self.graph:
    526                                          self.add_error("Erroneous usage of disjoint properties %s and %s on %s and %s" % (p1,p2,x,y))
    527
    528
    529                  # RULES prp-inv1 and prp-inv2
    530                  elif t == inverseOf:
    531                          p1, p2 = p, o
    532                          # RULE prp-inv1
    533                          for x, y in self.graph.subject_objects(p1):
    534                                  self.store_triple((y, p2, x))
    535                          # RULE prp-inv2
    536                          for x, y in self.graph.subject_objects(p2):
    537                                  self.store_triple((y, p1, x))
    538
    539                  # RULE prp-key
    540                  elif t == hasKey :
    541                          c, u = p, o
    542                          pis = self._list(u)
    543                          if len(pis) > 0 :
    544                                  for x in self.graph.subjects(type, c):
    545                                          # "Calculate" the keys for 'x'. The complication is that there can be various combinations
    546                                          # of the keys, and that is the structure one has to build up here...
    547                                          #
    548                                          # The final list will be a list of lists, with each constituents being the possible combinations of the
    549                                          # key values.
    550                                          # startup the list
    551                                          finalList = [[zi] for zi in self.graph.objects(x, pis[0])]
    552                                          for pi in pis[1:]:
    553                                                  newList = []
    554                                                  for zi in self.graph.objects(x, pi):
    555                                                          newList = newList + [l + [zi] for l in finalList]
    556                                                  finalList = newList
    557
    558                                          # I am not sure this can happen, but better safe then sorry... ruling out
    559                                          # the value lists whose length are not kosher
    560                                          # (To be checked whether this is necessary in the first place)
    561                                          valueList = [l for l in finalList if len(l) == len(pis)]
    562
    563                                          # Now we can look for the y-s, to see if they have the same key values
    564                                          for y in self.graph.subjects(type, c):
    565                                                  # rule out the existing equivalences
    566                                                  if not(y == x or (y, sameAs, x) in self.graph or (x, sameAs, y) in self.graph) :
    567                                                          # 'calculate' the keys for the y values and see if there is a match
    568                                                          for vals in valueList:
    569                                                                  same = True
    570                                                                  for i in xrange(0,len(pis) - 1):
    571                                                                          if (y, pis[i], vals[i]) not in self.graph :
    572                                                                                  same = False
    573                                                                                  # No use going with this property line
    574                                                                                  break
    575                                                                  if same :
    576                                                                          self.store_triple((x, sameAs, y))
    577                                                                          # Look for the next 'y', this branch is finished, no reason to continue
    578                                                                          break
    579
    580                  # RULES prp-npa1 and prp-npa2
    581                  elif t == sourceIndividual:
    582                          x, i1 = p, o
    583                          for p1 in self.graph.objects(x, assertionProperty):
    584                                  for i2 in self.graph.objects(x, targetIndividual):
    585                                          if (i1, p1, i2) in self.graph :
    586                                                  self.add_error("Negative (object) property assertion violated for: (%s, %s, %s)" % (i1, p1, i2))
    587                                  for i2 in self.graph.objects(x,targetValue) :
    588                                          if (i1, p1, i2) in self.graph :
    589                                                  self.add_error("Negative (datatype) property assertion violated for: (%s, %s, %s)" % (i1, p1, self.get_literal_value(i2)))
    590
    591 -        def _classes(self, triple, cycle_num) :
    592                  """
    593                  Table 6: The Semantics of Classes. Essentially, the cls-* rules
    594                  @param triple: triple to work on
    595                  @param cycle_num: which cycle are we in, starting with 1. Can be used for some optimization.
    596                  """
    597                  # In many of the 'if' branches, corresponding to rules in the document,
    598                  # the branch begins by a renaming of variables (eg, pp,c = s,o).
    599                  # There is no programming reasons for doing that, but by renaming the
    600                  # variables it becomes easier to compare the declarative rules
    601                  # in the document with the implementation
    602                  c, p, x = triple
    603
    604                  # RULE cls-nothing2
    605                  if p == type and x == Nothing :
    606                          self.add_error("%s is defined of type 'Nothing'" % c)
    607
    608                  # RULES cls-int1 and cls-int2
    609                  if p == intersectionOf:
    610                          classes = self._list(x)
    611                          # RULE cls-int1
    612                          # Optimization: by looking at the members of class[0] right away one
    613                          # reduces the search spaces a bit. Individuals not in that class
    614                          # are without interest anyway
    615                          # I am not sure how empty lists are sanctioned, so having an extra check
    616                          # on that does not hurt..
    617                          if len(classes) > 0 :
    618                                  for y in self.graph.subjects(type, classes[0]):
    619                                          if False not in [(y, type, cl) in self.graph for cl in classes[1:]] :
    620                                                  self.store_triple((y, type, c))
    621                          # RULE cls-int2
    622                          for y in self.graph.subjects(type, c):
    623                                  for cl in classes:
    624                                          self.store_triple((y, type, cl))
    625
    626                  # RULE cls-uni
    627                  elif p == unionOf:
    628                          for cl in self._list(x):
    629                                  for y in self.graph.subjects(type, cl):
    630                                          self.store_triple((y, type, c))
    631
    632                  # RULE cls-comm
    633                  elif p == complementOf:
    634                          c1, c2 = c, x
    635                          for x1 in self.graph.subjects(type, c1):
    636                                  if (x1, type, c2) in self.graph :
    637                                          self.add_error("Violation of complementarity for classes %s and %s on element %s" % (c1, c2, x))
    638
    639                  # RULES cls-svf1 and cls=svf2
    640                  elif p == someValuesFrom:
    641                          xx, y = c, x
    642                          # RULE cls-svf1
    643                          # RULE cls-svf2
    644                          for pp in self.graph.objects(xx, onProperty):
    645                                  for u, v in self.graph.subject_objects(pp) :
    646                                          if y == Thing or (v, type, y) in self.graph:
    647                                                  self.store_triple((u, type, xx))
    648
    649                  # RULE cls-avf
    650                  elif p == allValuesFrom:
    651                          xx, y = c, x
    652                          for pp in self.graph.objects(xx, onProperty):
    653                                  for u in self.graph.subjects(type, xx):
    654                                          for v in self.graph.objects(u, pp) :
    655                                                  if self.restriction_typing_check(v, y):
    656                                                          self.store_triple((v, type, y))
    657                                                  else :
    658                                                          self.add_error("Violation of type restriction for allValuesFrom in %s for datatype %s on value %s" % (pp, y, self._get_resource_or_literal(v)))
    659
    660                  # RULES cls-hv1 and cls-hv2
    661                  elif p == hasValue:
    662                          xx, y = c, x
    663                          for pp in self.graph.objects(xx, onProperty):
    664                                  # RULE cls-hv1
    665                                  for u in self.graph.subjects(type, xx):
    666                                          self.store_triple((u, pp, y))
    667                                  # RULE cls-hv2
    668                                  for u in self.graph.subjects(pp, y):
    669                                          self.store_triple((u, type, xx))
    670
    671                  # RULES cls-maxc1 and cls-maxc1
    672                  elif p == maxCardinality:
    673                          # This one is a bit complicated, because the literals have been
    674                          # exchanged against bnodes...
    675                          #
    676                          # The construct should lead to an integer. Something may go wrong along the line
    677                          # leading to an exception...
    678                          val = -1
    679                          try:
    680                                  val = int(self.literal_proxies.bnode_to_lit[x].lit)
    681                          except:
    682                                  pass
    683                          xx = c
    684                          if val == 0:
    685                                  # RULE cls-maxc1
    686                                  for pp in self.graph.objects(xx, onProperty):
    687                                          for u,y in self.graph.subject_objects(pp):
    688                                                  # This should not occur:
    689                                                  if (u, type, xx) in self.graph:
    690                                                          self.add_error("Erroneous usage of maximum cardinality with %s, %s" % (xx, y))
    691                          elif val == 1:
    692                                  # RULE cls-maxc2
    693                                  for pp in self.graph.objects(xx, onProperty):
    694                                          for u, y1 in self.graph.subject_objects(pp):
    695                                                  if (u, type, xx) in self.graph:
    696                                                          for y2 in self.graph.objects(u, pp):
    697                                                                  if y1 != y2 :
    698                                                                          self.store_triple((y1, sameAs, y2))
    699
    700                  # RULES cls-maxqc1, cls-maxqc2, cls-maxqc3, cls-maxqc4
    701                  elif p == maxQualifiedCardinality:
    702                          # This one is a bit complicated, because the literals have been
    703                          # exchanged against bnodes...
    704                          #
    705                          # The construct should lead to an integer. Something may go wrong along the line
    706                          # leading to an exception...
    707                          val = -1
    708                          try :
    709                                  val = int(self.literal_proxies.bnode_to_lit[x].lit)
    710                          except:
    711                                  pass
    712                          xx = c
    713                          if val == 0 :
    714                                  # RULES cls-maxqc1 and cls-maxqc2 folded in one
    715                                  for pp in self.graph.objects(xx, onProperty):
    716                                          for cc in self.graph.objects(xx, onClass):
    717                                                  for u,y in self.graph.subject_objects(pp):
    718                                                          # This should not occur:
    719                                                          if (u, type, xx) in self.graph and (cc == Thing or (y, type, cc) in self.graph):
    720                                                                  self.add_error("Erroneous usage of maximum qualified cardinality with %s, %s, and %s" % (xx, cc, y))
    721                          elif val == 1 :
    722                                  # RULE cls-maxqc3 and cls-maxqc4 folded in one
    723                                  for pp in self.graph.objects(xx, onProperty):
    724                                          for cc in self.graph.objects(xx, onClass) :
    725                                                  for u, y1 in self.graph.subject_objects(pp):
    726                                                          if (u, type, xx) in self.graph :
    727                                                                  if cc == Thing:
    728                                                                          for y2 in self.graph.objects(u, pp):
    729                                                                                  if y1 != y2:
    730                                                                                          self.store_triple((y1, sameAs, y2))
    731                                                                  else :
    732                                                                          if (y1, type, cc) in self.graph:
    733                                                                                  for y2 in self.graph.objects(u, pp) :
    734                                                                                          if y1 != y2 and (y2, type, cc) in self.graph:
    735                                                                                                  self.store_triple((y1, sameAs, y2))
    736
    737                  # RULE cls-oo
    738                  elif p == oneOf:
    739                          for y in self._list(x):
    740                                  self.store_triple((y, type, c))
    741
    742 -        def _class_axioms(self, triple, cycle_num):
    743                  """
    744                  Table 7: Class Axioms. Essentially, the cax-* rules.
    745                  @param triple: triple to work on
    746                  @param cycle_num: which cycle are we in, starting with 1. Can be used for some optimization.
    747                  """
    748                  # In many of the 'if' branches, corresponding to rules in the document,
    749                  # the branch begins by a renaming of variables (eg, pp,c = s,o).
    750                  # There is no programming reasons for doing that, but by renaming the
    751                  # variables it becomes easier to compare the declarative rules
    752                  # in the document with the implementation
    753                  c1,p,c2 = triple
    754                  # RULE cax-sco
    755                  if p == subClassOf:
    756                          # Other axioms sets classes to be subclasses of themselves, to one can optimize the trivial case
    757                          if c1 != c2 :
    758                                  for x in self.graph.subjects(type, c1):
    759                                          self.store_triple((x, type, c2))
    760
    761                  # RULES cax-eqc1 and cax-eqc1
    762                  # Other axioms set classes to be equivalent to themselves, one can optimize the trivial case
    763                  elif p == equivalentClass and c1 != c2:
    764                          # RULE cax-eqc1
    765                          for x in self.graph.subjects(type, c1):
    766                                  self.store_triple((x, type, c2))
    767                          # RULE cax-eqc1
    768                          for x in self.graph.subjects(type, c2):
    769                                  self.store_triple((x, type, c1))
    770
    771                  # RULE cax-dw
    772                  elif p == disjointWith:
    773                          for x in self.graph.subjects(type, c1):
    774                                  if (x, type, c2) in self.graph:
    775                                          self.add_error("Disjoint classes %s and %s have a common individual %s" % (c1, c2, self._get_resource_or_literal(x)))
    776
    777                  # RULE cax-adc
    778                  elif p == type and c2 == AllDisjointClasses:
    779                          x = c1
    780                          for y in self.graph.objects(x, members):
    781                                  classes = self._list(y)
    782                                  if len(classes) > 0:
    783                                          for i in xrange(0, len(classes) - 1):
    784                                                  cl1 = classes[i]
    785                                                  for z in self.graph.subjects(type,cl1):
    786                                                          for cl2 in classes[(i + 1):]:
    787                                                                  if (z, type, cl2) in self.graph:
    788                                                                          self.add_error("Disjoint classes %s and %s have a common individual %s" % (cl1, cl2, z))
    789
    790 -        def _schema_vocabulary(self, triple, cycle_num):
    791                  """
    792                  Table 9: The Semantics of Schema Vocabulary. Essentially, the scm-* rules
    793                  @param triple: triple to work on
    794                  @param cycle_num: which cycle are we in, starting with 1. Can be used for some optimization.
    795                  """
    796                  # In many of the 'if' branches, corresponding to rules in the document,
    797                  # the branch begins by a renaming of variables (eg, pp,c = s,o).
    798                  # There is no programming reasons for doing that, but by renaming the
    799                  # variables it becomes easier to compare the declarative rules
    800                  # in the document with the implementation
    801                  s,p,o = triple
    802
    803                  # RULE scm-cls
    804                  if p == type and o == OWLClass:
    805                          c = s
    806                          self.store_triple((c, subClassOf, c))
    807                          self.store_triple((c, equivalentClass, c))
    808                          self.store_triple((c, subClassOf, Thing))
    809                          self.store_triple((Nothing, subClassOf, c))
    810
    811                  # RULE scm-sco
    812                  # Rule scm-eqc2
    813                  elif p == subClassOf:
    814                          c1, c2 = s, o
    815                          # RULE scm-sco
    816                          # Optimize out the trivial identity case (set elsewhere already)
    817                          if c1 != c2:
    818                                  for c3 in self.graph.objects(c2, subClassOf):
    819                                          # Another axiom already sets that...
    820                                          if c1 != c3 : self.store_triple((c1, subClassOf, c3))
    821                          # RULE scm-eqc2
    822                          if (c2, subClassOf, c1) in self.graph:
    823                                  self.store_triple((c1, equivalentClass, c2))
    824
    825                  # RULE scm-eqc
    826                  elif p == equivalentClass and s != o:
    827                          c1,c2 = s,o
    828                          self.store_triple((c1, subClassOf, c2))
    829                          self.store_triple((c2, subClassOf, c1))
    830
    831                  # RULE scm-op and RULE scm-dp folded together
    832                  # There is a bit of a cheating here: 'Property' is not, strictly speaking, in the rule set!
    833                  elif p == type and (o == ObjectProperty or o == DatatypeProperty or o == Property):
    834                          pp = s
    835                          self.store_triple((pp, subPropertyOf, pp))
    836                          self.store_triple((pp, equivalentProperty, pp))
    837
    838                  # RULE scm-spo
    839                  # RULE scm-eqp2
    840                  elif p == subPropertyOf and s != o:
    841                          p1, p2 = s, o
    842                          # Optimize out the trivial identity case (set elsewhere already)
    843                          # RULE scm-spo
    844                          if p1 != p2 :
    845                                  for p3 in self.graph.objects(p2,subPropertyOf):
    846                                          if p1 != p3:
    847                                                  self.store_triple((p1, subPropertyOf, p3))
    848
    849                          #RULE scm-eqp2
    850                          if (p2, subPropertyOf, p1) in self.graph:
    851                                  self.store_triple((p1, equivalentProperty, p2))
    852
    853                  # RULE scm-eqp
    854                  # Optimize out the trivial identity case (set elsewhere already)
    855                  elif p == equivalentProperty and s != o:
    856                          p1, p2 = s, o
    857                          self.store_triple((p1, subPropertyOf, p2))
    858                          self.store_triple((p2, subPropertyOf, p1))
    859
    860                  # RULES scm-dom1 and scm-dom2
    861                  elif p == domain:
    862                          # RULE scm-dom1
    863                          pp, c1 = s, o
    864                          for (_x, _y, c2) in self.graph.triples((c1, subClassOf, None)) :
    865                                  if c1 != c2 :
    866                                          self.store_triple((pp, domain, c2))
    867                          # RULE scm-dom1
    868                          p2, c = s, o
    869                          for (p1,_x,_y) in self.graph.triples((None, subPropertyOf, p2)) :
    870                                  if p1 != p2 :
    871                                          self.store_triple((p1, domain, c))
    872
    873                  # RULES scm-rng1 and scm-rng2
    874                  elif p == range:
    875                          # RULE scm-rng1
    876                          pp, c1 = s, o
    877                          for (_x,_y,c2) in self.graph.triples((c1, subClassOf, None)) :
    878                                  if c1 != c2 : self.store_triple((pp, range, c2))
    879                          # RULE scm-rng1
    880                          p2, c = s, o
    881                          for (p1,_x,_y) in self.graph.triples((None, subPropertyOf, p2)) :
    882                                  if p1 != p2 : self.store_triple((p1, range, c))
    883
    884                  # RULE scm-hv
    885                  elif p == hasValue:
    886                          c1, i = s, o
    887                          for p1 in self.graph.objects(c1, onProperty):
    888                                  for c2 in self.graph.subjects(hasValue, i):
    889                                          for p2 in self.graph.objects(c2, onProperty):
    890                                                  if (p1, subPropertyOf, p2) in self.graph:
    891                                                          self.store_triple((c1, subClassOf, c2))
    892
    893                  # RULES scm-svf1 and scm-svf2
    894                  elif p == someValuesFrom:
    895                          # RULE scm-svf1
    896                          c1, y1 = s, o
    897                          for pp in self.graph.objects(c1,onProperty):
    898                                  for c2 in self.graph.subjects(onProperty,pp):
    899                                          for y2 in self.graph.objects(c2, someValuesFrom):
    900                                                  if (y1,subClassOf, y2) in self.graph:
    901                                                          self.store_triple((c1, subClassOf, c2))
    902
    903                          # RULE scm-svf2
    904                          c1, y = s, o
    905                          for p1 in self.graph.objects(c1, onProperty):
    906                                  for c2 in self.graph.subjects(someValuesFrom,y):
    907                                          for p2 in self.graph.objects(c2, onProperty):
    908                                                  if (p1,subPropertyOf,p2) in self.graph:
    909                                                          self.store_triple((c1, subClassOf, c2))
    910
    911                  # RULES scm-avf1 and scm-avf2
    912                  elif p == allValuesFrom:
    913                          # RULE scm-avf1
    914                          c1, y1 = s, o
    915                          for pp in self.graph.objects(c1, onProperty):
    916                                  for c2 in self.graph.subjects(onProperty, pp) :
    917                                          for y2 in self.graph.objects(c2, allValuesFrom) :
    918                                                  if (y1,subClassOf,y2) in self.graph:
    919                                                          self.store_triple((c1, subClassOf, c2))
    920
    921                          # RULE scm-avf2
    922                          c1, y = s, o
    923                          for p1 in self.graph.objects(c1, onProperty):
    924                                  for c2 in self.graph.subjects(allValuesFrom, y):
    925                                          for p2 in self.graph.objects(c2, onProperty):
    926                                                  if (p1,subPropertyOf, p2) in self.graph :
    927                                                          self.store_triple((c2, subClassOf, c1))
    928
    929                  # RULE scm-int
    930                  elif p == intersectionOf:
    931                          c, x = s, o
    932                          for ci in self._list(x):
    933                                  self.store_triple((c, subClassOf, ci))
    934
    935                  # RULE scm-uni
    936                  elif p == unionOf:
    937                          c, x = s, o
    938                          for ci in self._list(x):
    939                                  self.store_triple((ci, subClassOf, c))
    940