"""
A commandline tool for drawing RDF graphs in Graphviz DOT format
You can draw the graph of an RDF file directly:
.. code-block: bash
rdf2dot my_rdf_file.rdf | dot -Tpng | display
"""
from __future__ import annotations
import collections
import html
import sys
from typing import TYPE_CHECKING, Any, TextIO
import rdflib
import rdflib.extras.cmdlineutils
from rdflib import XSD
from rdflib.graph import Graph
from rdflib.term import Literal, URIRef
if TYPE_CHECKING:
from rdflib.graph import _ObjectType, _PredicateType, _SubjectType
LABEL_PROPERTIES = [
rdflib.RDFS.label,
rdflib.URIRef("http://purl.org/dc/elements/1.1/title"),
rdflib.URIRef("http://xmlns.com/foaf/0.1/name"),
rdflib.URIRef("http://www.w3.org/2006/vcard/ns#fn"),
rdflib.URIRef("http://www.w3.org/2006/vcard/ns#org"),
]
XSDTERMS = [
XSD[x]
for x in (
"anyURI",
"base64Binary",
"boolean",
"byte",
"date",
"dateTime",
"decimal",
"double",
"duration",
"float",
"gDay",
"gMonth",
"gMonthDay",
"gYear",
"gYearMonth",
"hexBinary",
"ID",
"IDREF",
"IDREFS",
"int",
"integer",
"language",
"long",
"Name",
"NCName",
"negativeInteger",
"NMTOKEN",
"NMTOKENS",
"nonNegativeInteger",
"nonPositiveInteger",
"normalizedString",
"positiveInteger",
"QName",
"short",
"string",
"time",
"token",
"unsignedByte",
"unsignedInt",
"unsignedLong",
"unsignedShort",
)
]
EDGECOLOR = "blue"
NODECOLOR = "black"
ISACOLOR = "black"
[docs]
def rdf2dot(g: Graph, stream: TextIO, opts: dict[str, Any] = {}):
"""
Convert the RDF graph to DOT
writes the dot output to the stream
"""
fields = collections.defaultdict(set)
nodes: dict[_SubjectType | _PredicateType | _ObjectType, str] = {}
def node(x: _SubjectType | _PredicateType | _ObjectType) -> str:
if x not in nodes:
nodes[x] = "node%d" % len(nodes)
return nodes[x]
def label(x: _SubjectType, g: Graph):
for labelProp in LABEL_PROPERTIES: # noqa: N806
l_ = g.value(x, labelProp)
if l_:
return l_
try:
return g.namespace_manager.compute_qname(x)[2]
except Exception:
return x
def formatliteral(l: Literal, g): # noqa: E741
v = html.escape(l)
if l.datatype:
return ""%s"^^%s" % (v, qname(l.datatype, g))
elif l.language:
return ""%s"@%s" % (v, l.language)
return ""%s"" % v
def qname(x: URIRef, g: Graph) -> str:
try:
q = g.compute_qname(x)
return q[0] + ":" + q[2]
except Exception:
return x
def color(p):
return "BLACK"
stream.write('digraph { \n node [ fontname="DejaVu Sans" ] ; \n')
for s, p, o in g:
sn = node(s)
if p == rdflib.RDFS.label:
continue
if isinstance(o, (rdflib.URIRef, rdflib.BNode)):
on = node(o)
opstr = (
"\t%s -> %s [ color=%s, label=< <font point-size='10' "
+ "color='#336633'>%s</font> > ] ;\n"
)
# type error: Argument 1 to "qname" has incompatible type "Node"; expected "URIRef"
stream.write(opstr % (sn, on, color(p), qname(p, g))) # type: ignore[arg-type]
else:
# type error: Argument 1 to "qname" has incompatible type "Node"; expected "URIRef"
fields[sn].add((qname(p, g), formatliteral(o, g))) # type: ignore[arg-type]
for u, n in nodes.items():
stream.write("# %s %s\n" % (u, n))
f = [
"<tr><td align='left'>%s</td><td align='left'>%s</td></tr>" % x
for x in sorted(fields[n])
]
opstr = (
"%s [ shape=none, color=%s label=< <table color='#666666'"
+ " cellborder='0' cellspacing='0' border='1'><tr>"
+ "<td colspan='2' bgcolor='grey'><B>%s</B></td></tr><tr>"
+ "<td href='%s' bgcolor='#eeeeee' colspan='2'>"
+ "<font point-size='10' color='#6666ff'>%s</font></td>"
+ "</tr>%s</table> > ] \n"
)
stream.write(
opstr
% (n, NODECOLOR, html.escape(label(u, g)), u, html.escape(u), "".join(f))
)
stream.write("}\n")
def _help():
sys.stderr.write(
"""
rdf2dot.py [-f <format>] files...
Read RDF files given on STDOUT, writes a graph of the RDFS schema in DOT
language to stdout
-f specifies parser to use, if not given,
"""
)
[docs]
def main():
rdflib.extras.cmdlineutils.main(rdf2dot, _help)
if __name__ == "__main__":
main()