2025-12-01
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
"""
|
||||
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 Any, Dict, TextIO
|
||||
|
||||
import rdflib
|
||||
import rdflib.extras.cmdlineutils
|
||||
from rdflib import XSD
|
||||
from rdflib.graph import Graph
|
||||
from rdflib.term import Literal, Node, URIRef
|
||||
|
||||
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"
|
||||
|
||||
|
||||
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[Node, str] = {}
|
||||
|
||||
def node(x: Node) -> str:
|
||||
if x not in nodes:
|
||||
nodes[x] = "node%d" % len(nodes)
|
||||
return nodes[x]
|
||||
|
||||
def label(x: Node, g: Graph):
|
||||
for labelProp in LABEL_PROPERTIES: # noqa: N806
|
||||
l_ = g.value(x, labelProp)
|
||||
if l_:
|
||||
return l_
|
||||
try:
|
||||
# type error: Argument 1 to "compute_qname" of "NamespaceManager" has incompatible type "Node"; expected "str"
|
||||
return g.namespace_manager.compute_qname(x)[2] # type: ignore[arg-type]
|
||||
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
|
||||
# type error: Value of type variable "AnyStr" of "escape" cannot be "Node"
|
||||
% (n, NODECOLOR, html.escape(label(u, g)), u, html.escape(u), "".join(f)) # type: ignore[type-var]
|
||||
)
|
||||
|
||||
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,
|
||||
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
rdflib.extras.cmdlineutils.main(rdf2dot, _help)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user