nt.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. from __future__ import annotations
  2. import codecs
  3. import warnings
  4. from typing import IO, TYPE_CHECKING, Any, Optional, Tuple, Union
  5. from rdflib.graph import Graph
  6. from rdflib.serializer import Serializer
  7. from rdflib.term import Literal
  8. if TYPE_CHECKING:
  9. from rdflib.graph import _TripleType
  10. """
  11. N-Triples RDF graph serializer for RDFLib.
  12. See <http://www.w3.org/TR/rdf-testcases/#ntriples> for details about the
  13. format.
  14. """
  15. __all__ = ["NTSerializer"]
  16. class NTSerializer(Serializer):
  17. """Serializes RDF graphs to NTriples format."""
  18. def __init__(self, store: Graph):
  19. Serializer.__init__(self, store)
  20. def serialize(
  21. self,
  22. stream: IO[bytes],
  23. base: Optional[str] = None,
  24. encoding: Optional[str] = "utf-8",
  25. **kwargs: Any,
  26. ) -> None:
  27. if base is not None:
  28. warnings.warn("NTSerializer does not support base.")
  29. if encoding != "utf-8":
  30. warnings.warn(
  31. "NTSerializer always uses UTF-8 encoding. "
  32. f"Given encoding was: {encoding}"
  33. )
  34. for triple in self.store:
  35. stream.write(_nt_row(triple).encode())
  36. class NT11Serializer(NTSerializer):
  37. """Serializes RDF graphs to RDF 1.1 NTriples format.
  38. Exactly like nt - only utf8 encoded.
  39. """
  40. def __init__(self, store: Graph):
  41. Serializer.__init__(self, store) # default to utf-8
  42. def _nt_row(triple: _TripleType) -> str:
  43. if isinstance(triple[2], Literal):
  44. return "%s %s %s .\n" % (
  45. triple[0].n3(),
  46. triple[1].n3(),
  47. _quoteLiteral(triple[2]),
  48. )
  49. else:
  50. return "%s %s %s .\n" % (triple[0].n3(), triple[1].n3(), triple[2].n3())
  51. def _quoteLiteral(l_: Literal) -> str: # noqa: N802
  52. """A simpler version of term.Literal.n3()"""
  53. encoded = _quote_encode(l_)
  54. if l_.language:
  55. if l_.datatype:
  56. raise Exception("Literal has datatype AND language!")
  57. return "%s@%s" % (encoded, l_.language)
  58. elif l_.datatype:
  59. return "%s^^<%s>" % (encoded, l_.datatype)
  60. else:
  61. return "%s" % encoded
  62. def _quote_encode(l_: str) -> str:
  63. return '"%s"' % l_.replace("\\", "\\\\").replace("\n", "\\n").replace(
  64. '"', '\\"'
  65. ).replace("\r", "\\r")
  66. def _nt_unicode_error_resolver(
  67. err: UnicodeError,
  68. ) -> Tuple[Union[str, bytes], int]:
  69. """
  70. Do unicode char replaces as defined in https://www.w3.org/TR/2004/REC-rdf-testcases-20040210/#ntrip_strings
  71. """
  72. def _replace_single(c):
  73. c = ord(c)
  74. fmt = "\\u%04X" if c <= 0xFFFF else "\\U%08X"
  75. return fmt % c
  76. # type error: "UnicodeError" has no attribute "object"
  77. # type error: "UnicodeError" has no attribute "start"
  78. # type error: "UnicodeError" has no attribute "end"
  79. string = err.object[err.start : err.end] # type: ignore[attr-defined]
  80. # type error: "UnicodeError" has no attribute "end"
  81. return "".join(_replace_single(c) for c in string), err.end # type: ignore[attr-defined]
  82. codecs.register_error("_rdflib_nt_escape", _nt_unicode_error_resolver)