In [5]:
from rdflib import Graph, URIRef, Literal, BNode
from collections import defaultdict
In [6]:
class RDFTranslator:
    """
    Translate RDF into a Cypher-like property graph.
    """

    def __init__(self):
        self.graph = Graph()

        # RDF term -> printable node id
        self.node_names = {}

        # blank node numbering
        self.next_blank = 1

    ####################################################################
    # Parsing
    ####################################################################

    def parse(self, data, format="turtle"):
        self.graph = Graph()
        self.graph.parse(data=data, format=format)

    ####################################################################
    # Name resolution
    ####################################################################

    def node_name(self, term):

        if term in self.node_names:
            return self.node_names[term]

        if isinstance(term, URIRef):
            uri = str(term)

            if "#" in uri:
                name = uri.split("#")[-1]
            else:
                name = uri.rsplit("/", 1)[-1]

        elif isinstance(term, BNode):
            name = f"_b{self.next_blank}"
            self.next_blank += 1

        else:
            raise ValueError("Expected URI or blank node")

        self.node_names[term] = name
        return name

    def predicate_name(self, pred):

        uri = str(pred)

        if "#" in uri:
            return uri.split("#")[-1]

        return uri.rsplit("/", 1)[-1]

    ####################################################################
    # Translation
    ####################################################################

    def property_graph(self):

        node_properties = defaultdict(dict)
        edges = []

        for s, p, o in self.graph:

            subject = self.node_name(s)
            predicate = self.predicate_name(p)

            if isinstance(o, Literal):
                node_properties[subject][predicate] = o.toPython()

            else:
                object_node = self.node_name(o)
                edges.append((subject, predicate, object_node))

        return node_properties, edges

    ####################################################################
    # Pretty printer
    ####################################################################

    def print_cypher(self):

        node_properties, edges = self.property_graph()

        # Nodes participating in edges
        nodes = set()

        for s, _, o in edges:
            nodes.add(s)
            nodes.add(o)

        nodes.update(node_properties.keys())

        printed = set()

        def node_text(node):

            props = node_properties.get(node, {})

            if not props:
                return f"({node})"

            prop_string = ", ".join(
                f'{k}: {repr(v)}'
                for k, v in sorted(props.items())
            )

            return f"({node} {{{prop_string}}})"

        for s, p, o in edges:

            left = node_text(s) if s not in printed else f"({s})"
            printed.add(s)

            right = node_text(o) if o not in printed else f"({o})"
            printed.add(o)

            print(f"{left}-[:{p}]->{right}")
In [7]:
def test ():
    rdf = """
    @prefix foaf: <http://xmlns.com/foaf/0.1/> .
    @prefix ex:   <http://example.org/> .

    ex:art foaf:knows ex:bob .
    ex:art foaf:knows ex:bea .

    ex:bob foaf:knows ex:cal .
    ex:bob foaf:knows ex:cam .

    ex:bea foaf:knows ex:coe .
    ex:bea foaf:knows ex:cory .
    ex:bea foaf:age 23 .
    ex:bea foaf:based_near _:o1 .

    _:o1 ex:city "Seattle" .
    _:o1 ex:state "WA" .
    _:o1 ex:country "USA" .
    """
    translator = RDFTranslator()
    translator.parse(rdf)
    translator.print_cypher()
In [8]:
test()
(art)-[:knows]->(bob)
(art)-[:knows]->(bea {age: 23})
(bea)-[:based_near]->(_b1 {city: 'Seattle', country: 'USA', state: 'WA'})
(bea)-[:knows]->(cory)
(bea)-[:knows]->(coe)
(bob)-[:knows]->(cam)
(bob)-[:knows]->(cal)