nquads.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. """
  2. This is a rdflib plugin for parsing NQuad files into Conjunctive
  3. graphs that can be used and queried. The store that backs the graph
  4. *must* be able to handle contexts.
  5. ```python
  6. >>> from rdflib import ConjunctiveGraph, URIRef, Namespace
  7. >>> g = ConjunctiveGraph()
  8. >>> data = open("test/data/nquads.rdflib/example.nquads", "rb")
  9. >>> g.parse(data, format="nquads") # doctest:+ELLIPSIS
  10. <Graph identifier=... (<class 'rdflib.graph.ConjunctiveGraph'>)>
  11. >>> assert len(g.store) == 449
  12. >>> # There should be 16 separate contexts
  13. >>> assert len([x for x in g.store.contexts()]) == 16
  14. >>> # is the name of entity E10009 "Arco Publications"?
  15. >>> # (in graph http://bibliographica.org/entity/E10009)
  16. >>> # Looking for:
  17. >>> # <http://bibliographica.org/entity/E10009>
  18. >>> # <http://xmlns.com/foaf/0.1/name>
  19. >>> # "Arco Publications"
  20. >>> # <http://bibliographica.org/entity/E10009>
  21. >>> s = URIRef("http://bibliographica.org/entity/E10009")
  22. >>> FOAF = Namespace("http://xmlns.com/foaf/0.1/")
  23. >>> assert(g.value(s, FOAF.name).eq("Arco Publications"))
  24. ```
  25. """
  26. from __future__ import annotations
  27. from codecs import getreader
  28. from typing import Any, MutableMapping, Optional
  29. from rdflib.exceptions import ParserError as ParseError
  30. from rdflib.graph import ConjunctiveGraph, Dataset, Graph
  31. from rdflib.parser import InputSource
  32. # Build up from the NTriples parser:
  33. from rdflib.plugins.parsers.ntriples import W3CNTriplesParser, r_tail, r_wspace
  34. from rdflib.term import BNode
  35. __all__ = ["NQuadsParser"]
  36. _BNodeContextType = MutableMapping[str, BNode]
  37. class NQuadsParser(W3CNTriplesParser):
  38. # type error: Signature of "parse" incompatible with supertype "W3CNTriplesParser"
  39. def parse( # type: ignore[override]
  40. self,
  41. inputsource: InputSource,
  42. sink: Graph,
  43. bnode_context: Optional[_BNodeContextType] = None,
  44. skolemize: bool = False,
  45. **kwargs: Any,
  46. ):
  47. """Parse inputsource as an N-Quads file.
  48. Args:
  49. inputsource: The source of N-Quads-formatted data.
  50. sink: The graph where parsed quads will be stored.
  51. bnode_context: Optional dictionary mapping blank node identifiers to
  52. [`BNode`][rdflib.term.BNode] instances.
  53. See `.W3CNTriplesParser.parse` for more details.
  54. skolemize: Whether to skolemize blank nodes.
  55. Returns:
  56. The Dataset containing the parsed quads.
  57. Raises:
  58. AssertionError: If the sink store is not context-aware.
  59. ParseError: If the input is not a file-like object or contains invalid lines.
  60. """
  61. assert (
  62. sink.store.context_aware
  63. ), "NQuadsParser must be given a context-aware store."
  64. # Set default_union to True to mimic ConjunctiveGraph behavior
  65. ds = Dataset(store=sink.store, default_union=True)
  66. ds_default = ds.default_context # the DEFAULT_DATASET_GRAPH_ID
  67. new_default_context = None
  68. if isinstance(sink, (Dataset, ConjunctiveGraph)):
  69. new_default_context = sink.default_context
  70. elif sink.identifier is not None:
  71. if sink.identifier == ds_default.identifier:
  72. new_default_context = sink
  73. else:
  74. new_default_context = ds.get_context(sink.identifier)
  75. if new_default_context is not None:
  76. ds.default_context = new_default_context
  77. ds.remove_graph(ds_default) # remove the original unused default graph
  78. # type error: Incompatible types in assignment (expression has type "ConjunctiveGraph", base class "W3CNTriplesParser" defined the type as "Union[DummySink, NTGraphSink]")
  79. self.sink: Dataset = ds # type: ignore[assignment]
  80. self.skolemize = skolemize
  81. source = inputsource.getCharacterStream()
  82. if not source:
  83. source = inputsource.getByteStream()
  84. source = getreader("utf-8")(source)
  85. if not hasattr(source, "read"):
  86. raise ParseError("Item to parse must be a file-like object.")
  87. self.file = source
  88. self.buffer = ""
  89. while True:
  90. self.line = __line = self.readline()
  91. if self.line is None:
  92. break
  93. try:
  94. self.parseline(bnode_context)
  95. except ParseError as msg:
  96. raise ParseError("Invalid line (%s):\n%r" % (msg, __line))
  97. return self.sink
  98. def parseline(self, bnode_context: Optional[_BNodeContextType] = None) -> None:
  99. self.eat(r_wspace)
  100. if (not self.line) or self.line.startswith("#"):
  101. return # The line is empty or a comment
  102. subject = self.subject(bnode_context)
  103. self.eat(r_wspace)
  104. predicate = self.predicate()
  105. self.eat(r_wspace)
  106. obj = self.object(bnode_context)
  107. self.eat(r_wspace)
  108. context = self.uriref() or self.nodeid(bnode_context)
  109. self.eat(r_tail)
  110. if self.line:
  111. raise ParseError("Trailing garbage")
  112. # Must have a context aware store - add on a normal Graph
  113. # discards anything where the ctx != graph.identifier
  114. if context:
  115. self.sink.get_context(context).add((subject, predicate, obj))
  116. else:
  117. self.sink.default_context.add((subject, predicate, obj))