trix.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. """
  2. A TriX parser for RDFLib
  3. """
  4. from __future__ import annotations
  5. from typing import TYPE_CHECKING, Any, Dict, List, NoReturn, Optional, Tuple
  6. from xml.sax import handler, make_parser
  7. from xml.sax.handler import ErrorHandler
  8. from rdflib.exceptions import ParserError
  9. from rdflib.graph import Graph
  10. from rdflib.namespace import Namespace
  11. from rdflib.parser import InputSource, Parser
  12. from rdflib.store import Store
  13. from rdflib.term import BNode, Identifier, Literal, URIRef
  14. if TYPE_CHECKING:
  15. # from xml.sax.expatreader import ExpatLocator
  16. from xml.sax.xmlreader import AttributesImpl, Locator, XMLReader
  17. __all__ = ["create_parser", "TriXHandler", "TriXParser"]
  18. TRIXNS = Namespace("http://www.w3.org/2004/03/trix/trix-1/")
  19. XMLNS = Namespace("http://www.w3.org/XML/1998/namespace")
  20. class TriXHandler(handler.ContentHandler):
  21. """An Sax Handler for TriX. See http://sw.nokia.com/trix/"""
  22. lang: Optional[str]
  23. datatype: Optional[str]
  24. def __init__(self, store: Store):
  25. self.store = store
  26. self.preserve_bnode_ids = False
  27. self.reset()
  28. def reset(self) -> None:
  29. self.bnode: Dict[str, BNode] = {}
  30. self.graph: Optional[Graph] = None
  31. self.triple: Optional[List[Identifier]] = None
  32. self.state = 0
  33. self.lang = None
  34. self.datatype = None
  35. # ContentHandler methods
  36. def setDocumentLocator(self, locator: Locator):
  37. self.locator = locator
  38. def startDocument(self) -> None:
  39. pass
  40. def startPrefixMapping(self, prefix: Optional[str], namespace: str) -> None:
  41. pass
  42. def endPrefixMapping(self, prefix: Optional[str]) -> None:
  43. pass
  44. def startElementNS(
  45. self, name: Tuple[Optional[str], str], qname, attrs: AttributesImpl
  46. ) -> None:
  47. if name[0] != str(TRIXNS):
  48. self.error(
  49. "Only elements in the TriX namespace are allowed. %s!=%s"
  50. % (name[0], TRIXNS)
  51. )
  52. if name[1].lower() == "trix":
  53. if self.state == 0:
  54. self.state = 1
  55. else:
  56. self.error("Unexpected TriX element")
  57. elif name[1] == "graph":
  58. if self.state == 1:
  59. self.state = 2
  60. else:
  61. self.error("Unexpected graph element")
  62. elif name[1] == "uri":
  63. if self.state == 2:
  64. # the context uri
  65. self.state = 3
  66. elif self.state == 4:
  67. # part of a triple
  68. pass
  69. else:
  70. self.error("Unexpected uri element")
  71. elif name[1] == "triple":
  72. if self.state == 2:
  73. if self.graph is None:
  74. # anonymous graph, create one with random bnode id
  75. self.graph = Graph(store=self.store)
  76. # start of a triple
  77. self.triple = []
  78. self.state = 4
  79. else:
  80. self.error("Unexpected triple element")
  81. elif name[1] == "typedLiteral":
  82. if self.state == 4:
  83. # part of triple
  84. self.lang = None
  85. self.datatype = None
  86. try:
  87. self.lang = attrs.getValue((str(XMLNS), "lang")) # type: ignore[arg-type, unused-ignore]
  88. except Exception:
  89. # language not required - ignore
  90. pass
  91. try:
  92. self.datatype = attrs.getValueByQName("datatype") # type: ignore[arg-type, unused-ignore]
  93. except KeyError:
  94. self.error("No required attribute 'datatype'")
  95. else:
  96. self.error("Unexpected typedLiteral element")
  97. elif name[1] == "plainLiteral":
  98. if self.state == 4:
  99. # part of triple
  100. self.lang = None
  101. self.datatype = None
  102. try:
  103. # type error: Argument 1 to "getValue" of "AttributesImpl" has incompatible type "Tuple[str, str]"; expected "str"
  104. self.lang = attrs.getValue((str(XMLNS), "lang")) # type: ignore[arg-type, unused-ignore]
  105. except Exception:
  106. # language not required - ignore
  107. pass
  108. else:
  109. self.error("Unexpected plainLiteral element")
  110. elif name[1] == "id":
  111. if self.state == 2:
  112. # the context uri
  113. self.state = 3
  114. elif self.state == 4:
  115. # part of triple
  116. pass
  117. else:
  118. self.error("Unexpected id element")
  119. else:
  120. self.error("Unknown element %s in TriX namespace" % name[1])
  121. self.chars = ""
  122. def endElementNS(self, name: Tuple[Optional[str], str], qname) -> None:
  123. if TYPE_CHECKING:
  124. assert self.triple is not None
  125. if name[0] != str(TRIXNS):
  126. self.error(
  127. "Only elements in the TriX namespace are allowed. %s!=%s"
  128. % (name[0], TRIXNS)
  129. )
  130. if name[1] == "uri":
  131. if self.state == 3:
  132. self.graph = Graph(
  133. store=self.store, identifier=URIRef(self.chars.strip())
  134. )
  135. self.state = 2
  136. elif self.state == 4:
  137. self.triple += [URIRef(self.chars.strip())]
  138. else:
  139. self.error(
  140. "Illegal internal self.state - This should never "
  141. + "happen if the SAX parser ensures XML syntax correctness"
  142. )
  143. elif name[1] == "id":
  144. if self.state == 3:
  145. self.graph = Graph(
  146. self.store, identifier=self.get_bnode(self.chars.strip())
  147. )
  148. self.state = 2
  149. elif self.state == 4:
  150. self.triple += [self.get_bnode(self.chars.strip())]
  151. else:
  152. self.error(
  153. "Illegal internal self.state - This should never "
  154. + "happen if the SAX parser ensures XML syntax correctness"
  155. )
  156. elif name[1] == "plainLiteral" or name[1] == "typedLiteral":
  157. if self.state == 4:
  158. self.triple += [
  159. Literal(self.chars, lang=self.lang, datatype=self.datatype)
  160. ]
  161. else:
  162. self.error(
  163. "This should never happen if the SAX parser "
  164. + "ensures XML syntax correctness"
  165. )
  166. elif name[1] == "triple":
  167. if self.state == 4:
  168. if len(self.triple) != 3:
  169. self.error(
  170. "Triple has wrong length, got %d elements: %s"
  171. % (len(self.triple), self.triple)
  172. )
  173. # type error: Item "None" of "Optional[Graph]" has no attribute "add"
  174. # type error: Argument 1 to "add" of "Graph" has incompatible type "List[Identifier]"; expected "Tuple[Node, Node, Node]"
  175. self.graph.add(self.triple) # type: ignore[union-attr, arg-type]
  176. # self.store.store.add(self.triple,context=self.graph)
  177. # self.store.addN([self.triple+[self.graph]])
  178. self.state = 2
  179. else:
  180. self.error(
  181. "This should never happen if the SAX parser "
  182. + "ensures XML syntax correctness"
  183. )
  184. elif name[1] == "graph":
  185. self.graph = None
  186. self.state = 1
  187. elif name[1].lower() == "trix":
  188. self.state = 0
  189. else:
  190. self.error("Unexpected close element")
  191. def get_bnode(self, label: str) -> BNode:
  192. if self.preserve_bnode_ids:
  193. bn = BNode(label)
  194. else:
  195. if label in self.bnode:
  196. bn = self.bnode[label]
  197. else:
  198. bn = BNode(label)
  199. self.bnode[label] = bn
  200. return bn
  201. def characters(self, content: str) -> None:
  202. self.chars += content
  203. def ignorableWhitespace(self, content) -> None:
  204. pass
  205. def processingInstruction(self, target, data) -> None:
  206. pass
  207. def error(self, message: str) -> NoReturn:
  208. locator = self.locator
  209. info = "%s:%s:%s: " % (
  210. locator.getSystemId(),
  211. locator.getLineNumber(),
  212. locator.getColumnNumber(),
  213. )
  214. raise ParserError(info + message)
  215. def create_parser(store: Store) -> XMLReader:
  216. parser = make_parser()
  217. try:
  218. # Workaround for bug in expatreader.py. Needed when
  219. # expatreader is trying to guess a prefix.
  220. # type error: "XMLReader" has no attribute "start_namespace_decl"
  221. parser.start_namespace_decl("xml", "http://www.w3.org/XML/1998/namespace") # type: ignore[attr-defined]
  222. except AttributeError:
  223. pass # Not present in Jython (at least)
  224. parser.setFeature(handler.feature_namespaces, 1)
  225. trix = TriXHandler(store)
  226. parser.setContentHandler(trix)
  227. parser.setErrorHandler(ErrorHandler())
  228. return parser
  229. class TriXParser(Parser):
  230. """A parser for TriX. See http://sw.nokia.com/trix/"""
  231. def __init__(self):
  232. pass
  233. def parse(self, source: InputSource, sink: Graph, **args: Any) -> None:
  234. assert (
  235. sink.store.context_aware
  236. ), "TriXParser must be given a context aware store."
  237. self._parser = create_parser(sink.store)
  238. content_handler = self._parser.getContentHandler()
  239. preserve_bnode_ids = args.get("preserve_bnode_ids", None)
  240. if preserve_bnode_ids is not None:
  241. # type error: ContentHandler has no attribute "preserve_bnode_ids"
  242. content_handler.preserve_bnode_ids = preserve_bnode_ids # type: ignore[attr-defined, unused-ignore]
  243. # We're only using it once now
  244. # content_handler.reset()
  245. # self._parser.reset()
  246. self._parser.parse(source)