""" 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=< %s > ] ;\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 = [ "%s%s" % x for x in sorted(fields[n]) ] opstr = ( "%s [ shape=none, color=%s label=< " + "" + "" + "%s
%s
" + "%s
> ] \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 ] 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()