tsvresults.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. """
  2. This implements the Tab Separated SPARQL Result Format
  3. It is implemented with pyparsing, reusing the elements from the SPARQL Parser
  4. """
  5. from __future__ import annotations
  6. import codecs
  7. import typing
  8. from typing import IO, Union
  9. from pyparsing import (
  10. FollowedBy,
  11. LineEnd,
  12. Literal,
  13. Optional,
  14. ParserElement,
  15. Suppress,
  16. ZeroOrMore,
  17. )
  18. from rdflib.plugins.sparql.parser import (
  19. BLANK_NODE_LABEL,
  20. IRIREF,
  21. LANGTAG,
  22. STRING_LITERAL1,
  23. STRING_LITERAL2,
  24. BooleanLiteral,
  25. NumericLiteral,
  26. Var,
  27. )
  28. from rdflib.plugins.sparql.parserutils import Comp, CompValue, Param
  29. from rdflib.query import Result, ResultParser
  30. from rdflib.term import BNode, Identifier, URIRef
  31. from rdflib.term import Literal as RDFLiteral
  32. ParserElement.set_default_whitespace_chars(" \n")
  33. String = STRING_LITERAL1 | STRING_LITERAL2
  34. RDFLITERAL = Comp(
  35. "literal",
  36. Param("string", String)
  37. + Optional(
  38. Param("lang", LANGTAG.leave_whitespace())
  39. | Literal("^^").leave_whitespace()
  40. + Param("datatype", IRIREF).leave_whitespace()
  41. ),
  42. )
  43. NONE_VALUE = object()
  44. EMPTY = FollowedBy(LineEnd()) | FollowedBy("\t")
  45. EMPTY.set_parse_action(lambda x: NONE_VALUE)
  46. TERM = RDFLITERAL | IRIREF | BLANK_NODE_LABEL | NumericLiteral | BooleanLiteral
  47. ROW = (EMPTY | TERM) + ZeroOrMore(Suppress("\t") + (EMPTY | TERM))
  48. ROW.parse_with_tabs()
  49. HEADER = Var + ZeroOrMore(Suppress("\t") + Var)
  50. HEADER.parse_with_tabs()
  51. class TSVResultParser(ResultParser):
  52. """Parses SPARQL TSV results into a Result object."""
  53. # type error: Signature of "parse" incompatible with supertype "ResultParser" [override]
  54. def parse(self, source: IO, content_type: typing.Optional[str] = None) -> Result: # type: ignore[override]
  55. if isinstance(source.read(0), bytes):
  56. # if reading from source returns bytes do utf-8 decoding
  57. # type error: Incompatible types in assignment (expression has type "StreamReader", variable has type "IO[Any]")
  58. source = codecs.getreader("utf-8")(source) # type: ignore[assignment]
  59. r = Result("SELECT")
  60. header = source.readline()
  61. r.vars = list(HEADER.parse_string(header.strip(), parse_all=True))
  62. r.bindings = []
  63. while True:
  64. line = source.readline()
  65. if not line:
  66. break
  67. line = line.strip("\n")
  68. if line == "":
  69. continue
  70. row = ROW.parse_string(line, parse_all=True)
  71. this_row_dict = {}
  72. for var, val_read in zip(r.vars, row):
  73. val = self.convertTerm(val_read)
  74. if val is None:
  75. # Skip unbound vars
  76. continue
  77. this_row_dict[var] = val
  78. # Preserve solution row cardinality, including fully-unbound rows.
  79. r.bindings.append(this_row_dict)
  80. return r
  81. def convertTerm(
  82. self, t: Union[object, RDFLiteral, BNode, CompValue, URIRef]
  83. ) -> typing.Optional[Identifier]:
  84. if t is NONE_VALUE:
  85. return None
  86. if isinstance(t, CompValue):
  87. if t.name == "literal":
  88. return RDFLiteral(t.string, lang=t.lang, datatype=t.datatype)
  89. else:
  90. raise Exception("I dont know how to handle this: %s" % (t,))
  91. elif isinstance(t, (RDFLiteral, BNode, URIRef)):
  92. return t
  93. else:
  94. raise ValueError(f"Unexpected type {type(t)} found in TSV result")