Repository: RDFLib/rdflib

Issue:
rdf prefix is not emitted when saving to turtle when only rdf:type is used
whenusing rdflib 6.1.1 to convert the following jsonld to turtle:

```json
{
  "slots": [
    {
      "name": "type",
      "slot_uri": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
    }
  ],
  "@context": [
  {
      "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
      "@vocab": "https://w3id.org/linkml/",
      "slot_uri": {
         "@type": "@id"
      }
    }
  ]
}
```

the turtle emitted is:

```turtle
@prefix : <https://w3id.org/linkml/> .

[] :slots [ :name "type" ;
            :slot_uri rdf:type ] .
```

which is invalid, as it lacks a prefix declaration for rdf

The situation is resolved by either

 - adding another node to the graph in the rdf namespace, other than rdf:type
 - saving to a format like nt

This reproduces the error:

```python
JSONLD = """
{
  "slots": [
    {
      "name": "type",
      "slot_uri": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
    }
  ],
  "@context": [
  {
      "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
      "@vocab": "https://w3id.org/linkml/",
      "slot_uri": {
         "@type": "@id"
      }
    }
  ]
}"""

from rdflib import Graph

graph = Graph()
graph.parse(data=JSONLD, format="json-ld", prefix=False)
ttl_str = graph.serialize(format='turtle')
print(ttl_str)
graph.parse(data=ttl_str, format="turtle")
```

This is in contrast to other libs, e.g. Jena, which will produce valid turtle:

```turtle
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

_:b0    <https://w3id.org/linkml/slots>  _:b1 .

_:b1    <https://w3id.org/linkml/name>  "type" ;
        <https://w3id.org/linkml/slot_uri>  rdf:type .

```

Discussion:
aucampia: New tests

```console
$ .venv/bin/python3 -m pytest 'test/test_serializer.py::test_rdf_type' -rA --log-level DEBUG -vvv
============================================================================ test session starts ============================================================================
platform linux -- Python 3.8.12, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- /home/iwana/sw/d/github.com/iafork/rdflib/.venv/bin/python3
cachedir: .pytest_cache
rootdir: /home/iwana/sw/d/github.com/iafork/rdflib, configfile: tox.ini
plugins: subtests-0.5.0, cov-3.0.0, monkeytype-1.1.0
collected 10 items                                                                                                                                                          

test/test_serializer.py::test_rdf_type[turtle-0-False] PASSED                                                                                                         [ 10%]
test/test_serializer.py::test_rdf_type[turtle-1-True] PASSED                                                                                                          [ 20%]
test/test_serializer.py::test_rdf_type[turtle-2-False] PASSED                                                                                                         [ 30%]
test/test_serializer.py::test_rdf_type[n3-0-False] PASSED                                                                                                             [ 40%]
test/test_serializer.py::test_rdf_type[n3-1-True] PASSED                                                                                                              [ 50%]
test/test_serializer.py::test_rdf_type[n3-2-False] PASSED                                                                                                             [ 60%]
test/test_serializer.py::test_rdf_type[trig-0-False] PASSED                                                                                                           [ 70%]
test/test_serializer.py::test_rdf_type[trig-1-True] PASSED                                                                                                            [ 80%]
test/test_serializer.py::test_rdf_type[trig-2-False] PASSED                                                                                                           [ 90%]
test/test_serializer.py::test_rdf_type[trig-3-False] PASSED                                                                                                           [100%]

================================================================================== PASSES ===================================================================================
_______________________________________________________________________ test_rdf_type[turtle-0-False] _______________________________________________________________________
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO     root:test_serializer.py:41 data = @prefix eg: <example:> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

rdf:type eg:pred eg:obj .
_______________________________________________________________________ test_rdf_type[turtle-1-True] ________________________________________________________________________
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO     root:test_serializer.py:41 data = @prefix eg: <example:> .

eg:subj a eg:obj .
_______________________________________________________________________ test_rdf_type[turtle-2-False] _______________________________________________________________________
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO     root:test_serializer.py:41 data = @prefix eg: <example:> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

eg:subj eg:pred rdf:type .
_________________________________________________________________________ test_rdf_type[n3-0-False] _________________________________________________________________________
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO     root:test_serializer.py:41 data = @prefix eg: <example:> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

rdf:type eg:pred eg:obj .
_________________________________________________________________________ test_rdf_type[n3-1-True] __________________________________________________________________________
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO     root:test_serializer.py:41 data = @prefix eg: <example:> .

eg:subj a eg:obj .
_________________________________________________________________________ test_rdf_type[n3-2-False] _________________________________________________________________________
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO     root:test_serializer.py:41 data = @prefix eg: <example:> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

eg:subj eg:pred rdf:type .
________________________________________________________________________ test_rdf_type[trig-0-False] ________________________________________________________________________
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO     root:test_serializer.py:41 data = @prefix eg: <example:> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

eg:graph {
    rdf:type eg:pred eg:obj .
}
________________________________________________________________________ test_rdf_type[trig-1-True] _________________________________________________________________________
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO     root:test_serializer.py:41 data = @prefix eg: <example:> .

eg:graph {
    eg:subj a eg:obj .
}
________________________________________________________________________ test_rdf_type[trig-2-False] ________________________________________________________________________
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO     root:test_serializer.py:41 data = @prefix eg: <example:> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

eg:graph {
    eg:subj eg:pred rdf:type .
}
________________________________________________________________________ test_rdf_type[trig-3-False] ________________________________________________________________________
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO     root:test_serializer.py:41 data = @prefix eg: <example:> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

rdf:type {
    eg:subj eg:pred eg:obj .
}
========================================================================== short test summary info ==========================================================================
PASSED test/test_serializer.py::test_rdf_type[turtle-0-False]
PASSED test/test_serializer.py::test_rdf_type[turtle-1-True]
PASSED test/test_serializer.py::test_rdf_type[turtle-2-False]
PASSED test/test_serializer.py::test_rdf_type[n3-0-False]
PASSED test/test_serializer.py::test_rdf_type[n3-1-True]
PASSED test/test_serializer.py::test_rdf_type[n3-2-False]
PASSED test/test_serializer.py::test_rdf_type[trig-0-False]
PASSED test/test_serializer.py::test_rdf_type[trig-1-True]
PASSED test/test_serializer.py::test_rdf_type[trig-2-False]
PASSED test/test_serializer.py::test_rdf_type[trig-3-False]
============================================================================ 10 passed in 0.09s =============================================================================

```

