diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLParser.java b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLParser.java index 917fa0793..b7db9779d 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLParser.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLParser.java @@ -174,7 +174,6 @@ private void handleEndElement(String uri, String localName, String qName) { IRI predicate = ctx.predicateStack.pop(); Resource subject = ctx.subjectStack.peek(); String datatypeUri = ctx.datatypeStack.isEmpty() ? null : ctx.datatypeStack.pop(); - //emitLiteral(subject, predicate, text, datatypeUri); String lang = ctx.langStack.isEmpty() ? null : ctx.langStack.peek(); emitter.emitLiteral(subject, predicate, text, datatypeUri, lang); return; @@ -213,7 +212,8 @@ private void updateLang(Attributes attrs) { private void updateDatatype(Attributes attrs) { String datatype = attrs.getValue(RDF.type.getNamespace(), "datatype"); if (datatype != null) { - ctx.datatypeStack.push(datatype); + String expanded = expandQNameFromQName(datatype); + ctx.datatypeStack.push(expanded); } } diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLUtils.java b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLUtils.java index e31e09eff..011e8c0d0 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLUtils.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLUtils.java @@ -48,6 +48,21 @@ public static Optional resolveDatatype(String datatypeUri) { return Optional.empty(); } + /** + * Expands a QName string (e.g. "xsd:integer") into a full URI if known. + * Currently supports "xsd:" β†’ XML Schema namespace. + * + * @param qname the QName string + * @return expanded full URI if a known prefix, otherwise returns qname unchanged + */ + public static String expandQNameFromQName(String qname) { + if (qname == null) return null; + if (qname.startsWith("xsd:")) { + return "http://www.w3.org/2001/XMLSchema#" + qname.substring("xsd:".length()); + } + return qname; + } + /** * Extracts a subject resource from RDF/XML attributes. * Supports rdf:about, rdf:nodeID, rdf:ID. diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/parser/turtle/TurtleListenerImpl.java b/src/main/java/fr/inria/corese/core/next/impl/io/parser/turtle/TurtleListenerImpl.java index f89eea896..c1192583c 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/parser/turtle/TurtleListenerImpl.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/parser/turtle/TurtleListenerImpl.java @@ -117,11 +117,17 @@ private String resolveIRI(String raw) { * @return the stripped string */ private String stripQuotes(String text) { - if (text == null || text.length() < 2) + if (text == null || text.length() < 2) { return text; + } + if (text.startsWith("\"\"\"") && text.endsWith("\"\"\"") && text.length() >= 6) { + return text.substring(3, text.length() - 3); + } + if (text.startsWith("'''") && text.endsWith("'''") && text.length() >= 6) { + return text.substring(3, text.length() - 3); + } if ((text.startsWith("\"") && text.endsWith("\"")) || - (text.startsWith("'''") && text.endsWith("'''")) || - (text.startsWith("\"\"\"") && text.endsWith("\"\"\""))) { + (text.startsWith("'") && text.endsWith("'"))) { return text.substring(1, text.length() - 1); } return text; diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractGraphSerializer.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractGraphSerializer.java index 1d9b28ce0..12a72c708 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractGraphSerializer.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractGraphSerializer.java @@ -322,18 +322,23 @@ protected void writeValue(Writer writer, Value value) throws IOException { currentlyWritingBlankNodes.add(bNode); boolean handled = false; - if (option instanceof AbstractSerializerOption && getTFamilyOption().useCollections() && bNode.isBNode()) { - handled = writeRDFList(writer, bNode); - } - if (!handled && option instanceof AbstractSerializerOption && getTFamilyOption().getBlankNodeStyle() == BlankNodeStyleEnum.ANONYMOUS && bNode.isBNode()) { // getBlankNodeStyle is on AbstractTFamilyConfig - List properties = model.stream() - .filter(stmt -> stmt.getSubject().equals(bNode)) - .toList(); + boolean isSubject = model.stream().anyMatch(stmt -> stmt.getSubject().equals(bNode)); + + if (!isSubject && option instanceof AbstractSerializerOption) { + if (getTFamilyOption().useCollections() && bNode.isBNode()) { + handled = writeRDFList(writer, bNode); + } + + if (!handled && getTFamilyOption().getBlankNodeStyle() == BlankNodeStyleEnum.ANONYMOUS && bNode.isBNode()) { + List properties = model.stream() + .filter(stmt -> stmt.getSubject().equals(bNode)) + .toList(); - if (!properties.isEmpty()) { - writeInlineBlankNode(writer, properties); - handled = true; + if (!properties.isEmpty()) { + writeInlineBlankNode(writer, properties); + handled = true; + } } } @@ -343,10 +348,12 @@ protected void writeValue(Writer writer, Value value) throws IOException { currentlyWritingBlankNodes.remove(bNode); } else { - throw new IllegalArgumentException("Unsupported value type for " + getFormatName() + " serialization: " + value.getClass().getName()); + throw new IllegalArgumentException("Unsupported value type for " + getFormatName() + + " serialization: " + value.getClass().getName()); } } + /** * Writes an {@link IRI} to the writer. * Attempts to use a prefixed name if possible, otherwise writes the full IRI in angle brackets. @@ -661,48 +668,8 @@ protected Set precomputeInlineBlankNodesAndLists() { if (stmt.getSubject().isBNode()) { Resource bNodeSubject = stmt.getSubject(); if (tFamilyConfig.useCollections() && isRDFListHead(bNodeSubject)) { - Resource current = bNodeSubject; - Set listNodes = new HashSet<>(); - Set visitedInPrecomp = new HashSet<>(); - boolean isList = true; - while (current != null && current.isBNode() && !visitedInPrecomp.contains(current)) { - visitedInPrecomp.add(current); - listNodes.add(current); - final Resource finalCurrentForLambda = current; - List listProps = model.stream() - .filter(s -> s.getSubject().equals(finalCurrentForLambda)) - .toList(); - - if (listProps.size() != 2) { - isList = false; - break; - } - - Optional first = listProps.stream() - .filter(s -> s.getPredicate().stringValue().equals(SerializationConstants.RDF_FIRST)) - .map(Statement::getObject) - .findFirst(); - - Optional rest = listProps.stream() - .filter(s -> s.getPredicate().stringValue().equals(SerializationConstants.RDF_REST)) - .map(Statement::getObject) - .findFirst(); - - if (!first.isPresent() || !rest.isPresent()) { - isList = false; - break; - } - - if (rest.get().stringValue().equals(SerializationConstants.RDF_NIL)) { - current = null; - } else if (rest.get().isBNode()) { - current = (Resource) rest.get(); - } else { - isList = false; - break; - } - } - if (isList && current == null) { + Set listNodes = detectListNodes(bNodeSubject); + if (!listNodes.isEmpty()) { precomputed.addAll(listNodes); } } @@ -716,7 +683,10 @@ protected Set precomputeInlineBlankNodesAndLists() { s.getPredicate().stringValue().equals(SerializationConstants.RDF_REST) ); - if (!properties.isEmpty() && !isPartOfList) { + boolean usedAsTopLevelSubject = model.stream() + .anyMatch(s -> s.getSubject().equals(bNodeSubject)); + + if (!properties.isEmpty() && !isPartOfList && !usedAsTopLevelSubject) { precomputed.add(bNodeSubject); } } @@ -725,6 +695,55 @@ protected Set precomputeInlineBlankNodesAndLists() { return precomputed; } + /** + * Traverses an RDF list starting from the given head and collects all list nodes. + * + * @param head the blank node that may be the head of an RDF list + * @return the set of blank nodes that form the list, or an empty set if not a valid list + */ + private Set detectListNodes(Resource head) { + Set listNodes = new HashSet<>(); + Set visited = new HashSet<>(); + Resource current = head; + + while (current != null && current.isBNode() && !visited.contains(current)) { + visited.add(current); + listNodes.add(current); + + final Resource finalCurrent = current; + List props = model.stream() + .filter(s -> s.getSubject().equals(finalCurrent)) + .toList(); + + if (props.size() != 2) { + return Collections.emptySet(); + } + + Optional first = props.stream() + .filter(s -> s.getPredicate().stringValue().equals(SerializationConstants.RDF_FIRST)) + .map(Statement::getObject) + .findFirst(); + + Optional rest = props.stream() + .filter(s -> s.getPredicate().stringValue().equals(SerializationConstants.RDF_REST)) + .map(Statement::getObject) + .findFirst(); + + if (!first.isPresent() || !rest.isPresent()) { + return Collections.emptySet(); + } + + if (rest.get().stringValue().equals(SerializationConstants.RDF_NIL)) { + current = null; + } else if (rest.get().isBNode()) { + current = (Resource) rest.get(); + } else { + return Collections.emptySet(); + } + } + return current == null ? listNodes : Collections.emptySet(); + } + /** * Checks if a given blank node is the head of an RDF list. * diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/jsonld/TitaniumRDFDatasetSerializationAdapter.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/jsonld/TitaniumRDFDatasetSerializationAdapter.java index d02fce12a..6b776f1cd 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/jsonld/TitaniumRDFDatasetSerializationAdapter.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/jsonld/TitaniumRDFDatasetSerializationAdapter.java @@ -38,6 +38,8 @@ import fr.inria.corese.core.next.impl.common.vocabulary.RDF; import fr.inria.corese.core.next.impl.common.vocabulary.XSD; import fr.inria.corese.core.next.impl.exception.SerializationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Adapter class from Model to RdfDataset for usage in the JSON-LD serialization process using the titanium library. @@ -45,6 +47,7 @@ */ public class TitaniumRDFDatasetSerializationAdapter implements RdfDataset { + private final static Logger logger = LoggerFactory.getLogger(TitaniumRDFDatasetSerializationAdapter.class); private Model model; /** @@ -58,17 +61,18 @@ public TitaniumRDFDatasetSerializationAdapter(Model model) { @Override public RdfGraph getDefaultGraph() { - return new RdfGraph() { + RdfGraph resultGraph = new RdfGraph() { @Override public boolean contains(RdfTriple triple) { - return model.contains(toResource(triple.getSubject()), toIRI(triple.getPredicate()), toValue(triple.getObject())); + return model.contains(toResource(triple.getSubject()), toIRI(triple.getPredicate()), toValue(triple.getObject()), (Resource) null); } @Override public List toList() { - return model.stream().map(TitaniumRDFDatasetSerializationAdapter.this::toRdfTriple).toList(); + return model.filter(null, null, null, (Resource) null).stream().map(TitaniumRDFDatasetSerializationAdapter.this::toRdfTriple).toList(); } }; + return resultGraph; } @Override @@ -187,27 +191,15 @@ public RdfValue getObject() { * @return the converted resource */ private RdfResource toRdfResource(Resource resource) { - if (resource != null && (! (resource.isBNode() || resource.isIRI()))) { - throw new SerializationException("Unknown resource type " + resource, "JSON-LD"); - } else if (resource == null) { + if (resource == null) { return null; + } else if (resource.isIRI()) { + return toRdfIRI((IRI) resource); + } else if (resource.isBNode()) { + return toRdfBlankNode((BNode) resource); + } else { + throw new SerializationException("Unknown resource type " + resource, "JSON-LD"); } - return new RdfResource() { - @Override - public boolean isIRI() { - return resource.isIRI(); - } - - @Override - public boolean isBlankNode() { - return resource.isBNode(); - } - - @Override - public String getValue() { - return resource.stringValue(); - } - }; } /** @@ -258,7 +250,7 @@ public boolean isBlankNode() { } @Override public String getValue() { - return bnode.stringValue(); + return "_:" + bnode.stringValue(); } }; } @@ -290,7 +282,7 @@ public String getDatatype() { ) { return literal.getDatatype().stringValue(); } else if (literal.getLanguage().isPresent()) { - return RDF.langString.getIRI().stringValue(); + return "rdf:langString"; // Titanium JSONLD expect the langstring datatype to be in this format ... } else { return XSD.xsdString.getIRI().stringValue(); } diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializer.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializer.java index 326725de9..04c33134b 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializer.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializer.java @@ -143,16 +143,20 @@ protected String escapeLiteralString(String value) { case '"': sb.append(SerializationConstants.BACK_SLASH).append(SerializationConstants.QUOTE); break; - case '\\': - sb.append(SerializationConstants.BACK_SLASH).append(SerializationConstants.BACK_SLASH); - break; default: - if (option.escapeUnicode() && (c <= 0x1F || c == 0x7F || (c >= 0x80 && c <= 0xFFFF))) { + if (Character.isISOControl(c) ||c == 0x7F) { + sb.append(String.format("\\u%04X", (int) c)); + } + else if (option.escapeUnicode() && c >= 0x80 && c <= 0xFFFF) { sb.append(String.format("\\u%04X", (int) c)); } else if (Character.isHighSurrogate(c)) { int codePoint = value.codePointAt(i); if (Character.isValidCodePoint(codePoint)) { - sb.append(String.format("\\U%08X", codePoint)); + if (option.escapeUnicode()) { + sb.append(String.format("\\U%08X", codePoint)); + } else { + sb.append(Character.toChars(codePoint)); + } i++; } else { sb.append(c); @@ -178,6 +182,7 @@ protected String escapeMultilineLiteralString(String value) { SerializationConstants.BACK_SLASH + SerializationConstants.QUOTE + SerializationConstants.BACK_SLASH + SerializationConstants.QUOTE + SerializationConstants.BACK_SLASH + SerializationConstants.QUOTE); + } /** diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/parser/jsonld/JSONLDCircularTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/parser/jsonld/JSONLDCircularTest.java index 85a24445c..613dcbabf 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/parser/jsonld/JSONLDCircularTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/parser/jsonld/JSONLDCircularTest.java @@ -27,6 +27,8 @@ import fr.inria.corese.core.next.impl.io.serialization.DefaultSerializerFactory; import fr.inria.corese.core.next.impl.temp.CoreseAdaptedValueFactory; import fr.inria.corese.core.next.impl.temp.CoreseModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Circular tests for JSON-LD parser and serializer integration. @@ -42,6 +44,8 @@ @DisplayName("JSON-LD Circular Integration Tests") class JSONLDCircularTest { + private static final Logger logger = LoggerFactory.getLogger(JSONLDCircularTest.class); + private ValueFactory valueFactory; private SerializerFactory serializerFactory; private ParserFactory parserFactory; @@ -69,7 +73,8 @@ void setUp() { valueFactory = new CoreseAdaptedValueFactory(); serializerFactory = new DefaultSerializerFactory(); parserFactory = new ParserFactory(); - defaultConfig = new TitaniumJSONLDProcessorOption.Builder().build(); + defaultConfig = new TitaniumJSONLDProcessorOption.Builder() + .build(); } /** @@ -261,7 +266,7 @@ private Model createSpecialCharactersTestModel() { private Model performRoundTrip(Model originalModel) throws Exception { // Serialize to JSON-LD RDFSerializer serializer = serializerFactory.createSerializer( - RDFFormat.JSONLD, originalModel, (SerializationOption) defaultConfig); + RDFFormat.JSONLD, originalModel, defaultConfig); StringWriter writer = new StringWriter(); serializer.write(writer); diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLCircularTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLCircularTest.java index 5821ee0ca..8314b3aca 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLCircularTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLCircularTest.java @@ -265,7 +265,6 @@ private Model performRoundTrip(Model originalModel) throws Exception { } @Test - @Disabled("Waiting for RDF/XML parser implementation from PR #176") @DisplayName("Round-trip test with simple model containing basic IRIs and literals") void testRoundTripWithSimpleModel() throws Exception { // Given: A simple model with basic triples @@ -282,7 +281,6 @@ void testRoundTripWithSimpleModel() throws Exception { } @Test - @Disabled("Waiting for RDF/XML parser implementation from PR #176") @DisplayName("Round-trip test with complex model containing diverse RDF value types") void testRoundTripWithComplexModel() throws Exception { // Given: A complex model with various RDF constructs @@ -299,7 +297,6 @@ void testRoundTripWithComplexModel() throws Exception { } @Test - @Disabled("Waiting for RDF/XML parser implementation from PR #176") @DisplayName("Round-trip test with empty model") void testRoundTripWithEmptyModel() throws Exception { // Given: An empty model @@ -315,15 +312,12 @@ void testRoundTripWithEmptyModel() throws Exception { } @Test - @Disabled("Waiting for RDF/XML parser implementation from PR #176") @DisplayName("Round-trip test with model containing only typed literals") void testRoundTripWithTypedLiterals() throws Exception { // Given: A model with various typed literals Model originalModel = createTypedLiteralsTestModel(); - // When: Performing round-trip serialization and parsing Model deserializedModel = performRoundTrip(originalModel); - // Then: All typed literals should be preserved correctly assertEquals(originalModel.size(), deserializedModel.size(), "Model sizes should be equal after round-trip"); @@ -332,7 +326,6 @@ void testRoundTripWithTypedLiterals() throws Exception { } @Test - @Disabled("Waiting for RDF/XML parser implementation from PR #176") @DisplayName("Round-trip test with model containing only language-tagged literals") void testRoundTripWithLanguageTaggedLiterals() throws Exception { // Given: A model with language-tagged literals @@ -349,7 +342,6 @@ void testRoundTripWithLanguageTaggedLiterals() throws Exception { } @Test - @Disabled("Waiting for RDF/XML parser implementation from PR #176") @DisplayName("Round-trip test with model containing only blank nodes") void testRoundTripWithBlankNodes() throws Exception { // Given: A model with blank nodes as subjects and objects @@ -367,7 +359,6 @@ void testRoundTripWithBlankNodes() throws Exception { } @Test - @Disabled("Waiting for RDF/XML parser implementation from PR #176") @DisplayName("Round-trip test with model containing special characters and escape sequences") void testRoundTripWithSpecialCharacters() throws Exception { // Given: A model with special characters and escape sequences diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLParserTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLParserTest.java index f4677cdab..27c7f1e1c 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLParserTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLParserTest.java @@ -7,6 +7,8 @@ import fr.inria.corese.core.next.impl.temp.CoreseAdaptedValueFactory; import fr.inria.corese.core.next.impl.temp.CoreseModel; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -21,6 +23,8 @@ * and unescaping of IRIs and literals, and named graphs. */ public class RDFXMLParserTest { + private static final Logger logger = LoggerFactory.getLogger(RDFXMLParserTest.class); + /** * Helper method to parse the RDF/XML String * @param rdfXml @@ -37,6 +41,10 @@ private Model parseRdfXml(String rdfXml) throws Exception { return model; } + /** + * Helper method to print the model. + * @param model + */ /** * Helper method to print the model. * @param model @@ -44,29 +52,34 @@ private Model parseRdfXml(String rdfXml) throws Exception { private void printModel(Model model) { model.stream().forEach(stmt -> { Value obj = stmt.getObject(); + String subjectString = stmt.getSubject().stringValue(); + String predicateString = stmt.getPredicate().stringValue(); + if (obj instanceof Literal literal) { - if (literal.getLanguage().isPresent()) { - System.out.printf("(%s, %s, \"%s\"@%s)%n", - stmt.getSubject().stringValue(), - stmt.getPredicate().stringValue(), - literal.getLabel(), - literal.getLanguage().get()); + String label = String.valueOf(literal.getLabel()); + String languageTag = literal.getLanguage().orElse(null); + + if (languageTag != null) { + logger.debug("({}, {}, \"{}\"@{})", + subjectString, + predicateString, + label, + languageTag); } else { - System.out.printf("(%s, %s, \"%s\")%n", - stmt.getSubject().stringValue(), - stmt.getPredicate().stringValue(), - literal.getLabel()); + logger.debug("({}, {}, \"{}\")", + subjectString, + predicateString, + label); } } else { - System.out.printf("(%s, %s, %s)%n", - stmt.getSubject().stringValue(), - stmt.getPredicate().stringValue(), + logger.debug("({}, {}, {})", + subjectString, + predicateString, obj.stringValue()); } }); } - /** * Test node elements with IRIs * @throws Exception diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/parser/turtle/TurtleCircularTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/parser/turtle/TurtleCircularTest.java index e62524828..3335815cf 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/parser/turtle/TurtleCircularTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/parser/turtle/TurtleCircularTest.java @@ -216,6 +216,11 @@ private Model createSpecialCharactersTestModel() { Literal literalUnicode = valueFactory.createLiteral("Hello δΈ–η•Œ 🌍"); model.add(subject, predicateNote, literalUnicode); + // Literal with character forbidden in URIs + Literal badURICharacters = valueFactory.createLiteral("<>-.!~*()?:;/=[]+@&$,%#~^ \\\\ \\t \\b \\n \\r \\f"); + model.add(subject, predicateNote, badURICharacters); + + return model; } diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/jsonld/JSONLDSerializerTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/jsonld/JSONLDSerializerTest.java index b8a97059d..b6573c617 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/jsonld/JSONLDSerializerTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/jsonld/JSONLDSerializerTest.java @@ -115,17 +115,17 @@ public void modelWithBlankNodesTest() { { "@id":"http://example.org/iri1", "http://example.org/pred1":[ - {"@id":"blank1"} + {"@id":"_:blank1"} ] }, { - "@id":"blank1", + "@id":"_:blank1", "http://example.org/pred1":[ { "@id":"http://example.org/iri1" }, { - "@id":"blank1" + "@id":"_:blank1" }, { "@value":"literal1" @@ -171,17 +171,6 @@ public void modelWithNamedGraphsTest() { "http://example.org/pred1":[ { "@id":"http://example.org/iri1" - }, - { - "@value":"literal1" - }, - { - "@value":"literal2", - "@type":"http://example.org/datatype1" - }, - { - "@language":"en", - "@value":"literal3" } ] }, diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerTest.java index f76700962..baa7cfb7f 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerTest.java @@ -1,13 +1,13 @@ package fr.inria.corese.core.next.impl.io.serialization.turtle; -import fr.inria.corese.core.next.api.IRI; -import fr.inria.corese.core.next.api.Model; -import fr.inria.corese.core.next.api.Statement; +import fr.inria.corese.core.next.api.*; import fr.inria.corese.core.next.impl.common.literal.RDF; import fr.inria.corese.core.next.impl.io.serialization.TestStatementFactory; import fr.inria.corese.core.next.impl.io.serialization.option.LiteralDatatypePolicyEnum; import fr.inria.corese.core.next.impl.io.serialization.util.SerializationConstants; import fr.inria.corese.core.next.impl.exception.SerializationException; +import fr.inria.corese.core.next.impl.temp.CoreseAdaptedValueFactory; +import fr.inria.corese.core.next.impl.temp.CoreseModel; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -18,8 +18,7 @@ import java.util.Collections; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; /** @@ -283,11 +282,22 @@ void testBlankNodeSerialization() throws SerializationException, IOException { @prefix rdfs: . @prefix xsd: . + _:b1 ns:hasValue "Value of BNode" . ns:mainSubject ns:refersTo _:b1 . """; String actual = writer.toString().replace("\r\n", "\n"); - assertEquals(expected, actual); + + String expected1 = """ + _:b1 ns:hasValue "Value of BNode" . + ns:mainSubject ns:refersTo _:b1 . +""".trim(); + + String expected2 = """ + ns:mainSubject ns:refersTo _:b1 . + _:b1 ns:hasValue "Value of BNode" . +""".trim(); + assertTrue(expected.contains(expected1) || expected.contains(expected2)); } /** @@ -495,4 +505,39 @@ void testMultilineLiteralSerialization() throws SerializationException, IOExcept assertEquals(expected, actual); } + /** + * Tests serialization of a literal containing escaped characters. + * + * @throws SerializationException if a serialization error occurs. + * @throws IOException if an I/O error occurs during writing. + */ + @Test + void testEscapedCharacterLiteralSerialization() throws SerializationException, IOException { + ValueFactory coreseFactory = new CoreseAdaptedValueFactory(); + Statement statement = coreseFactory.createStatement( + coreseFactory.createIRI("http://example.org/book/1"), + coreseFactory.createIRI("http://example.org/properties/description"), + coreseFactory.createLiteral("\\\\ \\t \\b \\n \\r \\f") + ); + + Model coreseModel = new CoreseModel(); + coreseModel.add(statement); + + StringWriter writer = new StringWriter(); + TurtleOption config = new TurtleOption.Builder() + .autoDeclarePrefixes(false) + .includeContext(false) + .prettyPrint(false) + .usePrefixes(false) + .build(); + TurtleSerializer turtleSerializer = new TurtleSerializer(coreseModel, config); + + turtleSerializer.write(writer); + + String expected = " \"\\\\ \\t \\b \\n \\r \\f\" .".trim(); + + String actual = writer.toString().trim(); + assertEquals(expected, actual); + } + }