RDFClosure.Closure

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

# Source Code for Module RDFClosure.Closure

      1  #!/d/Bin/Python/python.exe
      2  # -*- coding: utf-8 -*-
      3  #
      4  """
      5  The generic superclasses for various rule based semantics and the possible extensions.
      6
      7  @requires: U{RDFLib<https://github.com/RDFLib/rdflib>}, 4.0.0 and higher
      8  @license: This software is available for use under the U{W3C Software License<http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231>}
      9  @organization: U{World Wide Web Consortium<http://www.w3.org>}
     10  @author: U{Ivan Herman<a href="http://www.w3.org/People/Ivan/">}
     11
     12  """
     13
     14  __author__  = 'Ivan Herman'
     15  __contact__ = 'Ivan Herman, ivan@w3.org'
     16  __license__ = u'W3C® SOFTWARE NOTICE AND LICENSE, http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231'
     17
     18  import rdflib
     19  from rdflib import BNode
     20  from rdflib import Literal as rdflibLiteral
     21  from rdflib import Namespace
     22
     23  # noinspection PyPep8Naming
     24  from RDFClosure.RDFS import RDFNS as ns_rdf
     25  from RDFClosure.RDFS import type
     26
     27  from RDFClosure.Literals import LiteralProxies
     28
     29  debugGlobal       = False
     30  offlineGeneration = False
     31
     32
     33  ######################################################################################################
     34  # noinspection PyMethodMayBeStatic,PyPep8Naming,PyPep8Naming
     35 -class Core :
     36          """Core of the semantics management, dealing with the RDFS and other Semantic triples. The only
     37          reason to have it in a separate class is for an easier maintainability.
     38
     39          This is a common superclass only. In the present module, it is subclassed by
     40          a L{RDFS Closure<RDFClosure.RDFSClosure.RDFS_Semantics>} class and a L{OWL RL Closure<RDFClosure.OWLRL.OWLRL_Semantics>} classes.
     41          There are some methods that are implemented in the subclasses only, ie, this class cannot be used by itself!
     42
     43          @ivar IMaxNum: maximal index of C{rdf:_i} occurrence in the graph
     44          @ivar literal_proxies: L{Literal Proxies with BNodes<RDFClosure.Literals.LiteralProxies>} for the graph
     45          @type literal_proxies: L{LiteralProxies<RDFClosure.Literals.LiteralProxies>}
     46          @ivar graph: the real graph
     47          @type graph: rdflib.Graph
     48          @ivar axioms: whether axioms should be added or not
     49          @type axioms: boolean
     50          @ivar daxioms: whether datatype axioms should be added or not
     51          @type daxioms: boolean
     52          @ivar added_triples: triples added to the graph, conceptually, during one processing cycle
     53          @type added_triples: set of triples
     54          @ivar 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
     55          @type error_messages: array of strings
     56          @ivar rdfs: whether RDFS inference is also done (used in subclassed only)
     57          @type rdfs: boolean
     58          """
     59          # noinspection PyUnusedLocal
     60 -        def __init__(self, graph, axioms, daxioms, rdfs=False):
     61                  """
     62                  @param graph: the RDF graph to be extended
     63                  @type graph: rdflib.Graph
     64                  @param axioms: whether axioms should be added or not
     65                  @type axioms: boolean
     66                  @param daxioms: whether datatype axioms should be added or not
     67                  @type daxioms: boolean
     68                  @param rdfs: whether RDFS inference is also done (used in subclassed only)
     69                  @type rdfs: boolean
     70                  """
     71                  self._debug = debugGlobal
     72
     73                  # Calculate the maximum 'n' value for the '_i' type predicates (see Horst's paper)
     74                  n      = 1
     75                  maxnum = 0
     76                  cont   = True
     77                  while cont:
     78                          cont = False
     79                          predicate = ns_rdf[("_%d" % n)]
     80                          for (s, p, o) in graph.triples((None, predicate, None)):
     81                                  # there is at least one if we got here
     82                                  maxnum = n
     83                                  n += 1
     84                                  cont = True
     85                  self.IMaxNum = maxnum
     86
     87                  self.graph   = graph
     88                  self.axioms  = axioms
     89                  self.daxioms = daxioms
     90
     91                  self.rdfs    = rdfs
     92
     93                  self.error_messages = []
     94                  self.empty_stored_triples()
     95
     96 -        def add_error(self, message):
     97                  """
     98                  Add an error message
     99                  @param message: error message
    100                  @type message: string
    101                  """
    102                  if message not in self.error_messages:
    103                          self.error_messages.append(message)
    104
    105 -        def pre_process(self):
    106                  """
    107                  Do some pre-processing step. This method before anything else in the closure. By default, this method is empty, subclasses
    108                  can add content to it by overriding it.
    109                  """
    110                  pass
    111
    112 -        def post_process(self):
    113                  """
    114                  Do some post-processing step. This method when all processing is done, but before handling possible
    115                  errors (ie, the method can add its own error messages). By default, this method is empty, subclasses
    116                  can add content to it by overriding it.
    117                  """
    118                  pass
    119
    120 -        def rules(self,t,cycle_num):
    121                  """
    122                  The core processing cycles through every tuple in the graph and dispatches it to the various methods implementing
    123                  a specific group of rules. By default, this method raises an exception; indeed, subclasses
    124                  I{must} add content to by overriding it.
    125                  @param t: one triple on which to apply the rules
    126                  @type t: tuple
    127                  @param cycle_num: which cycle are we in, starting with 1. This value is forwarded to all local rules; it is also used
    128                  locally to collect the bnodes in the graph.
    129                  """
    130                  raise Exception("This method should not be called directly; subclasses should override it")
    131
    132 -        def add_axioms(self):
    133                  """
    134                  Add axioms.
    135                  This is only a placeholder and raises an exception by default; subclasses I{must} fill this with real content
    136                  """
    137                  raise Exception("This method should not be called directly; subclasses should override it")
    138
    139 -        def add_d_axioms(self):
    140                  """
    141                  Add d axioms.
    142                  This is only a placeholder and raises an exception by default; subclasses I{must} fill this with real content
    143                  """
    144                  raise Exception("This method should not be called directly; subclasses should override it")
    145
    146 -        def one_time_rules(self):
    147                  """
    148                  This is only a placeholder; subclasses should fill this with real content. By default, it is just an empty call.
    149                  This set of rules is invoked only once and not in a cycle.
    150                  """
    151                  pass
    152
    153          # noinspection PyBroadException
    154 -        def get_literal_value(self, node):
    155                  """
    156                  Return the literal value corresponding to a Literal node. Used in error messages.
    157                  @param node: literal node
    158                  @return: the literal value itself
    159                  """
    160                  try:
    161                          return self.literal_proxies.bnode_to_lit[node].lex
    162                  except:
    163                          return "????"
    164
    165          # noinspection PyAttributeOutsideInit
    166 -        def empty_stored_triples(self):
    167                  """
    168                  Empty the internal store for triples
    169                  """
    170                  self.added_triples = set()
    171
    172 -        def flush_stored_triples(self):
    173                  """
    174                  Send the stored triples to the graph, and empty the container
    175                  """
    176                  for t in self.added_triples:
    177                          self.graph.add(t)
    178                  self.empty_stored_triples()
    179
    180 -        def store_triple(self, t):
    181                  """
    182                  In contrast to its name, this does not yet add anything to the graph itself, it just stores the tuple in an
    183                  L{internal set<Core.added_triples>}. (It is important for this to be a set: some of the rules in the various closures may
    184                  generate the same tuples several times.) Before adding the tuple to the set, the method checks whether
    185                  the tuple is in the final graph already (if yes, it is not added to the set).
    186
    187                  The set itself is emptied at the start of every processing cycle; the triples are then effectively added to the
    188                  graph at the end of such a cycle. If the set is
    189                  actually empty at that point, this means that the cycle has not added any new triple, and the full processing can stop.
    190
    191                  @param t: the triple to be added to the graph, unless it is already there
    192                  @type t: a 3-element tuple of (s,p,o)
    193                  """
    194                  (s, p, o) = t
    195                  if not(isinstance(s, rdflibLiteral) or isinstance(p, rdflibLiteral)) and t not in self.graph:
    196                          if self._debug or offlineGeneration:
    197                                  print t
    198                          self.added_triples.add(t)
    199
    200          # noinspection PyAttributeOutsideInit
    201 -        def closure(self):
    202                  """
    203                     Generate the closure the graph. This is the real 'core'.
    204
    205                     The processing rules store new triples via the L{separate method<store_triple>} which stores
    206                     them in the L{added_triples<added_triples>} array. If that array is emtpy at the end of a cycle,
    207                     it means that the whole process can be stopped.
    208
    209                     If required, the relevant axiomatic triples are added to the graph before processing in cycles. Similarly
    210                     the exchange of literals against bnodes is also done in this step (and restored after all cycles are over).
    211                  """
    212                  self.pre_process()
    213
    214                  # Handling the axiomatic triples. In general, this means adding all tuples in the list that
    215                  # forwarded, and those include RDF or RDFS. In both cases the relevant parts of the container axioms should also
    216                  # be added.
    217                  if self.axioms:
    218                          self.add_axioms()
    219
    220                  # Create the bnode proxy structure for literals
    221                  self.literal_proxies = LiteralProxies(self.graph, self)
    222
    223                  # Add the datatype axioms, if needed (note that this makes use of the literal proxies, the order of the call is important!
    224                  if self.daxioms:
    225                          self.add_d_axioms()
    226
    227                  self.flush_stored_triples()
    228
    229                  # Get first the 'one-time rules', ie, those that do not need an extra round in cycles down the line
    230                  self.one_time_rules()
    231                  self.flush_stored_triples()
    232
    233                  # Go cyclically through all rules until no change happens
    234                  new_cycle = True
    235                  cycle_num = 0
    236                  while new_cycle:
    237                          # yes, there was a change, let us go again
    238                          cycle_num += 1
    239
    240                          # DEBUG: print the cycle number out
    241                          if self._debug:
    242                                  print "----- Cycle #:%d" % cycle_num
    243
    244                          # go through all rules, and collect the replies (to see whether any change has been done)
    245                          # the new triples to be added are collected separately not to interfere with
    246                          # the current graph yet
    247                          self.empty_stored_triples()
    248
    249                          # Execute all the rules; these might fill up the added triples array
    250                          for t in self.graph:
    251                                  self.rules(t, cycle_num)
    252
    253                          # Add the tuples to the graph (if necessary, that is). If any new triple has been generated, a new cycle
    254                          # will be necessary...
    255                          new_cycle = len(self.added_triples) > 0
    256
    257                          for t in self.added_triples:
    258                                  self.graph.add(t)
    259
    260                  # All done, but we should restore the literals from their proxies
    261                  self.literal_proxies.restore()
    262
    263                  self.post_process()
    264                  self.flush_stored_triples()
    265
    266                  # Add possible error messages
    267                  if self.error_messages:
    268                          # I am not sure this is the right vocabulary to use for this purpose, but I haven't found anything!
    269                          # I could, of course, come up with my own, but I am not sure that would be kosher...
    270                          ERRNS  = Namespace("http://www.daml.org/2002/03/agents/agent-ont#")
    271                          self.graph.bind("err","http://www.daml.org/2002/03/agents/agent-ont#")
    272                          for m in self.error_messages:
    273                                  message = BNode()
    274                                  self.graph.add((message, type, ERRNS['ErrorMessage']))
    275                                  self.graph.add((message, ERRNS['error'], rdflibLiteral(m)))
    276