RDFClosure.Literals

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

# Source Code for Module RDFClosure.Literals

      1  # -*- coding: utf-8 -*-
      2  #
      3  """
      4  Separate module to handle literals.
      5
      6  The issue is that pure literals cannot appear in subject position according to the current rules on RDF. That means that
      7  different types of conclusions cannot properly finish. The present trick is trying to get around the problem as follows:
      8
      9   1. For all literals in the graph a bnode is created. The module does not do a full D entailment but just relies on RDFLib's ability to recognize identical literals
     10   2. All those bnodes get a type Literal
     11   3. All triples with literals are exchanged against a triple with the associated bnode
     12
     13  The inferences are then calculated with the modified Graph. At the end of the process, the above steps are done backwards: for all triples where
     14  a bnode representing a literal appear in object position, a triple is generated; however, all triples where the bnode appears in a
     15  subject position are removed from the final graph.
     16
     17
     18  @requires: U{RDFLib<https://github.com/RDFLib/rdflib>}, 4.0.0 and higher
     19  @license: This software is available for use under the U{W3C Software License<http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231>}
     20  @organization: U{World Wide Web Consortium<http://www.w3.org>}
     21  @author: U{Ivan Herman<a href="http://www.w3.org/People/Ivan/">}
     22
     23  """
     24
     25  __author__  = 'Ivan Herman'
     26  __contact__ = 'Ivan Herman, ivan@w3.org'
     27  __license__ = u'W3C® SOFTWARE NOTICE AND LICENSE, http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231'
     28
     29  from rdflib import BNode
     30  from rdflib import Literal as rdflibLiteral
     31  from rdflib.namespace import XSD as ns_xsd
     32
     33  from .RDFS import type
     34  from .RDFS import Literal
     35  from .DatatypeHandling import AltXSDToPYTHON
     36
     37
     38 -class _LiteralStructure :
     39          """This class serves as a wrapper around rdflib's Literal, by changing the equality function to a strict
     40          identity of datatypes and lexical values.
     41
     42          On the other hand, to implement, eg, OWL RL's datatype rules, a should be able to generate
     43          an 'a sameAs b' triple, ie, the distinction should be kept. Hence this class that overrides the equality,
     44          and then can be used as a key in a Python dictionary.
     45          """
     46
     47          # noinspection PyPep8
     48 -        def __init__(self, lit) :
     49                  self.lit   = lit
     50                  self.lex   = str(lit)
     51                  self.dt    = lit.datatype
     52                  self.lang  = lit.language
     53                  self.value = lit.value
     54
     55  #       def compare_value(self, other ) :
     56  #               """Compare to literal structure instances for equality. Here equality means in the sense of datatype values
     57  #               @return: comparison result
     58  #               @rtype: boolean
     59  #               """
     60  #               try:
     61  #                       if self.dt == OWLNS["rational"] and other.dt == OWLNS["rational"] :
     62  #                               return self.value == other.value
     63  #                       elif self.dt == OWLNS["rational"] and other.dt != OWLNS["rational"] :
     64  #                               l = rdflibLiteral(float(self.lit._cmp_value))
     65  #                               return l == other.lit
     66  #                       elif self.dt != OWLNS["rational"] and other.dt == OWLNS["rational"] :
     67  #                               l = rdflibLiteral(float(other.lit._cmp_value))
     68  #                               return self.lit == l
     69  #                       else :
     70  #                               return self.lit == other.lit
     71  #               except:
     72  #                       # There might be conversion problems...
     73  #                       return False
     74
     75          # noinspection PyBroadException
     76 -        def compare_value(self, other) :
     77                  """Compare to literal structure instances for equality. Here equality means in the sense of datatype values
     78                  @return: comparison result
     79                  @rtype: boolean
     80                  """
     81                  try:
     82                          return self.lit == other.lit
     83                  except:
     84                          # There might be conversion problems...
     85                          return False
     86
     87 -        def __eq__(self, other) :
     88                  if other is None :
     89                          return False
     90                  else :
     91                          return self.lex == other.lex and self.dt == other.dt and self.lang == other.lang
     92
     93 -        def __ne__(self, other) :
     94                  return not self.__eq__(other)
     95
     96 -        def __hash__(self) :
     97                  if self.dt is not None :
     98                          return hash(self.lit) ^ hash(self.dt)
     99                  else :
    100                          return hash(self.lit)
    101
    102 -        def __repr__(self) :
    103                  retval = ""
    104                  retval += "lexical value: %s; " % self.lex
    105                  retval += "datatype: %s; "      % self.dt
    106                  retval += "language tag: %s; "  % self.lang
    107                  return retval
    108
    109
    110 -class LiteralProxies :
    111          # noinspection PyPep8
    112 -        def __init__(self, graph, closure) :
    113                  """
    114                  @param graph: the graph to be modified
    115                  """
    116                  self.lit_to_bnode = {}
    117                  self.bnode_to_lit = {}
    118                  self.graph        = graph
    119
    120                  to_be_removed = []
    121                  to_be_added   = []
    122                  for t in self.graph :
    123                          (subj, pred, obj) = t
    124                          # This is supposed to be a "proper" graph, so only the obj may be a literal
    125                          if isinstance(obj, rdflibLiteral):
    126                                  # Test the validity of the datatype
    127                                  if obj.datatype:
    128                                          try:
    129                                                  AltXSDToPYTHON[obj.datatype](str(obj))
    130                                          except ValueError:
    131                                                  closure.add_error("Lexical value of the literal '%s' does not match its datatype (%s)" % (str(obj), obj.datatype))
    132
    133                                  # In any case, this should be removed:
    134                                  if t not in to_be_removed:
    135                                          to_be_removed.append(t)
    136                                  # Check if a BNode has already been associated with that literal
    137                                  obj_st = _LiteralStructure(obj)
    138                                  found = False
    139                                  for l in self.lit_to_bnode.keys() :
    140                                          if obj_st.lex == l.lex and obj_st.dt == l.dt and obj_st.lang == l.lang :
    141                                                  t1 = (subj, pred, self.lit_to_bnode[l])
    142                                                  to_be_added.append(t1)
    143                                                  found = True
    144                                                  break
    145                                  if not found:
    146                                          # the bnode has to be created
    147                                          bn = BNode()
    148                                          # store this in the internal administration
    149                                          self.lit_to_bnode[obj_st] = bn
    150                                          self.bnode_to_lit[bn] = obj_st
    151                                          # modify the graph
    152                                          to_be_added.append((subj, pred, bn))
    153                                          to_be_added.append((bn, type, Literal))
    154                                          # Furthermore: a plain literal should be identified with a corresponding xsd:string and vice versa,
    155                                          # cf, RDFS Semantics document
    156                                          if obj_st.dt is None and obj_st.lang is None:
    157                                                  newLit = rdflibLiteral(obj_st.lex, datatype=ns_xsd["string"])
    158                                                  new_obj_st = _LiteralStructure(newLit)
    159                                                  new_obj_st.dt = ns_xsd["string"]
    160                                                  bn2 = BNode()
    161                                                  self.lit_to_bnode[new_obj_st] = bn2
    162                                                  self.bnode_to_lit[bn2] = new_obj_st
    163                                                  to_be_added.append((subj, pred, bn2))
    164                                                  to_be_added.append((bn2, type, Literal))
    165                                          elif obj_st.dt == ns_xsd["string"]:
    166                                                  newLit = rdflibLiteral(obj_st.lex, datatype=None)
    167                                                  new_obj_st = _LiteralStructure(newLit)
    168                                                  # new_obj_st = _LiteralStructure(obj) # Was this the correct one, or was this an old bug?
    169                                                  new_obj_st.dt = None
    170                                                  bn2 = BNode()
    171                                                  self.lit_to_bnode[new_obj_st] = bn2
    172                                                  self.bnode_to_lit[bn2] = new_obj_st
    173                                                  to_be_added.append((subj, pred, bn2))
    174                                                  to_be_added.append((bn2, type, Literal))
    175
    176                  # Do the real modifications
    177                  self._massageGraph(to_be_removed, to_be_added)
    178
    179 -        def restore(self) :
    180                  """
    181                  This method is to be invoked at the end of the forward chain processing. It restores literals (whenever possible)
    182                  to their original self...
    183                  """
    184                  to_be_removed = []
    185                  to_be_added   = []
    186                  for t in self.graph :
    187                          (subj, pred, obj) = t
    188                          # The two cases, namely when the literal appears in subject or object positions, should be treated differently
    189                          if subj in self.bnode_to_lit :
    190                                  # well... there may be to cases here: either this is the original tuple stating that
    191                                  # this bnode is a literal, or it is the result of an inference. In both cases, the tuple must
    192                                  # be removed from the result without any further action
    193                                  if t not in to_be_removed:
    194                                          to_be_removed.append(t)
    195                          elif obj in self.bnode_to_lit:
    196                                  # This is where the exchange should take place: put back the real literal into the graph, removing the proxy one
    197                                  if t not in to_be_removed:
    198                                          to_be_removed.append(t)
    199                                  # This is an additional thing due to the latest change of literal handling in RDF concepts.
    200                                  # If a literal is an xsd:string then a plain literal is put in its place for the purpose of serialization...
    201                                  lit = self.bnode_to_lit[obj].lit
    202                                  if lit.datatype is not None and lit.datatype == ns_xsd["string"]:
    203                                          lit = rdflibLiteral(str(lit))
    204                                  to_be_added.append((subj, pred, lit))
    205
    206                  # Do the real modifications
    207                  self._massageGraph(to_be_removed, to_be_added)
    208
    209 -        def _massageGraph(self, to_be_removed, to_be_added) :
    210                  """
    211                  Perform the removal and addition actions on the graph
    212                  @param to_be_removed: list of tuples to be removed
    213                  @param to_be_added : list of tuples to be added
    214                  """
    215                  for t in to_be_removed:
    216                          self.graph.remove(t)
    217                  for t in to_be_added:
    218                          self.graph.add(t)
    219