rdfpipe.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. #!/usr/bin/env python
  2. """
  3. A commandline tool for parsing RDF in different formats and serializing the
  4. resulting graph to a chosen format.
  5. """
  6. from __future__ import annotations
  7. import logging
  8. import sys
  9. from optparse import OptionParser
  10. from typing import BinaryIO, Optional
  11. import rdflib
  12. from rdflib import plugin
  13. from rdflib.graph import ConjunctiveGraph
  14. from rdflib.parser import Parser
  15. from rdflib.serializer import Serializer
  16. from rdflib.store import Store
  17. from rdflib.util import guess_format
  18. DEFAULT_INPUT_FORMAT = "xml"
  19. DEFAULT_OUTPUT_FORMAT = "n3"
  20. def parse_and_serialize(
  21. input_files,
  22. input_format,
  23. guess,
  24. outfile,
  25. output_format,
  26. ns_bindings,
  27. store_conn="",
  28. store_type=None,
  29. ):
  30. if store_type:
  31. store = plugin.get(store_type, Store)()
  32. store.open(store_conn)
  33. graph = ConjunctiveGraph(store)
  34. else:
  35. store = None
  36. graph = ConjunctiveGraph()
  37. for prefix, uri in ns_bindings.items():
  38. graph.namespace_manager.bind(prefix, uri, override=False)
  39. for fpath in input_files:
  40. use_format, kws = _format_and_kws(input_format)
  41. if fpath == "-":
  42. fpath = sys.stdin
  43. elif not input_format and guess:
  44. use_format = guess_format(fpath) or DEFAULT_INPUT_FORMAT
  45. graph.parse(fpath, format=use_format, **kws)
  46. if outfile:
  47. output_format, kws = _format_and_kws(output_format)
  48. kws.setdefault("base", None)
  49. graph.serialize(destination=outfile, format=output_format, **kws)
  50. if store:
  51. store.rollback()
  52. def _format_and_kws(fmt):
  53. """
  54. ```python
  55. >>> _format_and_kws("fmt")
  56. ('fmt', {})
  57. >>> _format_and_kws("fmt:+a")
  58. ('fmt', {'a': True})
  59. >>> _format_and_kws("fmt:a")
  60. ('fmt', {'a': True})
  61. >>> _format_and_kws("fmt:+a,-b") #doctest: +SKIP
  62. ('fmt', {'a': True, 'b': False})
  63. >>> _format_and_kws("fmt:c=d")
  64. ('fmt', {'c': 'd'})
  65. >>> _format_and_kws("fmt:a=b:c")
  66. ('fmt', {'a': 'b:c'})
  67. ```
  68. """
  69. fmt, kws = fmt, {}
  70. if fmt and ":" in fmt:
  71. fmt, kwrepr = fmt.split(":", 1)
  72. for kw in kwrepr.split(","):
  73. if "=" in kw:
  74. k, v = kw.split("=")
  75. kws[k] = v
  76. elif kw.startswith("-"):
  77. kws[kw[1:]] = False
  78. elif kw.startswith("+"):
  79. kws[kw[1:]] = True
  80. else: # same as "+"
  81. kws[kw] = True
  82. return fmt, kws
  83. def make_option_parser():
  84. parser_names = _get_plugin_names(Parser)
  85. serializer_names = _get_plugin_names(Serializer)
  86. kw_example = "FORMAT:(+)KW1,-KW2,KW3=VALUE"
  87. oparser = OptionParser(
  88. "%prog [-h] [-i INPUT_FORMAT] [-o OUTPUT_FORMAT] "
  89. + "[--ns=PFX=NS ...] [-] [FILE ...]",
  90. description=__doc__.strip()
  91. + (
  92. " Reads file system paths, URLs or from stdin if '-' is given."
  93. " The result is serialized to stdout."
  94. ),
  95. version="%prog " + "(using rdflib %s)" % rdflib.__version__,
  96. )
  97. oparser.add_option(
  98. "-i",
  99. "--input-format",
  100. type=str, # default=DEFAULT_INPUT_FORMAT,
  101. help="Format of the input document(s)."
  102. " Available input formats are: %s." % parser_names
  103. + " If no format is given, it will be "
  104. + "guessed from the file name extension."
  105. + " Keywords to parser can be given after format like: %s." % kw_example,
  106. metavar="INPUT_FORMAT",
  107. )
  108. oparser.add_option(
  109. "-o",
  110. "--output-format",
  111. type=str,
  112. default=DEFAULT_OUTPUT_FORMAT,
  113. help="Format of the graph serialization."
  114. " Available output formats are: %s." % serializer_names
  115. + " Default format is: '%default'."
  116. + " Keywords to serializer can be given after format like: %s." % kw_example,
  117. metavar="OUTPUT_FORMAT",
  118. )
  119. oparser.add_option(
  120. "--ns",
  121. action="append",
  122. type=str,
  123. help="Register a namespace binding (QName prefix to a base URI). "
  124. "This can be used more than once.",
  125. metavar="PREFIX=NAMESPACE",
  126. )
  127. oparser.add_option(
  128. "--no-guess",
  129. dest="guess",
  130. action="store_false",
  131. default=True,
  132. help="Don't guess format based on file suffix.",
  133. )
  134. oparser.add_option(
  135. "--no-out",
  136. action="store_true",
  137. default=False,
  138. help="Don't output the resulting graph "
  139. + "(useful for checking validity of input).",
  140. )
  141. oparser.add_option(
  142. "-w",
  143. "--warn",
  144. action="store_true",
  145. default=False,
  146. help="Output warnings to stderr (by default only critical errors).",
  147. )
  148. return oparser
  149. def _get_plugin_names(kind):
  150. return ", ".join(p.name for p in plugin.plugins(kind=kind))
  151. def main():
  152. oparser = make_option_parser()
  153. opts, args = oparser.parse_args()
  154. if len(args) < 1:
  155. oparser.print_usage()
  156. oparser.exit()
  157. if opts.warn:
  158. loglevel = logging.WARNING
  159. else:
  160. loglevel = logging.CRITICAL
  161. logging.basicConfig(level=loglevel)
  162. ns_bindings = {}
  163. if opts.ns:
  164. for ns_kw in opts.ns:
  165. pfx, uri = ns_kw.split("=")
  166. ns_bindings[pfx] = uri
  167. outfile: Optional[BinaryIO] = sys.stdout.buffer
  168. if opts.no_out:
  169. outfile = None
  170. parse_and_serialize(
  171. args, opts.input_format, opts.guess, outfile, opts.output_format, ns_bindings
  172. )
  173. if __name__ == "__main__":
  174. main()