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