rdf2dot.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. """
  2. A commandline tool for drawing RDF graphs in Graphviz DOT format
  3. You can draw the graph of an RDF file directly:
  4. ```bash
  5. rdf2dot my_rdf_file.rdf | dot -Tpng | display
  6. ```
  7. """
  8. from __future__ import annotations
  9. import collections
  10. import html
  11. import sys
  12. from typing import Any, Dict, TextIO
  13. import rdflib
  14. import rdflib.extras.cmdlineutils
  15. from rdflib import XSD
  16. from rdflib.graph import Graph
  17. from rdflib.term import Literal, Node, URIRef
  18. LABEL_PROPERTIES = [
  19. rdflib.RDFS.label,
  20. rdflib.URIRef("http://purl.org/dc/elements/1.1/title"),
  21. rdflib.URIRef("http://xmlns.com/foaf/0.1/name"),
  22. rdflib.URIRef("http://www.w3.org/2006/vcard/ns#fn"),
  23. rdflib.URIRef("http://www.w3.org/2006/vcard/ns#org"),
  24. ]
  25. XSDTERMS = [
  26. XSD[x]
  27. for x in (
  28. "anyURI",
  29. "base64Binary",
  30. "boolean",
  31. "byte",
  32. "date",
  33. "dateTime",
  34. "decimal",
  35. "double",
  36. "duration",
  37. "float",
  38. "gDay",
  39. "gMonth",
  40. "gMonthDay",
  41. "gYear",
  42. "gYearMonth",
  43. "hexBinary",
  44. "ID",
  45. "IDREF",
  46. "IDREFS",
  47. "int",
  48. "integer",
  49. "language",
  50. "long",
  51. "Name",
  52. "NCName",
  53. "negativeInteger",
  54. "NMTOKEN",
  55. "NMTOKENS",
  56. "nonNegativeInteger",
  57. "nonPositiveInteger",
  58. "normalizedString",
  59. "positiveInteger",
  60. "QName",
  61. "short",
  62. "string",
  63. "time",
  64. "token",
  65. "unsignedByte",
  66. "unsignedInt",
  67. "unsignedLong",
  68. "unsignedShort",
  69. )
  70. ]
  71. EDGECOLOR = "blue"
  72. NODECOLOR = "black"
  73. ISACOLOR = "black"
  74. def rdf2dot(g: Graph, stream: TextIO, opts: Dict[str, Any] = {}):
  75. """
  76. Convert the RDF graph to DOT
  77. writes the dot output to the stream
  78. """
  79. fields = collections.defaultdict(set)
  80. nodes: Dict[Node, str] = {}
  81. def node(x: Node) -> str:
  82. if x not in nodes:
  83. nodes[x] = "node%d" % len(nodes)
  84. return nodes[x]
  85. def label(x: Node, g: Graph):
  86. for labelProp in LABEL_PROPERTIES: # noqa: N806
  87. l_ = g.value(x, labelProp)
  88. if l_:
  89. return l_
  90. try:
  91. # type error: Argument 1 to "compute_qname" of "NamespaceManager" has incompatible type "Node"; expected "str"
  92. return g.namespace_manager.compute_qname(x)[2] # type: ignore[arg-type]
  93. except Exception:
  94. return x
  95. def formatliteral(l: Literal, g): # noqa: E741
  96. v = html.escape(l)
  97. if l.datatype:
  98. return ""%s"^^%s" % (v, qname(l.datatype, g))
  99. elif l.language:
  100. return ""%s"@%s" % (v, l.language)
  101. return ""%s"" % v
  102. def qname(x: URIRef, g: Graph) -> str:
  103. try:
  104. q = g.compute_qname(x)
  105. return q[0] + ":" + q[2]
  106. except Exception:
  107. return x
  108. def color(p):
  109. return "BLACK"
  110. stream.write('digraph { \n node [ fontname="DejaVu Sans" ] ; \n')
  111. for s, p, o in g:
  112. sn = node(s)
  113. if p == rdflib.RDFS.label:
  114. continue
  115. if isinstance(o, (rdflib.URIRef, rdflib.BNode)):
  116. on = node(o)
  117. opstr = (
  118. "\t%s -> %s [ color=%s, label=< <font point-size='10' "
  119. + "color='#336633'>%s</font> > ] ;\n"
  120. )
  121. # type error: Argument 1 to "qname" has incompatible type "Node"; expected "URIRef"
  122. stream.write(opstr % (sn, on, color(p), qname(p, g))) # type: ignore[arg-type]
  123. else:
  124. # type error: Argument 1 to "qname" has incompatible type "Node"; expected "URIRef"
  125. fields[sn].add((qname(p, g), formatliteral(o, g))) # type: ignore[arg-type]
  126. for u, n in nodes.items():
  127. stream.write("# %s %s\n" % (u, n))
  128. f = [
  129. "<tr><td align='left'>%s</td><td align='left'>%s</td></tr>" % x
  130. for x in sorted(fields[n])
  131. ]
  132. opstr = (
  133. "%s [ shape=none, color=%s label=< <table color='#666666'"
  134. + " cellborder='0' cellspacing='0' border='1'><tr>"
  135. + "<td colspan='2' bgcolor='grey'><B>%s</B></td></tr><tr>"
  136. + "<td href='%s' bgcolor='#eeeeee' colspan='2'>"
  137. + "<font point-size='10' color='#6666ff'>%s</font></td>"
  138. + "</tr>%s</table> > ] \n"
  139. )
  140. stream.write(
  141. opstr
  142. # type error: Value of type variable "AnyStr" of "escape" cannot be "Node"
  143. % (n, NODECOLOR, html.escape(label(u, g)), u, html.escape(u), "".join(f)) # type: ignore[type-var]
  144. )
  145. stream.write("}\n")
  146. def _help():
  147. sys.stderr.write(
  148. """
  149. rdf2dot.py [-f <format>] files...
  150. Read RDF files given on STDOUT, writes a graph of the RDFS schema in DOT
  151. language to stdout
  152. -f specifies parser to use, if not given,
  153. """
  154. )
  155. def main():
  156. rdflib.extras.cmdlineutils.main(rdf2dot, _help)
  157. if __name__ == "__main__":
  158. main()