Patch:
diff --git a/rdflib/plugins/serializers/turtle.py b/rdflib/plugins/serializers/turtle.py
index a62c05c4..a8933a7e 100644
--- a/rdflib/plugins/serializers/turtle.py
+++ b/rdflib/plugins/serializers/turtle.py
@@ -257,7 +257,8 @@ class TurtleSerializer(RecursiveSerializer):
     def preprocessTriple(self, triple):
         super(TurtleSerializer, self).preprocessTriple(triple)
         for i, node in enumerate(triple):
-            if node in self.keywords:
+            if i == VERB and node in self.keywords:
+                # predicate is a keyword
                 continue
             # Don't use generated prefixes for subjects and objects
             self.getQName(node, gen_prefix=(i == VERB))


Test patch:
diff --git a/test/test_roundtrip.py b/test/test_roundtrip.py
index f65bd4e2..8589483c 100644
--- a/test/test_roundtrip.py
+++ b/test/test_roundtrip.py
@@ -138,22 +138,6 @@ XFAILS = {
         reason="missing escaping: PCDATA invalid Char value 12 and 8",
         raises=SAXParseException,
     ),
-    ("n3", "rdf_prefix.jsonld"): pytest.mark.xfail(
-        reason="missing 'rdf:' prefix",
-        raises=BadSyntax,
-    ),
-    ("ttl", "rdf_prefix.jsonld"): pytest.mark.xfail(
-        reason="missing 'rdf:' prefix",
-        raises=BadSyntax,
-    ),
-    ("trig", "rdf_prefix.jsonld"): pytest.mark.xfail(
-        reason="missing 'rdf:' prefix",
-        raises=BadSyntax,
-    ),
-    ("turtle", "rdf_prefix.jsonld"): pytest.mark.xfail(
-        reason="missing 'rdf:' prefix",
-        raises=BadSyntax,
-    ),
 }
 
 # This is for files which can only be represented properly in one format
diff --git a/test/test_serializer.py b/test/test_serializer.py
index a86f49f4..fb9cdc11 100644
--- a/test/test_serializer.py
+++ b/test/test_serializer.py
@@ -1,8 +1,53 @@
+import logging
 import unittest
-from rdflib import Graph, URIRef, Literal
+from rdflib import RDF, Graph, Literal, Namespace, URIRef
 from tempfile import TemporaryDirectory
 from pathlib import Path, PurePath
 
+from typing import Tuple, cast
+
+import pytest
+import itertools
+
+from rdflib.graph import ConjunctiveGraph
+
+from .testutils import GraphHelper
+
+
+@pytest.mark.parametrize(
+    "format, tuple_index, is_keyword",
+    [
+        (format, tuple_index, keyword)
+        for format, (tuple_index, keyword) in itertools.product(
+            ["turtle", "n3", "trig"],
+            [
+                (0, False),
+                (1, True),
+                (2, False),
+            ],
+        )
+    ]
+    + [("trig", 3, False)],
+)
+def test_rdf_type(format: str, tuple_index: int, is_keyword: bool) -> None:
+    NS = Namespace("example:")
+    graph = ConjunctiveGraph()
+    graph.bind("eg", NS)
+    nodes = [NS.subj, NS.pred, NS.obj, NS.graph]
+    nodes[tuple_index] = RDF.type
+    quad = cast(Tuple[URIRef, URIRef, URIRef, URIRef], tuple(nodes))
+    graph.add(quad)
+    data = graph.serialize(format=format)
+    logging.info("data = %s", data)
+    assert NS in data
+    if is_keyword:
+        assert str(RDF) not in data
+    else:
+        assert str(RDF) in data
+    parsed_graph = ConjunctiveGraph()
+    parsed_graph.parse(data=data, format=format)
+    GraphHelper.assert_triple_sets_equals(graph, parsed_graph)
+
 
 class TestSerialize(unittest.TestCase):
     def setUp(self) -> None:
@@ -56,7 +101,3 @@ class TestSerialize(unittest.TestCase):
             graph_check = Graph()
             graph_check.parse(source=tfpath, format="nt")
         self.assertEqual(self.triple, next(iter(graph_check)))
-
-
-if __name__ == "__main__":
-    unittest.main()