From 376c034858b238d4903a02399eb811b5c0387298 Mon Sep 17 00:00:00 2001 From: Iwan Aucamp Date: Mon, 19 Jun 2023 21:22:44 +0000 Subject: [PATCH] BREAKING CHANGE: Make Literal more standards compliant TBD checkpoint aucampia/20230609T2355-literal_datatype: checkpoint 20230817T000443 --- rdflib/plugins/parsers/notation3.py | 15 +- rdflib/plugins/parsers/ntriples.py | 15 +- rdflib/plugins/sparql/aggregates.py | 4 +- rdflib/plugins/sparql/operators.py | 24 +- rdflib/plugins/sparql/parser.py | 8 +- rdflib/plugins/sparql/results/jsonresults.py | 5 +- rdflib/plugins/sparql/results/xmlresults.py | 3 +- rdflib/term.py | 290 ++++++++++++------ rdflib/util.py | 28 +- test/test_graph/test_variants.py | 3 +- test/test_literal/test_literal.py | 16 +- test/utils/__init__.py | 15 +- test_reports/rdflib_sparql-HEAD.ttl | 2 + test_reports/rdflib_w3c_nquads-HEAD.ttl | 18 +- test_reports/rdflib_w3c_ntriples-HEAD.ttl | 14 +- test_reports/rdflib_w3c_rdfxml-HEAD.ttl | 2 + .../rdflib_w3c_rdfxml_non_normative-HEAD.ttl | 2 + test_reports/rdflib_w3c_sparql10-HEAD.ttl | 30 +- test_reports/rdflib_w3c_sparql11-HEAD.ttl | 34 +- test_reports/rdflib_w3c_trig-HEAD.ttl | 4 +- test_reports/rdflib_w3c_turtle-HEAD.ttl | 4 +- 21 files changed, 316 insertions(+), 220 deletions(-) diff --git a/rdflib/plugins/parsers/notation3.py b/rdflib/plugins/parsers/notation3.py index 290e7d04b..5eebb6cf0 100755 --- a/rdflib/plugins/parsers/notation3.py +++ b/rdflib/plugins/parsers/notation3.py @@ -1506,7 +1506,7 @@ def object( j, s = self.strconst(argstr, i, delim) - res.append(self._store.newLiteral(s)) # type: ignore[call-arg] # TODO FIXME + res.append(Literal(s)) return j else: return -1 @@ -1570,11 +1570,14 @@ def nodeOrLiteral(self, argstr: str, i: int, res: MutableSequence[Any]) -> int: i = m.end() lang = argstr[j + 1 : i] j = i - if argstr[j : j + 2] == "^^": + res.append(Literal(s, lang=lang)) + elif argstr[j : j + 2] == "^^": res2: typing.List[Any] = [] j = self.uri_ref2(argstr, j + 2, res2) # Read datatype URI dt = res2[0] - res.append(self._store.newLiteral(s, dt, lang)) + res.append(Literal(s, datatype=dt)) + else: + res.append(Literal(s)) return j else: return -1 @@ -1852,12 +1855,6 @@ def newBlankNode( bn = BNode(str(arg[0]).split("#").pop().replace("_", "b")) return bn - def newLiteral(self, s: str, dt: Optional[URIRef], lang: Optional[str]) -> Literal: - if dt: - return Literal(s, datatype=dt) - else: - return Literal(s, lang=lang) - def newList(self, n: typing.List[Any], f: Optional[Formula]) -> IdentifiedNode: nil = self.newSymbol("http://www.w3.org/1999/02/22-rdf-syntax-ns#nil") if not n: diff --git a/rdflib/plugins/parsers/ntriples.py b/rdflib/plugins/parsers/ntriples.py index 09656faff..6a41ba037 100644 --- a/rdflib/plugins/parsers/ntriples.py +++ b/rdflib/plugins/parsers/ntriples.py @@ -316,18 +316,17 @@ def nodeid( def literal(self) -> Union["te.Literal[False]", Literal]: if self.peek('"'): - lit, lang, dtype = self.eat(r_literal).groups() - if lang: + lit, lang, dtype = self.eat(r_literal).groups(default=None) + if TYPE_CHECKING: + # The pattern does not allow lit to be none. + assert lit is not None + if lang is not None: lang = lang - else: - lang = None - if dtype: + if dtype is not None: dtype = unquote(dtype) dtype = uriquote(dtype) dtype = URI(dtype) - else: - dtype = None - if lang and dtype: + if lang is not None and dtype is not None: raise ParseError("Can't have both a language and a datatype") lit = unquote(lit) return Literal(lit, lang, dtype) diff --git a/rdflib/plugins/sparql/aggregates.py b/rdflib/plugins/sparql/aggregates.py index d4a7d6592..5f42ab4ce 100644 --- a/rdflib/plugins/sparql/aggregates.py +++ b/rdflib/plugins/sparql/aggregates.py @@ -140,7 +140,9 @@ def update(self, row: FrozenBindings, aggregator: "Aggregator") -> None: pass def get_value(self) -> Literal: - return Literal(self.value, datatype=self.datatype) + return Literal( + self.value, datatype=XSD.integer if self.datatype is None else self.datatype + ) class Average(Accumulator): diff --git a/rdflib/plugins/sparql/operators.py b/rdflib/plugins/sparql/operators.py index 908b1d5c5..eea4ff471 100644 --- a/rdflib/plugins/sparql/operators.py +++ b/rdflib/plugins/sparql/operators.py @@ -290,18 +290,18 @@ def Builtin_CONCAT(expr: Expr, ctx) -> Literal: # dt/lang passed on only if they all match - dt = set(x.datatype for x in expr.arg if isinstance(x, Literal)) - # type error: Incompatible types in assignment (expression has type "Optional[str]", variable has type "Set[Optional[str]]") - dt = dt.pop() if len(dt) == 1 else None # type: ignore[assignment] - - lang = set(x.language for x in expr.arg if isinstance(x, Literal)) - # type error: error: Incompatible types in assignment (expression has type "Optional[str]", variable has type "Set[Optional[str]]") - lang = lang.pop() if len(lang) == 1 else None # type: ignore[assignment] - - # NOTE on type errors: this is because same variable is used for two incompatibel types - # type error: Argument "datatype" to "Literal" has incompatible type "Set[Any]"; expected "Optional[str]" [arg-type] - # type error: Argument "lang" to "Literal" has incompatible type "Set[Any]"; expected "Optional[str]" - return Literal("".join(string(x) for x in expr.arg), datatype=dt, lang=lang) # type: ignore[arg-type] + args = [x for x in expr.arg if isinstance(x, Literal)] + + if not args: + return Literal("") + + dt_set = set(x.datatype for x in args) + dt = dt_set.pop() + + lang_set = set(x.language for x in args) + lang = lang_set.pop() + + return Literal("".join(string(x) for x in expr.arg), datatype=dt, lang=lang) def _compatibleStrings(a: Literal, b: Literal) -> None: diff --git a/rdflib/plugins/sparql/parser.py b/rdflib/plugins/sparql/parser.py index 455377ed1..699f4827b 100644 --- a/rdflib/plugins/sparql/parser.py +++ b/rdflib/plugins/sparql/parser.py @@ -7,9 +7,7 @@ import re import sys -from typing import Any, BinaryIO, List -from typing import Optional as OptionalType -from typing import TextIO, Tuple, Union +from typing import Any, BinaryIO, List, TextIO, Tuple, Union from pyparsing import CaselessKeyword as Keyword # watch out :) from pyparsing import ( @@ -45,11 +43,11 @@ def neg(literal: rdflib.Literal) -> rdflib.Literal: return rdflib.Literal(-literal, datatype=literal.datatype) -def setLanguage(terms: Tuple[Any, OptionalType[str]]) -> rdflib.Literal: +def setLanguage(terms: Tuple[Any, str]) -> rdflib.Literal: return rdflib.Literal(terms[0], lang=terms[1]) -def setDataType(terms: Tuple[Any, OptionalType[str]]) -> rdflib.Literal: +def setDataType(terms: Tuple[Any, str]) -> rdflib.Literal: return rdflib.Literal(terms[0], datatype=terms[1]) diff --git a/rdflib/plugins/sparql/results/jsonresults.py b/rdflib/plugins/sparql/results/jsonresults.py index ecdb01247..4f667f839 100644 --- a/rdflib/plugins/sparql/results/jsonresults.py +++ b/rdflib/plugins/sparql/results/jsonresults.py @@ -3,6 +3,7 @@ import json from typing import IO, Any, Dict, Mapping, MutableSequence, Optional +from rdflib.namespace import XSD from rdflib.query import Result, ResultException, ResultParser, ResultSerializer from rdflib.term import BNode, Identifier, Literal, URIRef, Variable @@ -101,7 +102,9 @@ def parseJsonTerm(d: Dict[str, str]) -> Identifier: if t == "uri": return URIRef(d["value"]) elif t == "literal": - return Literal(d["value"], datatype=d.get("datatype"), lang=d.get("xml:lang")) + return Literal( + d["value"], datatype=d.get("datatype", XSD.string), lang=d.get("xml:lang") + ) elif t == "typed-literal": return Literal(d["value"], datatype=URIRef(d["datatype"])) elif t == "bnode": diff --git a/rdflib/plugins/sparql/results/xmlresults.py b/rdflib/plugins/sparql/results/xmlresults.py index 21ee3449d..f56b4ed7e 100644 --- a/rdflib/plugins/sparql/results/xmlresults.py +++ b/rdflib/plugins/sparql/results/xmlresults.py @@ -18,6 +18,7 @@ from xml.sax.saxutils import XMLGenerator from xml.sax.xmlreader import AttributesNSImpl +from rdflib.namespace import XSD from rdflib.query import Result, ResultException, ResultParser, ResultSerializer from rdflib.term import BNode, Identifier, Literal, URIRef, Variable @@ -131,7 +132,7 @@ def parseTerm(element: xml_etree.Element) -> Union[URIRef, Literal, BNode]: if tag == RESULTS_NS_ET + "literal": if text is None: text = "" - datatype = None + datatype = XSD.string lang = None if element.get("datatype", None): # type error: Argument 1 to "URIRef" has incompatible type "Optional[str]"; expected "str" diff --git a/rdflib/term.py b/rdflib/term.py index ff357d4de..3b31b7c4c 100644 --- a/rdflib/term.py +++ b/rdflib/term.py @@ -20,6 +20,8 @@ * Numerical Ranges """ +from __future__ import annotations + import re from fractions import Fraction @@ -51,11 +53,13 @@ Callable, Dict, List, + NoReturn, Optional, Tuple, Type, TypeVar, Union, + overload, ) from urllib.parse import urldefrag, urljoin, urlparse @@ -69,6 +73,8 @@ ) import rdflib + +# import rdflib.namespace as ns import rdflib.util from rdflib.compat import long_type @@ -97,8 +103,27 @@ def _is_valid_uri(uri: str) -> bool: _lang_tag_regex = compile("^[a-zA-Z]+(?:-[a-zA-Z0-9]+)*$") -def _is_valid_langtag(tag: str) -> bool: - return bool(_lang_tag_regex.match(tag)) +@overload +def _validate_language_tag(tag: None) -> NoReturn: + ... + + +@overload +def _validate_language_tag(tag: str) -> None: + ... + + +def _validate_language_tag(tag: Optional[str]) -> None: + if tag is None: + raise ValueError("language can not be None") + if not tag: + raise ValueError("language can not be empty string") + if not _lang_tag_regex.match(tag): + raise ValueError( + "language tag must match the pattern " + "^[a-zA-Z]+(?:-[a-zA-Z0-9]+)*$ " + f"but was {tag}" + ) def _is_valid_unicode(value: Union[str, bytes]) -> bool: @@ -370,6 +395,15 @@ def de_skolemize(self) -> "BNode": raise Exception("<%s> is not a skolem URI" % self) +# Cannot import Namespace/XSD because of circular dependencies +_XSD_PFX = "http://www.w3.org/2001/XMLSchema#" +_RDF_PFX = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" + +_RDF_LANGSTRING = URIRef(_RDF_PFX + "langString") + +_XSD_STRING = URIRef(_XSD_PFX + "string") + + class Genid(URIRef): __slots__ = () @@ -506,24 +540,48 @@ def skolemize( class Literal(Identifier): __doc__ = """ - - RDF 1.1's Literals Section: http://www.w3.org/TR/rdf-concepts/#section-Graph-Literal - - Literals are used for values such as strings, numbers, and dates. - - A literal in an RDF graph consists of two or three elements: - - * a lexical form, being a Unicode string, which SHOULD be in Normal Form C - * a datatype IRI, being an IRI identifying a datatype that determines how the lexical form maps to a literal value, and - * if and only if the datatype IRI is ``http://www.w3.org/1999/02/22-rdf-syntax-ns#langString``, a non-empty language tag. The language tag MUST be well-formed according to section 2.2.9 of `Tags for identifying languages `_. - - A literal is a language-tagged string if the third element is present. Lexical representations of language tags MAY be converted to lower case. The value space of language tags is always in lower case. + An `RDF Literal + `_, defined as: + + Literals are used for values such as strings, numbers, and dates. + + A literal in an `RDF graph + `__ consists of two + or three elements: + + - a lexical form, being a Unicode string, which *SHOULD* be in Normal + Form C, + - a datatype IRI, being an `IRI + `__ identifying a + datatype that determines how the lexical form maps to a `literal value + `__, and + - if and only if the `datatype IRI + `__ is + ``http://www.w3.org/1999/02/22-rdf-syntax-ns#langString``, a non-empty + language tag as defined by [`BCP47 + `__]. The language tag *MUST* be + well-formed according to `section 2.2.9 + `__ of + [`BCP47 `__]. + + A literal is a language-tagged string if the third element is present. + Lexical representations of language tags *MAY* be converted to lower case. + The value space of language tags is always in lower case. + + + + :param lexical_or_value: The lexical form or typed value of the literal. + :param datatype: The datatype IRI of the literal. + :param lang: The language tag of the literal. + :param normalize: Whether the value resulting from lexical-to-value mapping + should be normalized. If this is `None` then the behaviour will be + determined by ``rdflib.NORMALIZE_LITERALS``. --- - For valid XSD datatypes, the lexical form is optionally normalized - at construction time. Default behaviour is set by rdflib.NORMALIZE_LITERALS - and can be overridden by the normalize parameter to __new__ + For valid XSD datatypes, the lexical form is optionally normalized at + construction time. Default behaviour is set by ``rdflib.NORMALIZE_LITERALS`` and + can be overridden by the normalize parameter to `__new__`. Equality and hashing of Literals are done based on the lexical form, i.e.: @@ -559,13 +617,13 @@ class Literal(Identifier): >>> Literal('a').eq(1) # not fine, int incompatible with plain-lit NotImplemented - Greater-than/less-than ordering comparisons are also done in value - space, when compatible datatypes are used. Incompatible datatypes - are ordered by DT, or by lang-tag. For other nodes the ordering - is None < BNode < URIRef < Literal + Greater-than/less-than ordering comparisons are also done in value space, + when compatible datatypes are used. Incompatible datatypes are ordered by + DT, or by lang-tag. For other nodes the ordering is None < BNode < URIRef < + Literal - Any comparison with non-rdflib Node are "NotImplemented" - In PY3 this is an error. + Any comparison with non-rdflib Node are "NotImplemented" In PY3 this is an + error. >>> from rdflib import Literal, XSD >>> lit2006 = Literal('2006-01-01',datatype=XSD.date) @@ -599,10 +657,49 @@ class Literal(Identifier): _value: Any _language: Optional[str] # NOTE: _datatype should maybe be of type URIRef, and not optional. - _datatype: Optional[URIRef] + _datatype: URIRef _ill_typed: Optional[bool] __slots__ = ("_language", "_datatype", "_value", "_ill_typed") + # @overload + # def __new__( + # cls, + # lexical_or_value: Any, + # *, + # lang: str, + # normalize: Optional[bool] = None, + # ) -> Literal: + # ... + + # @overload + # def __new__( + # cls, + # lexical_or_value: Any, + # *, + # normalize: Optional[bool] = None, + # ) -> Literal: + # ... + + # @overload + # def __new__( + # cls, + # lexical_or_value: Any, + # *, + # datatype: str, + # normalize: Optional[bool] = None, + # ) -> Literal: + # ... + + # @overload + # def __new__( + # cls, + # lexical_or_value: str, + # lang: Optional[str], + # datatype: str, + # normalize: Optional[bool] = None, + # ) -> Literal: + # ... + def __new__( cls, lexical_or_value: Any, @@ -610,29 +707,31 @@ def __new__( datatype: Optional[str] = None, normalize: Optional[bool] = None, ) -> "Literal": - if lang == "": - lang = None # no empty lang-tags in RDF - normalize = normalize if normalize is not None else rdflib.NORMALIZE_LITERALS - - if lang is not None and datatype is not None: - raise TypeError( - "A Literal can only have one of lang or datatype, " - "per http://www.w3.org/TR/rdf-concepts/#section-Graph-Literal" - ) - - if lang is not None and not _is_valid_langtag(lang): - raise ValueError(f"'{str(lang)}' is not a valid language tag!") - if datatype is not None: datatype = URIRef(datatype) + if lang is not None or datatype == _RDF_LANGSTRING: + if lang is None: + raise ValueError(f"language cannot be None if datatype is {datatype}") + if not lang: + raise ValueError("language cannot be empty string") + if not _lang_tag_regex.match(lang): + raise ValueError( + "language tag must match the pattern " + "^[a-zA-Z]+(?:-[a-zA-Z0-9]+)*$ " + f"but was {lang}" + ) + + if datatype is None: + datatype = _RDF_LANGSTRING + value = None ill_typed: Optional[bool] = None if isinstance(lexical_or_value, Literal): # create from another Literal instance - lang = lang or lexical_or_value.language + lang = lang if lang is not None else lexical_or_value.language if datatype is not None: # override datatype value = _castLexicalToPython(lexical_or_value, datatype) @@ -640,34 +739,32 @@ def __new__( datatype = lexical_or_value.datatype value = lexical_or_value.value - elif isinstance(lexical_or_value, str) or isinstance(lexical_or_value, bytes): - # passed a string - # try parsing lexical form of datatyped literal + elif isinstance(lexical_or_value, (str, bytes, bytearray)): + if datatype is None: + datatype = _XSD_STRING + value = _castLexicalToPython(lexical_or_value, datatype) if datatype is not None and datatype in _toPythonMapping: # datatype is a recognized datatype IRI: # https://www.w3.org/TR/rdf11-concepts/#dfn-recognized-datatype-iris - dt_uri: URIRef = URIRef(datatype) - checker = _check_well_formed_types.get(dt_uri, _well_formed_by_value) + # dt_uri: URIRef = URIRef(datatype) + checker = _check_well_formed_types.get(datatype, _well_formed_by_value) well_formed = checker(lexical_or_value, value) ill_typed = ill_typed or (not well_formed) if value is not None and normalize: - _value, _datatype = _castPythonToLiteral(value, datatype) + _value, datatype = _castPythonToLiteral(value, datatype) if _value is not None and _is_valid_unicode(_value): lexical_or_value = _value else: # passed some python object value = lexical_or_value - _value, _datatype = _castPythonToLiteral(lexical_or_value, datatype) - - _datatype = None if _datatype is None else URIRef(_datatype) - - datatype = rdflib.util._coalesce(datatype, _datatype) + _value, datatype = _castPythonToLiteral(lexical_or_value, datatype) if _value is not None: lexical_or_value = _value - if datatype is not None: - lang = None + + if lang is not None and datatype != _RDF_LANGSTRING: + raise ValueError(f"Can't have a language tag with datatype {datatype}!") if isinstance(lexical_or_value, bytes): lexical_or_value = lexical_or_value.decode("utf-8") @@ -678,10 +775,21 @@ def __new__( if datatype in (_XSD_TOKEN,): lexical_or_value = _strip_and_collapse_whitespace(lexical_or_value) + return cls._make(lexical_or_value, lang, datatype, value, ill_typed) + + @classmethod + def _make( + cls, + lexical: Any, + lang: Optional[str], + datatype: URIRef, + value: Any, + ill_typed: Optional[bool], + ): try: - inst: Literal = str.__new__(cls, lexical_or_value) + inst: Literal = str.__new__(cls, lexical) except UnicodeDecodeError: - inst = str.__new__(cls, lexical_or_value, "utf-8") + inst = str.__new__(cls, lexical, "utf-8") inst._language = lang inst._datatype = datatype @@ -731,9 +839,13 @@ def language(self) -> Optional[str]: return self._language @property - def datatype(self) -> Optional[URIRef]: + def datatype(self) -> URIRef: return self._datatype + @property + def lexical(self) -> str: + return str(self) + def __reduce__( self, ) -> Tuple[Type["Literal"], Tuple[str, Union[str, None], Union[str, None]]]: @@ -1130,12 +1242,7 @@ def __gt__(self, other: Any) -> bool: # same language, same lexical form, check real dt # plain-literals come before xsd:string! if self.datatype != other.datatype: - if self.datatype is None: - return False - elif other.datatype is None: - return True - else: - return self.datatype > other.datatype + return self.datatype > other.datatype return False # they are the same @@ -1187,25 +1294,9 @@ def _comparable_to(self, other: Any) -> bool: rich-compare with this literal """ if isinstance(other, Literal): - if self.datatype is not None and other.datatype is not None: - # two datatyped literals - if ( - self.datatype not in XSDToPython - or other.datatype not in XSDToPython - ): - # non XSD DTs must match - if self.datatype != other.datatype: - return False - - else: - # xsd:string may be compared with plain literals - if not (self.datatype == _XSD_STRING and not other.datatype) or ( - other.datatype == _XSD_STRING and not self.datatype - ): - return False - - # if given lang-tag has to be case insensitive equal - if (self.language or "").lower() != (other.language or "").lower(): + if self.datatype not in _XSDToPython or other.datatype not in _XSDToPython: + # non XSD DTs must match + if self.datatype != other.datatype: return False return True @@ -1552,7 +1643,9 @@ def _literal_n3( encoded = self._quote_encode() - datatype = self.datatype + datatype: Optional[URIRef] = self.datatype + if datatype == _XSD_STRING: + datatype = None quoted_dt = None if datatype is not None: if qname_callback: @@ -1795,14 +1888,9 @@ def _well_formed_negative_integer(lexical: Union[str, bytes], value: Any) -> boo return isinstance(value, int) and value < 0 -# Cannot import Namespace/XSD because of circular dependencies -_XSD_PFX = "http://www.w3.org/2001/XMLSchema#" -_RDF_PFX = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" - _RDF_XMLLITERAL = URIRef(_RDF_PFX + "XMLLiteral") _RDF_HTMLLITERAL = URIRef(_RDF_PFX + "HTML") -_XSD_STRING = URIRef(_XSD_PFX + "string") _XSD_NORMALISED_STRING = URIRef(_XSD_PFX + "normalizedString") _XSD_TOKEN = URIRef(_XSD_PFX + "token") @@ -1911,25 +1999,23 @@ def _py2literal( obj: Any, pType: Any, # noqa: N803 castFunc: Optional[Callable[[Any], Any]], - dType: Optional[_StrT], -) -> Tuple[Any, Optional[_StrT]]: + dType: _StrT, +) -> Tuple[Any, _StrT]: if castFunc is not None: return castFunc(obj), dType - elif dType is not None: - return obj, dType else: - return obj, None + return obj, dType def _castPythonToLiteral( # noqa: N802 obj: Any, datatype: Optional[str] -) -> Tuple[Any, Optional[str]]: +) -> Tuple[Any, URIRef]: """ Casts a tuple of a python type and a special datatype URI to a tuple of the lexical value and a datatype URI (or None) """ castFunc: Optional[Callable[[Any], Union[str, bytes]]] # noqa: N806 - dType: Optional[str] # noqa: N806 + dType: URIRef # noqa: N806 for (pType, dType), castFunc in _SpecificPythonToXSDRules: # noqa: N806 if isinstance(obj, pType) and dType == datatype: return _py2literal(obj, pType, castFunc, dType) @@ -1937,7 +2023,7 @@ def _castPythonToLiteral( # noqa: N802 for pType, (castFunc, dType) in _GenericPythonToXSDRules: # noqa: N806 if isinstance(obj, pType): return _py2literal(obj, pType, castFunc, dType) - return obj, None # TODO: is this right for the fall through case? + return obj, _XSD_STRING # TODO: is this right for the fall through case? # Mappings from Python types to XSD datatypes and back (borrowed from sparta) @@ -1952,9 +2038,9 @@ def _castPythonToLiteral( # noqa: N802 # both map to the abstract integer type, # rather than some concrete bit-limited datatype _GenericPythonToXSDRules: List[ - Tuple[Type[Any], Tuple[Optional[Callable[[Any], Union[str, bytes]]], Optional[str]]] + Tuple[Type[Any], Tuple[Optional[Callable[[Any], Union[str, bytes]]], URIRef]] ] = [ - (str, (None, None)), + (str, (None, _XSD_STRING)), (float, (None, _XSD_DOUBLE)), (bool, (lambda i: str(i).lower(), _XSD_BOOLEAN)), (int, (None, _XSD_INTEGER)), @@ -1977,7 +2063,7 @@ def _castPythonToLiteral( # noqa: N802 _OriginalGenericPythonToXSDRules = list(_GenericPythonToXSDRules) _SpecificPythonToXSDRules: List[ - Tuple[Tuple[Type[Any], str], Optional[Callable[[Any], Union[str, bytes]]]] + Tuple[Tuple[Type[Any], URIRef], Optional[Callable[[Any], Union[str, bytes]]]] ] = [ ((date, _XSD_GYEAR), lambda val: val.strftime("%Y").zfill(4)), ((date, _XSD_GYEARMONTH), lambda val: val.strftime("%Y-%m").zfill(7)), @@ -1989,8 +2075,7 @@ def _castPythonToLiteral( # noqa: N802 _OriginalSpecificPythonToXSDRules = list(_SpecificPythonToXSDRules) -XSDToPython: Dict[Optional[str], Optional[Callable[[str], Any]]] = { - None: None, # plain literals map directly to value space +_XSDToPython: Dict[URIRef, Optional[Callable[[str], Any]]] = { URIRef(_XSD_PFX + "time"): parse_time, URIRef(_XSD_PFX + "date"): parse_date, URIRef(_XSD_PFX + "gYear"): parse_date, @@ -2042,9 +2127,9 @@ def _castPythonToLiteral( # noqa: N802 URIRef(_XSD_PFX + "unsignedByte"): _well_formed_unsignedbyte, } -_toPythonMapping: Dict[Optional[str], Optional[Callable[[str], Any]]] = {} # noqa: N816 +_toPythonMapping: Dict[URIRef, Optional[Callable[[str], Any]]] = {} # noqa: N816 -_toPythonMapping.update(XSDToPython) +_toPythonMapping.update(_XSDToPython) def _reset_bindings() -> None: @@ -2052,7 +2137,7 @@ def _reset_bindings() -> None: Reset lexical<->value space binding for `Literal` """ _toPythonMapping.clear() - _toPythonMapping.update(XSDToPython) + _toPythonMapping.update(_XSDToPython) _GenericPythonToXSDRules.clear() _GenericPythonToXSDRules.extend(_OriginalGenericPythonToXSDRules) @@ -2062,7 +2147,7 @@ def _reset_bindings() -> None: def _castLexicalToPython( # noqa: N802 - lexical: Union[str, bytes], datatype: Optional[URIRef] + lexical: Union[str, bytes], datatype: URIRef ) -> Any: """ Map a lexical form to the value-space for the given datatype @@ -2147,6 +2232,7 @@ def bind( from the pair (pythontype, datatype) if set to True or from the pythontype otherwise. False by default """ + datatype = URIRef(datatype) if datatype_specific and datatype is None: raise Exception("No datatype given for a datatype-specific binding") diff --git a/rdflib/util.py b/rdflib/util.py index 2442b3728..830abb46b 100644 --- a/rdflib/util.py +++ b/rdflib/util.py @@ -189,8 +189,16 @@ def from_n3( quotes = '"' value, rest = s.rsplit(quotes, 1) value = value[len(quotes) :] # strip leading quotes - datatype = None - language = None + # datatype = None + # language = None + + value = value.replace(r"\"", '"') + # unicode-escape interprets \xhh as an escape sequence, + # but n3 does not define it as such. + value = value.replace(r"\x", r"\\x") + # Hack: this should correctly handle strings with either native unicode + # characters, or \u1234 unicode escapes. + value = value.encode("raw-unicode-escape").decode("unicode-escape") # as a given datatype overrules lang-tag check for it first dtoffset = rest.rfind("^^") @@ -200,19 +208,15 @@ def from_n3( # see: http://www.w3.org/TR/2011/WD-turtle-20110809/ # #prod-turtle2-RDFLiteral datatype = from_n3(rest[dtoffset + 2 :], default, backend, nsm) + if not isinstance(datatype, str): + raise ValueError(f"datatype must be a string: {datatype!r}") + return rdflib.term.Literal(value, datatype=datatype) else: if rest.startswith("@"): language = rest[1:] # strip leading at sign - - value = value.replace(r"\"", '"') - # unicode-escape interprets \xhh as an escape sequence, - # but n3 does not define it as such. - value = value.replace(r"\x", r"\\x") - # Hack: this should correctly handle strings with either native unicode - # characters, or \u1234 unicode escapes. - value = value.encode("raw-unicode-escape").decode("unicode-escape") - # type error: Argument 3 to "Literal" has incompatible type "Union[Node, str, None]"; expected "Optional[str]" - return rdflib.term.Literal(value, language, datatype) # type: ignore[arg-type] + return rdflib.term.Literal(value, lang=language) + else: + return rdflib.term.Literal(value) elif s == "true" or s == "false": return rdflib.term.Literal(s == "true") elif ( diff --git a/test/test_graph/test_variants.py b/test/test_graph/test_variants.py index 09b2a156d..793076d8a 100644 --- a/test/test_graph/test_variants.py +++ b/test/test_graph/test_variants.py @@ -28,7 +28,6 @@ import rdflib.compare import rdflib.util from rdflib.graph import Dataset -from rdflib.namespace import XSD from rdflib.term import URIRef from rdflib.util import guess_format @@ -234,7 +233,7 @@ def test_variants(graph_variant: GraphVariants) -> None: # Stripping data types as different parsers (e.g. hext) have different # opinions of when a bare string is of datatype XSD.string or not. # Probably something that needs more investigation. - GraphHelper.strip_literal_datatypes(graph, {XSD.string}) + # GraphHelper.strip_literal_datatypes(graph, {XSD.string}) graph_variant.asserts.check(first_graph, graph) if first_graph is None: first_graph = graph diff --git a/test/test_literal/test_literal.py b/test/test_literal/test_literal.py index 074abe1e6..4a2301a82 100644 --- a/test/test_literal/test_literal.py +++ b/test/test_literal/test_literal.py @@ -88,8 +88,8 @@ class TestNewPT: @pytest.mark.parametrize( "lang, exception_type", [ - ({}, TypeError), - ([], TypeError), + ({}, ValueError), + ([], ValueError), (1, TypeError), (b"en", TypeError), ("999", ValueError), @@ -136,7 +136,7 @@ def test_cant_pass_invalid_lang( ("2147483648", XSD.integer, False), ("valid ASCII", XSD.string, False), pytest.param("هذا رجل ثلج⛄", XSD.string, False, id="snowman-ar"), - ("More ASCII", None, None), + ("More ASCII", None, False), ("Not a valid time", XSD.time, True), ("Not a valid date", XSD.date, True), ("7264666c6962", XSD.hexBinary, False), @@ -155,7 +155,10 @@ def test_ill_typed_literals( """ ill_typed has the correct value. """ - lit = Literal(lexical, datatype=datatype) + if datatype != RDF.langString: + lit = Literal(lexical, datatype=datatype) + else: + lit = Literal(lexical, datatype=datatype, lang="en") assert lit.ill_typed is is_ill_typed if is_ill_typed is False: # If the literal is not ill typed it should have a value associated with it. @@ -840,7 +843,6 @@ def unlexify(s: str) -> str: normal_l = Literal(s) assert str(normal_l) == s assert normal_l.toPython() == s - assert normal_l.datatype is None specific_l = Literal("--%s--" % s, datatype=datatype) assert str(specific_l) == lexify(s) @@ -998,8 +1000,8 @@ def unlexify(s: str) -> str: ), (lambda: Literal(Literal("1")), Literal("1")), ( - lambda: Literal(Literal("blue sky", "en")), - Literal("blue sky", "en"), + lambda: Literal(Literal("blue sky", lang="en")), + Literal("blue sky", lang="en"), ), ], ) diff --git a/test/utils/__init__.py b/test/utils/__init__.py index dc27251a3..44e2687f6 100644 --- a/test/utils/__init__.py +++ b/test/utils/__init__.py @@ -35,7 +35,7 @@ from rdflib import BNode, ConjunctiveGraph, Graph from rdflib.graph import Dataset from rdflib.plugin import Plugin -from rdflib.term import Identifier, Literal, Node, URIRef +from rdflib.term import Identifier, Node, URIRef PluginT = TypeVar("PluginT") @@ -368,19 +368,6 @@ def get_contexts(cgraph: ConjunctiveGraph) -> Dict[URIRef, Graph]: for id, lhs_context in lhs_contexts.items(): cls.assert_isomorphic(lhs_context, rhs_contexts[id], message) - @classmethod - def strip_literal_datatypes(cls, graph: Graph, datatypes: Set[URIRef]) -> None: - """ - Strips datatypes in the provided set from literals in the graph. - """ - for object in graph.objects(): - if not isinstance(object, Literal): - continue - if object.datatype is None: - continue - if object.datatype in datatypes: - object._datatype = None - def eq_(lhs, rhs, msg=None): """ diff --git a/test_reports/rdflib_sparql-HEAD.ttl b/test_reports/rdflib_sparql-HEAD.ttl index ee34f3c84..f4f7fef45 100644 --- a/test_reports/rdflib_sparql-HEAD.ttl +++ b/test_reports/rdflib_sparql-HEAD.ttl @@ -1,5 +1,7 @@ @prefix doap: . @prefix earl: . +@prefix rdf: . +@prefix xsd: . a doap:Project ; doap:description "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information."@en ; diff --git a/test_reports/rdflib_w3c_nquads-HEAD.ttl b/test_reports/rdflib_w3c_nquads-HEAD.ttl index 4eb9cbc62..0b4ac5dbf 100644 --- a/test_reports/rdflib_w3c_nquads-HEAD.ttl +++ b/test_reports/rdflib_w3c_nquads-HEAD.ttl @@ -1,5 +1,7 @@ @prefix doap: . @prefix earl: . +@prefix rdf: . +@prefix xsd: . a doap:Project ; doap:description "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information."@en ; @@ -11,7 +13,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -19,7 +21,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -27,7 +29,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -259,7 +261,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -307,7 +309,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -635,7 +637,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -643,7 +645,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -651,7 +653,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . diff --git a/test_reports/rdflib_w3c_ntriples-HEAD.ttl b/test_reports/rdflib_w3c_ntriples-HEAD.ttl index 7ca00f879..f42706d2a 100644 --- a/test_reports/rdflib_w3c_ntriples-HEAD.ttl +++ b/test_reports/rdflib_w3c_ntriples-HEAD.ttl @@ -1,5 +1,7 @@ @prefix doap: . @prefix earl: . +@prefix rdf: . +@prefix xsd: . a doap:Project ; doap:description "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information."@en ; @@ -11,7 +13,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -19,7 +21,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -27,7 +29,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -499,7 +501,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -507,7 +509,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -515,7 +517,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . diff --git a/test_reports/rdflib_w3c_rdfxml-HEAD.ttl b/test_reports/rdflib_w3c_rdfxml-HEAD.ttl index 9236a6878..96e495202 100644 --- a/test_reports/rdflib_w3c_rdfxml-HEAD.ttl +++ b/test_reports/rdflib_w3c_rdfxml-HEAD.ttl @@ -1,5 +1,7 @@ @prefix doap: . @prefix earl: . +@prefix rdf: . +@prefix xsd: . a doap:Project ; doap:description "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information."@en ; diff --git a/test_reports/rdflib_w3c_rdfxml_non_normative-HEAD.ttl b/test_reports/rdflib_w3c_rdfxml_non_normative-HEAD.ttl index 2d5bc8a2e..3cec240c2 100644 --- a/test_reports/rdflib_w3c_rdfxml_non_normative-HEAD.ttl +++ b/test_reports/rdflib_w3c_rdfxml_non_normative-HEAD.ttl @@ -1,5 +1,7 @@ @prefix doap: . @prefix earl: . +@prefix rdf: . +@prefix xsd: . a doap:Project ; doap:description "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information."@en ; diff --git a/test_reports/rdflib_w3c_sparql10-HEAD.ttl b/test_reports/rdflib_w3c_sparql10-HEAD.ttl index 78997b01c..6fff6cebf 100644 --- a/test_reports/rdflib_w3c_sparql10-HEAD.ttl +++ b/test_reports/rdflib_w3c_sparql10-HEAD.ttl @@ -1,5 +1,7 @@ @prefix doap: . @prefix earl: . +@prefix rdf: . +@prefix xsd: . a doap:Project ; doap:description "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information."@en ; @@ -475,7 +477,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -667,7 +669,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -691,7 +693,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -715,7 +717,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -739,7 +741,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -899,7 +901,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -1323,7 +1325,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -1331,7 +1333,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -1347,7 +1349,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -1355,7 +1357,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -1483,7 +1485,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -3379,7 +3381,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -3387,7 +3389,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -3515,7 +3517,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . diff --git a/test_reports/rdflib_w3c_sparql11-HEAD.ttl b/test_reports/rdflib_w3c_sparql11-HEAD.ttl index 6140fa914..44e3ecfcf 100644 --- a/test_reports/rdflib_w3c_sparql11-HEAD.ttl +++ b/test_reports/rdflib_w3c_sparql11-HEAD.ttl @@ -1,5 +1,7 @@ @prefix doap: . @prefix earl: . +@prefix rdf: . +@prefix xsd: . a doap:Project ; doap:description "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information."@en ; @@ -1763,7 +1765,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -1771,7 +1773,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -1787,7 +1789,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -1819,7 +1821,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -1867,7 +1869,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -1875,7 +1877,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -1963,7 +1965,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -2059,7 +2061,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -2067,7 +2069,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -2075,7 +2077,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -2083,7 +2085,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -2123,7 +2125,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -2131,7 +2133,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -2147,7 +2149,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -2155,7 +2157,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . @@ -2179,7 +2181,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:passed ] ; + earl:outcome earl:failed ] ; earl:subject ; earl:test . diff --git a/test_reports/rdflib_w3c_trig-HEAD.ttl b/test_reports/rdflib_w3c_trig-HEAD.ttl index 78a28d61c..8ca1ddb3c 100644 --- a/test_reports/rdflib_w3c_trig-HEAD.ttl +++ b/test_reports/rdflib_w3c_trig-HEAD.ttl @@ -1,5 +1,7 @@ @prefix doap: . @prefix earl: . +@prefix rdf: . +@prefix xsd: . a doap:Project ; doap:description "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information."@en ; @@ -1307,7 +1309,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:failed ] ; + earl:outcome earl:passed ] ; earl:subject ; earl:test . diff --git a/test_reports/rdflib_w3c_turtle-HEAD.ttl b/test_reports/rdflib_w3c_turtle-HEAD.ttl index 3496e7470..97ef5761c 100644 --- a/test_reports/rdflib_w3c_turtle-HEAD.ttl +++ b/test_reports/rdflib_w3c_turtle-HEAD.ttl @@ -1,5 +1,7 @@ @prefix doap: . @prefix earl: . +@prefix rdf: . +@prefix xsd: . a doap:Project ; doap:description "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information."@en ; @@ -1083,7 +1085,7 @@ earl:assertedBy ; earl:mode earl:automatic ; earl:result [ a earl:TestResult ; - earl:outcome earl:failed ] ; + earl:outcome earl:passed ] ; earl:subject ; earl:test .