# Copyright (c) 2004 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # Copyright (c) 2004 DoCoMo Euro-Labs GmbH (Munich, Germany). # http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """a RedLand (http://librdf.org/) based knowledge database :version: $Revision:$ :author: Logilab :copyright: 2004 LOGILAB S.A. (Paris, FRANCE) 2004 DoCoMo Euro-Labs GmbH (Munich, Germany) :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com """ __revision__ = '$Id$' # FIXME # from PyLogDB import KBError class KBError(Exception): pass from cStringIO import StringIO import traceback import re import os import RDF from narval.public import AL_NS, url_to_file from narval.elements.rdf import RDFStatementElement, RDQLResultLineElement from narval.interfaces.rdf import IRDFStatement, IRDQLQuery from narval.interfaces.base import ICommand from narval.elements import create_error, create_command from narval.elements.base import DataElement ###################################################################### DEFAULT_REDLAND_STORE_FILE_URL = 'file:$NARVAL_HOME/data/rdfstore.rdf' MINIMAL_CONTENT = ''' ''' def redland_kb_file(obj=None): """return the path to the redland knowledge file :param obj: object adaptable to IURL :rtype: tuple(str, str) """ path, encoding = url_to_file(obj, DEFAULT_REDLAND_STORE_FILE_URL) return path class RedLandModel(RDF.Model): """convenience class initialized with a simple kb uri""" def __init__(self, kb_uri): self._filename = redland_kb_file(kb_uri) try: storage = RDF.FileStorage(self._filename) # XXX FIXME: wait for Dave Beckett feedback about unifying # RDF.RedlandError and Redland_python.Error except: # Naive way one of the possible errors : self._filename # doesn't exist if not os.path.exists(self._filename): log(LOG_DEBUG, "%r doesn't exist, I'll create a default empty one" % (self._filename)) fp = file(self._filename, 'w') fp.write(MINIMAL_CONTENT) fp.close() else: # For now, we don't know how to hanlde other errors raise RDF.Model.__init__(self, storage) def save(self): """uses RDF.Serializer to serialize current model""" RDF.Serializer().serialize_model_to_file(self._filename, self) def exec_query(self, query, namespaces = {}): """execute query against current model""" # FIXME: break if query defines different NS for existing alias for alias, url in namespaces.items(): #query = query.replace('<%s:' % alias, '<%s'%url) query = re.sub('%s:(\w+)' % alias, r'<%s\1>' % url, query) log(LOG_DEBUG, "Query = %s" % query) query = RDF.Query(query) if query is None : raise Exception("Bad Query") # execute() returns an iterator, and we need a list # result = [rlnode_as_string(elt) for elt in query.execute(self)] result = query.execute(self) if result is None : result = [] else : result = list(result) log(LOG_DEBUG, "RESULT = %s" % result) return result def element_as_rlnode(element): """converts a simple 'full text' element into a RedLand Node""" element = str(element) if element.startswith('http://'): return RDF.Uri(element) return RDF.Node(element) def rlnode_as_string(rl_node): """converts a RDF.Node into a raw string""" if rl_node.is_resource(): return str(rl_node.uri) elif rl_node.is_blank(): return str(rl_node.blank_identifier) elif rl_node.is_literal(): return rl_node.literal_value['string'] return None def rl2al_statement(rl_statement): """converts a RedLandStatement into a (Al)RDFStatement""" return RDFStatementElement(subject = rlnode_as_string(rl_statement.subject), predicate = rlnode_as_string(rl_statement.predicate), object = rlnode_as_string(rl_statement.object)) def al2rl_statement(al_statement): """concerts a (Al)RDFStatement into a RedLandStatement""" subj = al_statement.subject and element_as_rlnode(al_statement.subject) pred = element_as_rlnode(al_statement.predicate) obj = al_statement.object and element_as_rlnode(al_statement.object) return RDF.Statement(subj, pred, obj) def unify_foaf(model): """ XXX move to extension. Useful outside this module. """ # search duplicate names names = {} query = RDF.Query('SELECT ?x, ?n WHERE (?x ?n)') results = query.execute(model) for res in results: names.setdefault(str(res['n']), []).append(res['x']) remap_id = {} for name, ids in names.items() : remap_id[name] = ids[0].blank_identifier # remap duplicates for name, ids in names.items(): for nodeid in ids: if nodeid.blank_identifier == remap_id[name] : continue blk = RDF.Node(blank=nodeid.blank_identifier) # remap subject to_suppress = [] search_stmt = RDF.Statement(blk, None, None) for stmt in model.find_statements(search_stmt): object = RDF.Node(stmt.object) if RDF.node_type_name(stmt.object.type) == 'NODE_TYPE_BLANK' : for other_name, other_ids in names.items() : other_ids = [other_id.blank_identifier for other_id in other_ids] if stmt.object.blank_identifier in other_ids : object = RDF.Node(blank=remap_id[other_name]) new_stmt = RDF.Statement(RDF.Node(blank=remap_id[name]), RDF.Node(stmt.predicate), object) model.add_statement(new_stmt) to_suppress.append(stmt) # remove statements for stmt in to_suppress: model.remove_statement(stmt) # remap object to_suppress = [] search_stmt = RDF.Statement(None, None, blk) for stmt in model.find_statements(search_stmt): subject = RDF.Node(stmt.subject) if RDF.node_type_name(stmt.subject.type) == 'NODE_TYPE_BLANK' : for other_name, other_ids in names.items() : other_ids = [other_id.blank_identifier for other_id in other_ids] if stmt.subject.blank_identifier in other_ids : subject = RDF.Node(blank=remap_id[other_name]) new_stmt = RDF.Statement(subject, RDF.Node(stmt.predicate), RDF.Node(blank=remap_id[name])) model.add_statement(stmt) to_suppress.append(stmt) # remove statements for stmt in to_suppress: model.remove_statement(stmt) # actions definitions start here ############################################## MOD_XML = ''' ''' % AL_NS def act_unify(inputs): """search for matching statements in the knowledge base""" print 'using backend',inputs['kb'] model = RedLandModel(inputs['kb']) result = [] for stmt in inputs['stmts']: for rl_stmt in model.find_statements(al2rl_statement(IRDFStatement(stmt))): result.append(rl2al_statement(rl_stmt)) return {'stmts' : result} MOD_XML += """ %s IRDFStatement(elmt) elmt.getattr((TYPE_NS, 'name')) == 'uri:memory:redland' IURL(elmt) IRDFStatement(elmt) """ % act_unify.__doc__ def act_add_statements(inputs): """add a list of `IRDFStatement` elements to the knowledge base""" print 'using backend',inputs['kb'] model = RedLandModel(inputs['kb']) for stmt in inputs['stmts']: statement = al2rl_statement(IRDFStatement(stmt)) if statement is None: raise KBError("new RDF.Statement failed") print 'adding', statement model.add_statement(statement) print 'added' ## try: ## model.save() ## except Exception, ex: ## print 'error while saving model', ex return {} MOD_XML += """ %s IRDFStatement(elmt) elmt.getattr((TYPE_NS, 'name')) == 'uri:memory:redland' IURL(elmt) """ % act_add_statements.__doc__ def act_export(inputs): """export all statements in the knowledge base""" model = RedLandModel(inputs['kb']) return {'stmts': [rl2al_statement(stmt) for stmt in model.find_statements(RDF.Statement())]} MOD_XML = MOD_XML + """ %s elmt.getattr((TYPE_NS, 'name')) == 'uri:memory:redland' IURL(elmt) IRDFStatement(elmt) """ % act_export.__doc__ def act_import_url(inputs): """import a FOAF file fetched from given URL""" log(LOG_DEBUG, "INPUTS %s" % inputs) model = RedLandModel(inputs['kb']) # XXX FIXME : RDF.Uri() doesn't accept unicode strings uri = RDF.Uri(str(ICommand(inputs['import-command']).args[1])) parser = RDF.Parser('raptor') if parser is None: raise Exception("Failed to create RDF.Parser raptor") try: parser.parse_into_model(model, uri, 'http://www.logilab.org/default.rdf') except Exception, exc: error = DataElement('Sorry, import failed\nlog: %s' % exc) return {'rdql-error' : error} unify_foaf(model) res = DataElement('imported %s' % uri) return {'rdql-info': res } MOD_XML += """ %s ICommand(elmt) elmt.getattr((TYPE_NS, 'name')) == 'uri:memory:redland' IURL(elmt) isinstance(elmt, DataElement) IData(elmt) """ % act_import_url.__doc__ def act_import_string(inputs): """import a FOAF string: FIXME""" log(LOG_DEBUG, "INPUTS %s" % inputs) model = RedLandModel(inputs['kb']) rdf_string = ICommand(inputs['command']).args[1] parser = RDF.Parser('raptor') if parser is None: raise Exception("Failed to create RDF.Parser raptor") parser.parse_string_into_model(model, rdf_string, 'http://www.logilab.org/default.rdf') unify_foaf(model) res = DataElement('imported') return {'rdql-info': res } MOD_XML += """ %s ICommand(elmt) elmt.getattr((TYPE_NS, 'name')) == 'uri:memory:redland' IURL(elmt) IData(elmt) """ % act_import_url.__doc__ def act_query_base(inputs): """queries the RDF knowledge base using RDQL""" namespaces = dict([(e.alias, e.address) for e in inputs['namespaces']]) model = RedLandModel(inputs['kb']) query = IRDQLQuery(inputs['query']).query error = None query_results = None try: query_results = [RDQLResultLineElement(res) for res in model.exec_query(query, namespaces)] except Exception, exc: data = StringIO() data.write('An error occured :\n') traceback.print_exc(file = data) error = DataElement(data.getvalue()) if not error and not query_results: error = DataElement('Sorry, no result found') if error: return {'rdql-error' : error} return {'rdql-results' : query_results} MOD_XML += """ %s IRDFNamespace(elmt) IRDQLQuery(elmt) elmt.getattr((TYPE_NS, 'name')) == 'uri:memory:redland' IURL(elmt) isinstance(elmt, DataElement) IRDQLResultLine(elmt) """ % act_query_base.__doc__ MOD_XML += "" # http://starship.python.net/~fdrake/fdrake.rdf