diff --git a/build.gradle.kts b/build.gradle.kts index 6a08e7797..033c60b50 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -117,7 +117,8 @@ dependencies { implementation("fr.inria.lille.shexjava:shexjava-core:1.0") // ShEx validation engine implementation("fr.inria.corese.org.semarglproject:semargl-rdfa:$semargl_version") // RDFa parser (Semargl) implementation("fr.inria.corese.org.semarglproject:semargl-core:$semargl_version") // Semargl core RDF parser - implementation("com.github.jsonld-java:jsonld-java:0.13.4") // JSON-LD processing + implementation("com.github.jsonld-java:jsonld-java:0.13.6") // Legacy JSON-LD processing + // === HTTP and XML === implementation("org.glassfish.jersey.core:jersey-client:$jersey_version") // HTTP client (Jersey) @@ -150,6 +151,7 @@ extraJavaModuleInfo { automaticModule("commons-lang:commons-lang", "commons.lang") // Module for Commons Lang automaticModule("fr.inria.lille.shexjava:shexjava-core", "shexjava.core") // Module for ShexJava core automaticModule("org.eclipse.rdf4j:rdf4j-model", "rdf4j.model") // Module for RDF4J model + } diff --git a/src/main/java/fr/inria/corese/core/next/api/parser/RDFFormat.java b/src/main/java/fr/inria/corese/core/next/api/parser/RDFFormat.java new file mode 100644 index 000000000..ad7988ad7 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/parser/RDFFormat.java @@ -0,0 +1,114 @@ +package fr.inria.corese.core.next.api.parser; + +import java.nio.charset.Charset; +import java.util.*; +import fr.inria.corese.core.next.api.IRI; + +public interface RDFFormat { + + /** + * Gets the name of this file format. + * + * @return A human-readable format name, e.g. "PLAIN TEXT". + */ + String getName(); + + + /** + * Gets the default MIME type for this file format. + * + * @return A MIME type string, e.g. "text/plain". + */ + String getDefaultMIMEType() ; + + + /** + * Checks if the specified MIME type matches the FileFormat's default MIME type. The MIME types are compared + * ignoring upper/lower-case differences. + * + * @param mimeType The MIME type to compare to the FileFormat's default MIME type. + * @return true if the specified MIME type matches the FileFormat's default MIME type. + */ + boolean hasDefaultMIMEType(String mimeType); + + /** + * Gets the file format's MIME types. + * + * @return An unmodifiable list of MIME type strings, e.g. "text/plain". + */ + List getMIMETypes(); + + + + /** + * Checks if specified MIME type matches one of the FileFormat's MIME types. The MIME types are compared ignoring + * upper/lower-case differences. + * + * @param mimeType The MIME type to compare to the FileFormat's MIME types. + * @return true if the specified MIME type matches one of the FileFormat's MIME types. + */ + boolean hasMIMEType(String mimeType); + + /** + * Gets the default file name extension for this file format. + * + * @return A file name extension (excluding the dot), e.g. "txt", or null if there is no common file + * extension for the format. + */ + String getDefaultFileExtension(); + + /** + * Checks if the specified file name extension matches the FileFormat's default file name extension. The file name + * extension MIME types are compared ignoring upper/lower-case differences. + * + * @param extension The file extension to compare to the FileFormat's file extension. + * @return true if the file format has a default file name extension and if it matches the specified + * extension, false otherwise. + */ + boolean hasDefaultFileExtension(String extension); + + /** + * Gets the file format's file extensions. + * + * @return An unmodifiable list of file extension strings, e.g. "txt". + */ + List getFileExtensions(); + + /** + * Checks if the FileFormat's file extension is equal to the specified file extension. The file extensions are + * compared ignoring upper/lower-case differences. + * + * @param extension The file extension to compare to the FileFormat's file extension. + * @return true if the specified file extension is equal to the FileFormat's file extension. + */ + boolean hasFileExtension(String extension); + + /** + * Get the (default) charset for this file format. + * + * @return the (default) charset for this file format, or null if this format does not have a default charset. + */ + Charset getCharset(); + + /** + * Checks if the FileFormat has a (default) charset. + * + * @return true if the FileFormat has a (default) charset. + */ + boolean hasCharset(); + + /** + * Return true if the RDFFormat supports the encoding of namespace/prefix information. + */ + boolean supportsNamespaces(); + + /** + * Return true if the RDFFormat supports the encoding of contexts/named graphs. + */ + boolean supportsContexts(); + + /** + * Return true if the RDFFormat supports the encoding of RDF-star triples natively. + */ + boolean supportsRDFStar(); +} diff --git a/src/main/java/fr/inria/corese/core/next/api/parser/RDFFormats.java b/src/main/java/fr/inria/corese/core/next/api/parser/RDFFormats.java new file mode 100644 index 000000000..f073fa898 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/parser/RDFFormats.java @@ -0,0 +1,255 @@ +package fr.inria.corese.core.next.api.parser; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; + +public enum RDFFormats implements RDFFormat { + + TURTLE("Turtle", + List.of("text/turtle"), + List.of("ttl"), + true, + false, + false), + N3("N3", + List.of("text/n3"), + List.of("n3"), + true, + false, + false), + RDF_XML("RDF/XML", + List.of("application/rdf+xml"), + List.of("rdf", "xml"), + true, + false, + false), + JSON_LD("JSON-LD", + List.of("application/ld+json"), + List.of("jsonld", "json"), + true, + true, + false), + N_TRIPLES("N-Triples", + List.of("application/n-triples"), + List.of("nt"), + false, + false, + false), + TRIG("TriG", + List.of("application/trig"), + List.of("trig"), + true, + true, + false), + NQUADS("N-Quads", + List.of("application/n-quads"), + List.of("nq"), + true, + true, + false); + + private static final boolean DEFAULT_SUPPORTS_NAMESPACES = true; + private static final boolean DEFAULT_SUPPORTS_CONTEXTS = true; + private static final boolean DEFAULT_SUPPORTS_RDF_STAR = false; + + /** + * The file format human-readable name. + */ + private final String name; + + /** + * The file format's MIME types. The first item in the list is interpreted as the default MIME type for the format. + */ + private final List mimeTypes; + + /** + * The file format's (default) charset. + */ + private final Charset charset; + + /** + * The file format's file extensions. The first item in the list is interpreted as the default file extension for + * the format. + */ + private final List fileExtensions; + + /** + * Flag indicating whether the RDFFormat can encode namespace information. + */ + private final boolean supportsNamespaces; + + /** + * Flag indicating whether the RDFFormat can encode context information (ex: Graphs or quads). + */ + private final boolean supportsContexts; + + /** + * Flag indicating whether the RDFFormat can encode RDF-star triples natively. + */ + private final boolean supportsRDFStar; + + RDFFormats(String name, + List mimeTypes, + Charset charset, + List fileExtensions, + boolean supportsNamespaces, + boolean supportsContexts, + boolean supportsRDFStar) { + this.name = name; + this.mimeTypes = mimeTypes; + this.charset = charset; + this.fileExtensions = fileExtensions; + this.supportsNamespaces = supportsNamespaces; + this.supportsContexts = supportsContexts; + this.supportsRDFStar = supportsRDFStar; + } + + RDFFormats(String name, + List mimeTypes, + Charset charset, + List fileExtensions) { + this(name, mimeTypes, charset, fileExtensions, DEFAULT_SUPPORTS_NAMESPACES, DEFAULT_SUPPORTS_CONTEXTS, DEFAULT_SUPPORTS_RDF_STAR); + } + + RDFFormats(String name, + List mimeTypes, + List fileExtensions) { + this(name, mimeTypes, StandardCharsets.UTF_8, fileExtensions, DEFAULT_SUPPORTS_NAMESPACES, DEFAULT_SUPPORTS_CONTEXTS, DEFAULT_SUPPORTS_RDF_STAR); + } + + RDFFormats(String name, + List mimeTypes, + List fileExtensions, + boolean supportsNamespaces, + boolean supportsContexts, + boolean supportsRDFStar) { + this(name, mimeTypes, StandardCharsets.UTF_8, fileExtensions, supportsNamespaces, supportsContexts, supportsRDFStar); + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDefaultMIMEType() { + return mimeTypes.get(0); + } + + @Override + public boolean hasDefaultMIMEType(String mimeType) { + return getDefaultMIMEType().equalsIgnoreCase(mimeType); + } + + @Override + public List getMIMETypes() { + return Collections.unmodifiableList(mimeTypes); + } + + @Override + public boolean hasMIMEType(String mimeType) { + if (mimeType == null) { + return false; + } + String type = mimeType; + if (mimeType.indexOf(';') > 0) { + type = mimeType.substring(0, mimeType.indexOf(';')); + } + for (String mt : this.mimeTypes) { + if (mt.equalsIgnoreCase(mimeType)) { + return true; + } + if (mimeType != type && mt.equalsIgnoreCase(type)) { + return true; + } + } + + return false; + } + + @Override + public String getDefaultFileExtension() { + if (fileExtensions.isEmpty()) { + return null; + } else { + return fileExtensions.get(0); + } + } + + @Override + public boolean hasDefaultFileExtension(String extension) { + String ext = getDefaultFileExtension(); + return ext != null && ext.equalsIgnoreCase(extension); + } + + @Override + public List getFileExtensions() { + return Collections.unmodifiableList(fileExtensions); + } + + @Override + public boolean hasFileExtension(String extension) { + for (String ext : fileExtensions) { + if (ext.equalsIgnoreCase(extension)) { + return true; + } + } + + return false; + } + + @Override + public Charset getCharset() { + return charset; + } + + @Override + public boolean hasCharset() { + return charset != null; + } + + @Override + public boolean supportsNamespaces() { + return supportsNamespaces; + } + + @Override + public boolean supportsContexts() { + return supportsContexts; + } + + @Override + public boolean supportsRDFStar() { + return supportsRDFStar; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(64); + + sb.append(name); + + sb.append(" (mimeTypes="); + for (int i = 0; i < mimeTypes.size(); i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(mimeTypes.get(i)); + } + + sb.append("; ext="); + for (int i = 0; i < fileExtensions.size(); i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(fileExtensions.get(i)); + } + + sb.append(")"); + + return sb.toString(); + } + +} diff --git a/src/main/java/fr/inria/corese/core/next/api/parser/RDFParser.java b/src/main/java/fr/inria/corese/core/next/api/parser/RDFParser.java new file mode 100644 index 000000000..2977bf158 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/parser/RDFParser.java @@ -0,0 +1,42 @@ +package fr.inria.corese.core.next.api.parser; + +import java.io.InputStream; +import java.io.Reader; + +public interface RDFParser { + + /** + * Gets the RDF format that this parser can parse. + */ + RDFFormat getRDFFormat(); + + /** + * Parses RDF data from the specified InputStream or Reader and adds it to the model. + * + * @param in The InputStream to read RDF data from. + */ + void parse(InputStream in); + + /** + * Parses RDF data from the specified InputStream or Reader and adds it to the model. + * + * @param in The InputStream to read RDF data from. + * @param baseURI The base URI for resolving relative URIs in the RDF data. + */ + void parse(InputStream in, String baseURI); + + /** + * Parses RDF data from the specified InputStream or Reader and adds it to the model. + * + * @param reader The Reader to read RDF data from. + */ + void parse(Reader reader); + + /** + * Parses RDF data from the specified InputStream or Reader and adds it to the model. + * + * @param reader The Reader to read RDF data from. + * @param baseURI The base URI for resolving relative URIs in the RDF data. + */ + void parse(Reader reader, String baseURI); +} diff --git a/src/main/java/fr/inria/corese/core/next/api/parser/RDFParserFactory.java b/src/main/java/fr/inria/corese/core/next/api/parser/RDFParserFactory.java new file mode 100644 index 000000000..68f7c16c1 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/parser/RDFParserFactory.java @@ -0,0 +1,17 @@ +package fr.inria.corese.core.next.api.parser; + +import fr.inria.corese.core.next.api.Model; +import fr.inria.corese.core.next.api.ValueFactory; + +public interface RDFParserFactory { + + /** + * Creates a new RDF parser for the specified format and model. + * + * @param format The RDF format to use for parsing. + * @param model The model to which the parsed data will be added. + * @return A new instance of an RDF parser for the specified format and model. + */ + RDFParser createRDFParser(RDFFormat format, Model model, ValueFactory factory); + +} diff --git a/src/main/java/fr/inria/corese/core/next/impl/exception/ParsingErrorException.java b/src/main/java/fr/inria/corese/core/next/impl/exception/ParsingErrorException.java new file mode 100644 index 000000000..cc6bf4eee --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/exception/ParsingErrorException.java @@ -0,0 +1,18 @@ +package fr.inria.corese.core.next.impl.exception; + +public class ParsingErrorException extends RuntimeException { + + private static final long serialVersionUID = -2053549958572141648L; + + public ParsingErrorException(String message) { + super(message); + } + + public ParsingErrorException(String message, Throwable cause) { + super(message, cause); + } + + public ParsingErrorException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/fr/inria/corese/core/next/impl/exception/UnsupportedFileFormatException.java b/src/main/java/fr/inria/corese/core/next/impl/exception/UnsupportedFileFormatException.java new file mode 100644 index 000000000..b44687d51 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/exception/UnsupportedFileFormatException.java @@ -0,0 +1,19 @@ +package fr.inria.corese.core.next.impl.exception; + +public class UnsupportedFileFormatException extends Exception { + + private static final long serialVersionUID = 7963163989802143570L; + + public UnsupportedFileFormatException(String message) { + super(message); + } + + public UnsupportedFileFormatException(String message, Throwable cause) { + super(message, cause); + } + + public UnsupportedFileFormatException(Throwable cause) { + super(cause); + } + +} diff --git a/src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/JSONLDParser.java b/src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/JSONLDParser.java new file mode 100644 index 000000000..e2fb6439a --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/JSONLDParser.java @@ -0,0 +1,121 @@ +package fr.inria.corese.core.next.impl.parser.jsonld; + +import com.github.jsonldjava.core.JsonLdError; +import com.github.jsonldjava.core.JsonLdOptions; +import com.github.jsonldjava.core.JsonLdProcessor; +import com.github.jsonldjava.core.RDFDataset; +import com.github.jsonldjava.utils.JsonUtils; +import fr.inria.corese.core.next.api.*; +import fr.inria.corese.core.next.api.parser.RDFFormat; +import fr.inria.corese.core.next.api.parser.RDFFormats; +import fr.inria.corese.core.next.api.parser.RDFParser; +import fr.inria.corese.core.next.impl.exception.ParsingErrorException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +public class JSONLDParser implements RDFParser { + + private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(JSONLDParser.class); + + private static final String jsonldjavadefaultGraphName = "@default"; + + private final Model model; + private final ValueFactory valueFactory; + private JsonLdOptions commonOptions = new JsonLdOptions(); + + public JSONLDParser(Model model, ValueFactory factory) { + this.model = model; + this.valueFactory = factory; + commonOptions.setCompactArrays(false); + commonOptions.setBase(null); + } + + @Override + public RDFFormat getRDFFormat() { + return RDFFormats.JSON_LD; + } + + private void parseFromJsonInput(Object input, String baseURI) { + try { + if(baseURI != null) { + commonOptions.setBase(baseURI); + } + RDFDataset output = (RDFDataset)JsonLdProcessor.toRDF(input, this.commonOptions); + for(String gName: output.graphNames()) { + for(RDFDataset.Quad q : output.getQuads(gName)) { + Resource subject = null; + if (q.getSubject().isIRI()) { + subject = valueFactory.createIRI(q.getSubject().getValue()); + } else { + subject = valueFactory.createBNode(q.getSubject().getValue()); + } + + IRI predicate = valueFactory.createIRI(q.getPredicate().getValue()); + + Value object = null; + if (q.getObject().isIRI()) { + object = valueFactory.createIRI(q.getObject().getValue()); + } else if (q.getObject().isBlankNode()) { + object = valueFactory.createBNode(q.getObject().getValue()); + } else if(q.getObject().isLiteral()){ + String objectDatatype = q.getObject().getDatatype(); + String objectLanguage = q.getObject().getLanguage(); + if(objectLanguage != null) { + object = valueFactory.createLiteral(q.getObject().getValue(), objectLanguage); + } else if(objectDatatype != null) { + object = valueFactory.createLiteral(q.getObject().getValue(), valueFactory.createIRI(objectDatatype)); + } else { + object = valueFactory.createLiteral(q.getObject().getValue()); + } + } else { + throw new ParsingErrorException("Unknown object type: " + q.getObject()); + } + + if(gName.equals(jsonldjavadefaultGraphName)) { + model.add(subject, predicate, object); + } else { + IRI graph = valueFactory.createIRI(gName); + model.add(subject, predicate, object, graph); + } + } + } + if(baseURI != null) { + commonOptions.setBase(null); + } + } catch (JsonLdError e) { + throw new ParsingErrorException(e); + } + } + + @Override + public void parse(InputStream in) throws ParsingErrorException { + parse(in, null); + } + + @Override + public void parse(InputStream in, String baseURI) throws ParsingErrorException { + try { + Object input = JsonUtils.fromInputStream(in); + parseFromJsonInput(input, baseURI); + } catch (IOException | JsonLdError e) { + throw new ParsingErrorException(e); + } + } + + @Override + public void parse(Reader reader) throws ParsingErrorException { + parse(reader, null); + } + + @Override + public void parse(Reader reader, String baseURI) { + try { + Object input = JsonUtils.fromReader(reader); + parseFromJsonInput(input, baseURI); + } catch (IOException | JsonLdError e) { + throw new ParsingErrorException(e); + } + } +} diff --git a/src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/JSONLDParserFactory.java b/src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/JSONLDParserFactory.java new file mode 100644 index 000000000..d7eeae5b8 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/JSONLDParserFactory.java @@ -0,0 +1,29 @@ +package fr.inria.corese.core.next.impl.parser.jsonld; + +import fr.inria.corese.core.next.api.Model; +import fr.inria.corese.core.next.api.ValueFactory; +import fr.inria.corese.core.next.api.parser.RDFFormat; +import fr.inria.corese.core.next.api.parser.RDFFormats; +import fr.inria.corese.core.next.api.parser.RDFParser; +import fr.inria.corese.core.next.api.parser.RDFParserFactory; + +public class JSONLDParserFactory implements RDFParserFactory { + + private static final JSONLDParserFactory INSTANCE = new JSONLDParserFactory(); + + public static JSONLDParserFactory getInstance() { + return INSTANCE; + } + + private JSONLDParserFactory() { + + } + + @Override + public RDFParser createRDFParser(RDFFormat format, Model model, ValueFactory factory) { + if(format == RDFFormats.JSON_LD) { + return new JSONLDParser(model, factory); + } + return null; + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index da52e9920..999115852 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -13,6 +13,8 @@ requires org.json; // requires org.apache.commons.lang3; requires org.slf4j; + requires jsonld.java; + exports fr.inria.corese.core.load; exports fr.inria.corese.core.load.result; diff --git a/src/test/java/fr/inria/corese/core/next/impl/parser/jsonld/JSONLDParserTest.java b/src/test/java/fr/inria/corese/core/next/impl/parser/jsonld/JSONLDParserTest.java new file mode 100644 index 000000000..42894cc47 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/impl/parser/jsonld/JSONLDParserTest.java @@ -0,0 +1,288 @@ +package fr.inria.corese.core.next.impl.parser.jsonld; + +import fr.inria.corese.core.next.api.BNode; +import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.api.Literal; +import fr.inria.corese.core.next.api.Statement; +import fr.inria.corese.core.next.api.parser.RDFFormats; +import fr.inria.corese.core.next.api.parser.RDFParser; +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 java.io.ByteArrayInputStream; +import java.io.StringReader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class JSONLDParserTest { + + @Test + void testGetRDFFormat() { + RDFParser parser = JSONLDParserFactory.getInstance().createRDFParser(RDFFormats.JSON_LD, new CoreseModel(), new CoreseAdaptedValueFactory()); + assertEquals(RDFFormats.JSON_LD, parser.getRDFFormat()); + } + + /** + * Test method for {@link JSONLDParser#parse(java.io.InputStream)}. No relative IRIs in this test. + */ + @Test + public void testParseInputStream() { + // taken from https://www.w3.org/TR/json-ld-api/#object-to-rdf-conversion + String sampleJsonLD = """ + { + "@context": { + "name": "http://xmlns.com/foaf/0.1/name", + "knows": "http://xmlns.com/foaf/0.1/knows" + }, + "@id": "http://me.markus-lanthaler.com/", + "name": "Markus Lanthaler", + "knows": [ + { + "name": "Dave Longley" + } + ] + } + """; + CoreseModel model = new CoreseModel(); + RDFParser parser = JSONLDParserFactory.getInstance().createRDFParser(RDFFormats.JSON_LD, model, new CoreseAdaptedValueFactory()); + parser.parse(new ByteArrayInputStream(sampleJsonLD.getBytes())); + + assertEquals(3, model.size()); + IRI subject = new CoreseAdaptedValueFactory().createIRI("http://me.markus-lanthaler.com/"); + IRI namePredicate = new CoreseAdaptedValueFactory().createIRI("http://xmlns.com/foaf/0.1/name"); + IRI knowsPredicate = new CoreseAdaptedValueFactory().createIRI("http://xmlns.com/foaf/0.1/knows"); + Literal nameMarkusObject = new CoreseAdaptedValueFactory().createLiteral("Markus Lanthaler"); + Literal nameDaveObject = new CoreseAdaptedValueFactory().createLiteral("Dave Longley"); + Statement daveNameStatement = new CoreseAdaptedValueFactory().createStatement(subject, namePredicate, nameMarkusObject); + + assertTrue(model.contains(daveNameStatement)); + assertTrue(model.contains(subject, knowsPredicate, null)); + assertTrue(model.contains(null, namePredicate, nameDaveObject)); + } + + /** + * Test method for {@link JSONLDParser#parse(java.io.InputStream, java.lang.String)}. A relative IRI is used in this test. + */ + @Test + public void testParseInputStreamString() { + // taken from https://www.w3.org/TR/json-ld-api/#object-to-rdf-conversion + String sampleJsonLD = """ + { + "@context": { + "name": "http://xmlns.com/foaf/0.1/name", + "knows": "http://xmlns.com/foaf/0.1/knows" + }, + "@id": "", + "name": "Markus Lanthaler", + "knows": [ + { + "name": "Dave Longley" + } + ] + } + """; + CoreseModel model = new CoreseModel(); + RDFParser parser = JSONLDParserFactory.getInstance().createRDFParser(RDFFormats.JSON_LD, model, new CoreseAdaptedValueFactory()); + parser.parse(new ByteArrayInputStream(sampleJsonLD.getBytes()), "http://me.markus-lanthaler.com/"); + + assertEquals(3, model.size()); + IRI subject = new CoreseAdaptedValueFactory().createIRI("http://me.markus-lanthaler.com/"); + IRI namePredicate = new CoreseAdaptedValueFactory().createIRI("http://xmlns.com/foaf/0.1/name"); + IRI knowsPredicate = new CoreseAdaptedValueFactory().createIRI("http://xmlns.com/foaf/0.1/knows"); + Literal nameMarkusObject = new CoreseAdaptedValueFactory().createLiteral("Markus Lanthaler"); + Literal nameDaveObject = new CoreseAdaptedValueFactory().createLiteral("Dave Longley"); + Statement daveNameStatement = new CoreseAdaptedValueFactory().createStatement(subject, namePredicate, nameMarkusObject); + + assertTrue(model.contains(daveNameStatement)); + assertTrue(model.contains(subject, knowsPredicate, null)); + assertTrue(model.contains(null, namePredicate, nameDaveObject)); + } + + /** + * Test of {@link JSONLDParser#parse(java.io.Reader, java.lang.String)}, of class JSONLDParser. No relative IRIs are used in this test. + */ + @Test + public void testParseReader() { + // taken from https://www.w3.org/TR/json-ld-api/#object-to-rdf-conversion + String sampleJsonLD = """ + { + "@context": { + "name": "http://xmlns.com/foaf/0.1/name", + "knows": "http://xmlns.com/foaf/0.1/knows" + }, + "@id": "http://me.markus-lanthaler.com/", + "name": "Markus Lanthaler", + "knows": [ + { + "name": "Dave Longley" + } + ] + } + """; + CoreseModel model = new CoreseModel(); + RDFParser parser = JSONLDParserFactory.getInstance().createRDFParser(RDFFormats.JSON_LD, model, new CoreseAdaptedValueFactory()); + parser.parse(new StringReader(sampleJsonLD)); + + assertEquals(3, model.size()); + IRI subject = new CoreseAdaptedValueFactory().createIRI("http://me.markus-lanthaler.com/"); + IRI namePredicate = new CoreseAdaptedValueFactory().createIRI("http://xmlns.com/foaf/0.1/name"); + IRI knowsPredicate = new CoreseAdaptedValueFactory().createIRI("http://xmlns.com/foaf/0.1/knows"); + Literal nameMarkusObject = new CoreseAdaptedValueFactory().createLiteral("Markus Lanthaler"); + Literal nameDaveObject = new CoreseAdaptedValueFactory().createLiteral("Dave Longley"); + Statement daveNameStatement = new CoreseAdaptedValueFactory().createStatement(subject, namePredicate, nameMarkusObject); + + assertTrue(model.contains(daveNameStatement)); + assertTrue(model.contains(subject, knowsPredicate, null)); + assertTrue(model.contains(null, namePredicate, nameDaveObject)); + } + + /** + * Test of {@link JSONLDParser#parse(java.io.Reader, java.lang.String)}, of class JSONLDParser. A relative IRI is used in this test. + */ + @Test + public void testParseReaderString() { + // taken from https://www.w3.org/TR/json-ld-api/#object-to-rdf-conversion + String sampleJsonLD = """ + { + "@context": { + "name": "http://xmlns.com/foaf/0.1/name", + "knows": "http://xmlns.com/foaf/0.1/knows" + }, + "@id": "", + "name": "Markus Lanthaler", + "knows": [ + { + "name": "Dave Longley" + } + ] + } + """; + CoreseModel model = new CoreseModel(); + RDFParser parser = JSONLDParserFactory.getInstance().createRDFParser(RDFFormats.JSON_LD, model, new CoreseAdaptedValueFactory()); + parser.parse(new StringReader(sampleJsonLD), "http://me.markus-lanthaler.com/"); + + assertEquals(3, model.size()); + IRI subject = new CoreseAdaptedValueFactory().createIRI("http://me.markus-lanthaler.com/"); + IRI namePredicate = new CoreseAdaptedValueFactory().createIRI("http://xmlns.com/foaf/0.1/name"); + IRI knowsPredicate = new CoreseAdaptedValueFactory().createIRI("http://xmlns.com/foaf/0.1/knows"); + Literal nameMarkusObject = new CoreseAdaptedValueFactory().createLiteral("Markus Lanthaler"); + Literal nameDaveObject = new CoreseAdaptedValueFactory().createLiteral("Dave Longley"); + Statement daveNameStatement = new CoreseAdaptedValueFactory().createStatement(subject, namePredicate, nameMarkusObject); + + assertTrue(model.contains(daveNameStatement)); + assertTrue(model.contains(subject, knowsPredicate, null)); + assertTrue(model.contains(null, namePredicate, nameDaveObject)); + } + + /** + * Test parsing JSON-LD with blank nodes. + */ + @Test + public void testParseJsonLDWithBlankNodes() { + String sampleJsonLD = """ + { + "@context": { + "foaf": "http://xmlns.com/foaf/0.1/" + }, + + "@graph": + [ + { + "@id": "_:b0", + "foaf:knows": {"@id": "_:b1"} + }, + + { + "@id": "_:b1", + "foaf:knows": {"@id": "_:b0"} + } + ] + } + + """; + CoreseModel model = new CoreseModel(); + RDFParser parser = JSONLDParserFactory.getInstance().createRDFParser(RDFFormats.JSON_LD, model, new CoreseAdaptedValueFactory()); + parser.parse(new StringReader(sampleJsonLD)); + + assertEquals(2, model.size()); + BNode b0 = new CoreseAdaptedValueFactory().createBNode("b0"); + BNode b1 = new CoreseAdaptedValueFactory().createBNode("b1"); + IRI knowsPredicate = new CoreseAdaptedValueFactory().createIRI("http://xmlns.com/foaf/0.1/knows"); + assertTrue(model.contains(b0, knowsPredicate, b1)); + assertTrue(model.contains(b1, knowsPredicate, b0)); + } + + @Test + public void testParseJSONLDWithGraphs() { + // Taken from https://www.w3.org/TR/json-ld11/#named-graphs + String sampleJsonLD = """ + { + "@context": { + "generatedAt": { + "@id": "http://www.w3.org/ns/prov#generatedAtTime" + }, + "Person": "http://xmlns.com/foaf/0.1/Person", + "name": "http://xmlns.com/foaf/0.1/name", + "knows": {"@id": "http://xmlns.com/foaf/0.1/knows", "@type": "@id"} + }, + "@id": "http://example.org/foaf-graph", + "generatedAt": { + "@value": "2012-04-09T00:00:00", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "@graph": [ + { + "@id": "http://manu.sporny.org/about#manu", + "@type": "Person", + "name": "Manu Sporny", + "knows": "https://greggkellogg.net/foaf#me" + }, { + "@id": "https://greggkellogg.net/foaf#me", + "@type": "Person", + "name": "Gregg Kellogg", + "knows": "http://manu.sporny.org/about#manu" + } + ] + } + """; + + CoreseModel model = new CoreseModel(); + RDFParser parser = JSONLDParserFactory.getInstance().createRDFParser(RDFFormats.JSON_LD, model, new CoreseAdaptedValueFactory()); + parser.parse(new StringReader(sampleJsonLD)); + + assertEquals(7, model.size()); + IRI graphIRI = new CoreseAdaptedValueFactory().createIRI("http://example.org/foaf-graph"); + IRI generatedAt = new CoreseAdaptedValueFactory().createIRI("http://www.w3.org/ns/prov#generatedAtTime"); + IRI datetimeDatatype = new CoreseAdaptedValueFactory().createIRI("http://www.w3.org/2001/XMLSchema#dateTime"); + Literal generatedAtValue = new CoreseAdaptedValueFactory().createLiteral("2012-04-09T00:00:00", datetimeDatatype) ; + IRI manuIRI = new CoreseAdaptedValueFactory().createIRI("http://manu.sporny.org/about#manu"); + Literal manuName = new CoreseAdaptedValueFactory().createLiteral("Manu Sporny"); + IRI greggIRI = new CoreseAdaptedValueFactory().createIRI("https://greggkellogg.net/foaf#me"); + Literal greggName = new CoreseAdaptedValueFactory().createLiteral("Gregg Kellogg"); + IRI typeIRI = new CoreseAdaptedValueFactory().createIRI("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"); + IRI knowsPredicate = new CoreseAdaptedValueFactory().createIRI("http://xmlns.com/foaf/0.1/knows"); + IRI personType = new CoreseAdaptedValueFactory().createIRI("http://xmlns.com/foaf/0.1/Person"); + IRI namePredicate = new CoreseAdaptedValueFactory().createIRI("http://xmlns.com/foaf/0.1/name"); + + + // { + // a foaf:Person; + assertTrue(model.contains(manuIRI, typeIRI, personType, graphIRI)); + // foaf:name "Manu Sporny"; + assertTrue(model.contains(manuIRI, namePredicate, manuName, graphIRI)); + // foaf:knows . + assertTrue(model.contains(manuIRI, knowsPredicate, greggIRI, graphIRI)); + // + // a foaf:Person; + assertTrue(model.contains(greggIRI, typeIRI, personType, graphIRI)); + // foaf:name "Gregg Kellogg"; + assertTrue(model.contains(greggIRI, namePredicate, greggName, graphIRI)); + // foaf:knows . + assertTrue(model.contains(greggIRI, knowsPredicate, manuIRI, graphIRI)); + //} + // prov:generatedAtTime "2012-04-09T00:00:00"^^xsd:dateTime . + assertTrue(model.contains(graphIRI, generatedAt, generatedAtValue)); + } +}