RDFLib / rdflib

RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information.
https://rdflib.readthedocs.org
BSD 3-Clause "New" or "Revised" License
2.17k stars 555 forks source link

Round-tripping Turtle removes prefix declaration `rdf` and results in invalid Turtle #1755

Open edmondchuc opened 2 years ago

edmondchuc commented 2 years ago

Python version 3.10.0 RDFLib version 6.1.1


Round-tripping Turtle removes prefix declaration for rdf and results in an invalid Turtle file.

I have the following Turtle file meta.shacl.ttl:

@prefix meta-shacl: <https://w3id.org/tern/shacl/meta/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

meta-shacl:RDFSClass a sh:NodeShape ;
    rdfs:label "RDFS Class" ;
    sh:property [ a sh:PropertyShape ;
            sh:hasValue owl:Class ;
            sh:maxCount 1 ;
            sh:minCount 1 ;
            sh:path rdf:type ] ;
    sh:targetClass rdfs:Class .

Round-tripping it with rdflib:

from rdflib import Graph

g = Graph()
g.parse("meta.shacl.ttl")

g.serialize("meta.shacl.ttl", format="turtle")

Result:

@prefix meta-shacl: <https://w3id.org/tern/shacl/meta/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

meta-shacl:RDFSClass a sh:NodeShape ;
    rdfs:label "RDFS Class" ;
    sh:property [ a sh:PropertyShape ;
            sh:hasValue owl:Class ;
            sh:maxCount 1 ;
            sh:minCount 1 ;
            sh:path rdf:type ] ;
    sh:targetClass rdfs:Class .

If I attempt to call Graph.parse() again, I get:

.../venv/lib/python3.10/site-packages/rdflib/plugins/parsers/notation3.py", line 1646, in BadSyntax
    raise BadSyntax(self._thisDoc, self.lines, argstr, i, msg)
rdflib.plugins.parsers.notation3.BadSyntax: at line 13 of <>:
Bad syntax (Prefix "rdf:" not bound) at ^ in:
"...b'axCount 1 ;\n            sh:minCount 1 ;\n            sh:path '^b'rdf:type ] ;\n    sh:targetClass rdfs:Class .\n\n'"

My workaround is to refer to something else besides rdf:type in the rdf namespace.

@prefix meta-shacl: <https://w3id.org/tern/shacl/meta/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

meta-shacl:RDFSClass a sh:NodeShape ;
    rdfs:label "RDFS Class" ;
    sh:property [ a sh:PropertyShape ;
            sh:hasValue owl:Class ;
            sh:maxCount 1 ;
            sh:minCount 1 ;
            sh:path rdf:type ] ;
    sh:targetClass rdfs:Class .

<urn:local:property:1> a rdf:Property .

Round-tripping now works correctly because I am referring to rdf:Property.


I suspect there must be some logic in the Turtle serialiser where it ignores the "existence" of rdf:type because usually it is used as a in the predicate position. However, the serialiser should not ignore rdf:type when it is in the object position.

ghost commented 2 years ago

Python version 3.10.0 RDFLib version 6.1.1

Round-tripping Turtle removes prefix declaration for rdf and results in an invalid Turtle file.

Thanks for providing a useful example, I was able to confirm the issue. Glad you have a workaround to resolve your immediate problem.

I suspect there must be some logic in the Turtle serialiser where it ignores the "existence" of rdf:type because usually it is used as a in the predicate position. However, the serialiser should not ignore rdf:type when it is in the object position.

Happily, I can report that the repos master (6.2alpha) is free of the issue. Looks like LoC#260 of the turtle serializer may be implicated, in 6.2 it's been changed from:

if node in self.keywords:
    continue

to

if i == VERB and node in self.keywords:
    # predicate is a keyword
    continue

and locally making that one change to 6.1.1 enables your example to execute without issue.