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