diff --git a/core/rio/turtle/src/main/java/org/eclipse/rdf4j/rio/turtle/TurtleWriter.java b/core/rio/turtle/src/main/java/org/eclipse/rdf4j/rio/turtle/TurtleWriter.java index 75460b0895..8fbe85929d 100644 --- a/core/rio/turtle/src/main/java/org/eclipse/rdf4j/rio/turtle/TurtleWriter.java +++ b/core/rio/turtle/src/main/java/org/eclipse/rdf4j/rio/turtle/TurtleWriter.java @@ -305,6 +305,11 @@ protected void handleStatementInternal(Statement st, boolean endRDFCalled, boole try { if (inlineBNodes) { + if (pred.equals(RDF.TYPE) && obj.equals(RDF.LIST) && subj instanceof BNode + && isWellFormedCollection(subj)) { + // skip explicit rdf:type rdf:List for collections we will inline as Turtle lists + return; + } if ((pred.equals(RDF.FIRST) || pred.equals(RDF.REST)) && isWellFormedCollection(subj)) { // we only use list shorthand syntax if the collection is considered well-formed handleList(st, canShortenObjectBNode); @@ -357,6 +362,9 @@ private boolean isWellFormedCollection(Resource subj) { // second rdf:rest statement on same subject is invalid. return false; } + } else if (pred.equals(RDF.TYPE) && RDF.LIST.equals(st.getObject())) { + // allow explicit rdf:type rdf:List + continue; } else { // non-list-structure statement connected to collection subject blank node return false; diff --git a/core/rio/turtle/src/test/java/org/eclipse/rdf4j/rio/turtle/TurtleWriterTest.java b/core/rio/turtle/src/test/java/org/eclipse/rdf4j/rio/turtle/TurtleWriterTest.java index c6069c87c5..b4647be4c5 100644 --- a/core/rio/turtle/src/test/java/org/eclipse/rdf4j/rio/turtle/TurtleWriterTest.java +++ b/core/rio/turtle/src/test/java/org/eclipse/rdf4j/rio/turtle/TurtleWriterTest.java @@ -15,11 +15,17 @@ import java.io.StringReader; import java.io.StringWriter; +import java.util.List; +import org.eclipse.rdf4j.model.BNode; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Model; import org.eclipse.rdf4j.model.impl.DynamicModelFactory; +import org.eclipse.rdf4j.model.impl.TreeModel; import org.eclipse.rdf4j.model.util.Models; +import org.eclipse.rdf4j.model.util.RDFCollections; +import org.eclipse.rdf4j.model.util.Values; +import org.eclipse.rdf4j.model.vocabulary.RDF; import org.eclipse.rdf4j.model.vocabulary.RDFS; import org.eclipse.rdf4j.rio.RDFFormat; import org.eclipse.rdf4j.rio.Rio; @@ -390,6 +396,37 @@ public void testBNodeValuesInList() throws Exception { assertTrue(Models.isomorphic(expected, actual)); } + @Test + public void testRdfCollectionsListNotFullyInlined() throws Exception { + String namespace = "http://example.com/ns#"; + IRI cities = Values.iri(namespace, "Cities"); + IRI listPredicate = Values.iri(namespace, "list"); + BNode listHead = vf.createBNode("n1"); + + Model model = new TreeModel(); + model.setNamespace("ex", namespace); + model.setNamespace("rdf", RDF.NAMESPACE); + + RDFCollections.asRDF(List.of( + Values.iri(namespace, "NewYork"), + Values.iri(namespace, "Rio"), + Values.iri(namespace, "Tokyo")), listHead, model); + model.add(cities, listPredicate, listHead); + + WriterConfig config = new WriterConfig(); + config.set(BasicWriterSettings.INLINE_BLANK_NODES, true); + config.set(BasicWriterSettings.PRETTY_PRINT, true); + + StringWriter stringWriter = new StringWriter(); + Rio.write(model, stringWriter, RDFFormat.TURTLE, config); + + String expected = String.join("\n", "@prefix ex: .", + "@prefix rdf: .", + "", "ex:Cities ex:list (ex:NewYork ex:Rio ex:Tokyo) .", ""); + + assertThat(stringWriter.toString()).isEqualTo(expected); + } + @Test public void testBNodeValuesInList2() throws Exception { String data = "" +