trig.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. """
  2. Trig RDF graph serializer for RDFLib.
  3. See <http://www.w3.org/TR/trig/> for syntax specification.
  4. """
  5. from __future__ import annotations
  6. from typing import IO, TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
  7. from rdflib.graph import ConjunctiveGraph, Graph
  8. from rdflib.plugins.serializers.turtle import TurtleSerializer
  9. from rdflib.term import BNode, Node
  10. if TYPE_CHECKING:
  11. from rdflib.graph import _ContextType, _SubjectType
  12. __all__ = ["TrigSerializer"]
  13. class TrigSerializer(TurtleSerializer):
  14. """TriG RDF graph serializer."""
  15. short_name = "trig"
  16. indentString = 4 * " "
  17. def __init__(self, store: Union[Graph, ConjunctiveGraph]):
  18. self.default_context: Optional[Node]
  19. if store.context_aware:
  20. if TYPE_CHECKING:
  21. assert isinstance(store, ConjunctiveGraph)
  22. self.contexts = list(store.contexts())
  23. self.default_context = store.default_context.identifier
  24. if store.default_context:
  25. self.contexts.append(store.default_context)
  26. else:
  27. self.contexts = [store]
  28. self.default_context = None
  29. super(TrigSerializer, self).__init__(store)
  30. def preprocess(self) -> None:
  31. for context in self.contexts:
  32. # do not write unnecessary prefix (ex: for an empty default graph)
  33. if len(context) == 0:
  34. continue
  35. self.store = context
  36. # Don't generate a new prefix for a graph URI if one already exists
  37. self.get_pname(context.identifier, False)
  38. self._subjects = {}
  39. for triple in context:
  40. self.preprocessTriple(triple)
  41. for subject in self._subjects.keys():
  42. self._references[subject] += 1
  43. self._contexts[context] = (self.orderSubjects(), self._subjects)
  44. def reset(self) -> None:
  45. super(TrigSerializer, self).reset()
  46. self._contexts: Dict[
  47. _ContextType,
  48. Tuple[List[_SubjectType], Dict[_SubjectType, bool]],
  49. ] = {}
  50. def serialize(
  51. self,
  52. stream: IO[bytes],
  53. base: Optional[str] = None,
  54. encoding: Optional[str] = None,
  55. spacious: Optional[bool] = None,
  56. **kwargs: Any,
  57. ) -> None:
  58. self.reset()
  59. self.stream = stream
  60. # if base is given here, use that, if not and a base is set for the graph use that
  61. if base is not None:
  62. self.base = base
  63. elif self.store.base is not None:
  64. self.base = self.store.base
  65. if spacious is not None:
  66. self._spacious = spacious
  67. self.preprocess()
  68. self.startDocument()
  69. firstTime = True
  70. for store, (ordered_subjects, subjects) in self._contexts.items():
  71. if not ordered_subjects:
  72. continue
  73. self._serialized = {}
  74. self.store = store
  75. self._subjects = subjects
  76. if self.default_context and store.identifier == self.default_context:
  77. self.write(self.indent() + "\n{")
  78. else:
  79. iri: Optional[str]
  80. if isinstance(store.identifier, BNode):
  81. iri = store.identifier.n3()
  82. else:
  83. # Show the full graph URI if a prefix for it doesn't already exist
  84. iri = self.get_pname(store.identifier, False)
  85. if iri is None:
  86. iri = store.identifier.n3()
  87. self.write(self.indent() + "\n%s {" % iri)
  88. self.depth += 1
  89. for subject in ordered_subjects:
  90. if self.isDone(subject):
  91. continue
  92. if firstTime:
  93. firstTime = False
  94. if self.statement(subject) and not firstTime:
  95. self.write("\n")
  96. self.depth -= 1
  97. self.write("}\n")
  98. self.endDocument()
  99. stream.write("\n".encode("latin-1"))