From 154f0cb0736dae2078d2cc992546f764821284dd Mon Sep 17 00:00:00 2001 From: Pierre Maillot Date: Wed, 4 Jun 2025 17:02:52 +0200 Subject: [PATCH 1/8] RDF formats --- .../core/next/api/base/parser/RDFFormat.java | 114 ++++++++ .../core/next/api/base/parser/RDFFormats.java | 255 ++++++++++++++++++ .../core/next/api/base/parser/RDFParser.java | 9 + .../api/base/parser/RDFParserFactory.java | 6 + .../impl/exception/ParsingErrorException.java | 18 ++ 5 files changed, 402 insertions(+) create mode 100644 src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormat.java create mode 100644 src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormats.java create mode 100644 src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java create mode 100644 src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParserFactory.java create mode 100644 src/main/java/fr/inria/corese/core/next/impl/exception/ParsingErrorException.java diff --git a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormat.java b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormat.java new file mode 100644 index 000000000..4f44df5c5 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormat.java @@ -0,0 +1,114 @@ +package main.java.fr.inria.corese.core.next.api.base.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/base/parser/RDFFormats.java b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormats.java new file mode 100644 index 000000000..3895d6d00 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormats.java @@ -0,0 +1,255 @@ +package main.java.fr.inria.corese.core.next.api.base.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); + + public static final boolean DEFAULT_SUPPORTS_NAMESPACES = true; + public static final boolean DEFAULT_SUPPORTS_CONTEXTS = true; + public 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/base/parser/RDFParser.java b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java new file mode 100644 index 000000000..f619b1430 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java @@ -0,0 +1,9 @@ +package main.java.fr.inria.corese.core.next.api.base.parser; + +public interface RDFParser { + + /** + * Gets the RDF format that this parser can parse. + */ + RDFFormat getRDFFormat(); +} diff --git a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParserFactory.java b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParserFactory.java new file mode 100644 index 000000000..52baf8603 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParserFactory.java @@ -0,0 +1,6 @@ +package main.java.fr.inria.corese.core.next.api.base.parser; + +public interface RDFParserFactory { + + +} 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..786a062bd --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/exception/ParsingErrorException.java @@ -0,0 +1,18 @@ +package main.java.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); + } +} From 9dcbfd1536d3212611c97a89f8d9edef3b69ed51 Mon Sep 17 00:00:00 2001 From: Pierre Maillot Date: Thu, 5 Jun 2025 11:31:16 +0200 Subject: [PATCH 2/8] Barebone RDFParser Factory --- .../core/next/api/base/parser/RDFFormat.java | 2 +- .../core/next/api/base/parser/RDFFormats.java | 8 ++++---- .../core/next/api/base/parser/RDFParser.java | 2 +- .../api/base/parser/RDFParserFactory.java | 12 +++++++++++- .../impl/exception/ParsingErrorException.java | 2 +- .../UnsupportedFileFormatException.java | 19 +++++++++++++++++++ 6 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 src/main/java/fr/inria/corese/core/next/impl/exception/UnsupportedFileFormatException.java diff --git a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormat.java b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormat.java index 4f44df5c5..379fba182 100644 --- a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormat.java +++ b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormat.java @@ -1,4 +1,4 @@ -package main.java.fr.inria.corese.core.next.api.base.parser; +package fr.inria.corese.core.next.api.base.parser; import java.nio.charset.Charset; import java.util.*; diff --git a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormats.java b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormats.java index 3895d6d00..d8d99d8b0 100644 --- a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormats.java +++ b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormats.java @@ -1,4 +1,4 @@ -package main.java.fr.inria.corese.core.next.api.base.parser; +package fr.inria.corese.core.next.api.base.parser; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; @@ -50,9 +50,9 @@ public enum RDFFormats implements RDFFormat { true, false); - public static final boolean DEFAULT_SUPPORTS_NAMESPACES = true; - public static final boolean DEFAULT_SUPPORTS_CONTEXTS = true; - public static final boolean DEFAULT_SUPPORTS_RDF_STAR = 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. diff --git a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java index f619b1430..301ed6211 100644 --- a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java +++ b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java @@ -1,4 +1,4 @@ -package main.java.fr.inria.corese.core.next.api.base.parser; +package fr.inria.corese.core.next.api.base.parser; public interface RDFParser { diff --git a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParserFactory.java b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParserFactory.java index 52baf8603..950de673e 100644 --- a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParserFactory.java +++ b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParserFactory.java @@ -1,6 +1,16 @@ -package main.java.fr.inria.corese.core.next.api.base.parser; +package fr.inria.corese.core.next.api.base.parser; + +import fr.inria.corese.core.next.api.Model; 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); } 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 index 786a062bd..cc6bf4eee 100644 --- 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 @@ -1,4 +1,4 @@ -package main.java.fr.inria.corese.core.next.impl.exception; +package fr.inria.corese.core.next.impl.exception; public class ParsingErrorException extends RuntimeException { 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); + } + +} From 81ceb4bc2da1b3dd1f939ebfc904f64a077a754d Mon Sep 17 00:00:00 2001 From: Pierre Maillot Date: Thu, 5 Jun 2025 11:40:16 +0200 Subject: [PATCH 3/8] barebone parser interface --- .../core/next/api/base/parser/RDFParser.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java index 301ed6211..cf8da275c 100644 --- a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java +++ b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java @@ -1,9 +1,42 @@ package fr.inria.corese.core.next.api.base.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); } From 1463e4a2f00cffc7e7f2128dfcb265ffca13dda3 Mon Sep 17 00:00:00 2001 From: Pierre Maillot Date: Thu, 5 Jun 2025 11:40:16 +0200 Subject: [PATCH 4/8] barebone parser interface --- .../core/next/api/base/parser/RDFParser.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java index 301ed6211..cf8da275c 100644 --- a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java +++ b/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java @@ -1,9 +1,42 @@ package fr.inria.corese.core.next.api.base.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); } From 94010422cf28b838cac3e92cb432416496dd0635 Mon Sep 17 00:00:00 2001 From: Pierre Maillot Date: Wed, 11 Jun 2025 17:36:35 +0200 Subject: [PATCH 5/8] First draft with consumer implementation --- build.gradle.kts | 6 +- .../next/api/{base => }/parser/RDFFormat.java | 2 +- .../api/{base => }/parser/RDFFormats.java | 2 +- .../next/api/{base => }/parser/RDFParser.java | 2 +- .../{base => }/parser/RDFParserFactory.java | 5 +- .../parser/jsonld/FromJsonLDConsumer.java | 55 +++++++++++++++ .../next/impl/parser/jsonld/JSONLDParser.java | 70 +++++++++++++++++++ .../parser/jsonld/JSONLDParserFactory.java | 18 +++++ src/main/java/module-info.java | 2 + 9 files changed, 155 insertions(+), 7 deletions(-) rename src/main/java/fr/inria/corese/core/next/api/{base => }/parser/RDFFormat.java (98%) rename src/main/java/fr/inria/corese/core/next/api/{base => }/parser/RDFFormats.java (99%) rename src/main/java/fr/inria/corese/core/next/api/{base => }/parser/RDFParser.java (95%) rename src/main/java/fr/inria/corese/core/next/api/{base => }/parser/RDFParserFactory.java (68%) create mode 100644 src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/FromJsonLDConsumer.java create mode 100644 src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/JSONLDParser.java create mode 100644 src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/JSONLDParserFactory.java diff --git a/build.gradle.kts b/build.gradle.kts index f7959310b..3a5856a56 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -112,7 +112,7 @@ dependencies { implementation("fr.com.hp.hpl.jena.rdf.arp:arp:2.2.b") // Exposed: RDF/XML parser implementation("org.apache.commons:commons-text:1.10.0") // Used internally (text manipulation) implementation("commons-lang:commons-lang:2.4") // Used internally (basic utilities) - implementation("org.json:json:20240303") // Used internally (JSON) + implementation("org.json:json:20250517") // Used internally (JSON) implementation("fr.inria.lille.shexjava:shexjava-core:1.0") // Used internally (ShEx validation) implementation("org.glassfish.jersey.core:jersey-client:$jersey_version") // Internal HTTP client implementation("org.glassfish.jersey.inject:jersey-hk2:$jersey_version") // Internal Jersey injection @@ -120,8 +120,10 @@ dependencies { implementation("javax.xml.bind:jaxb-api:2.3.1") // Internal XML binding implementation("fr.inria.corese.org.semarglproject:semargl-rdfa:$semargl_version") // RDFa parsing implementation("fr.inria.corese.org.semarglproject:semargl-core:$semargl_version") // RDF core parser - implementation("com.github.jsonld-java:jsonld-java:0.13.4") // Internal JSON-LD parser + implementation("com.github.jsonld-java:jsonld-java:0.13.4") // Legacy internal JSON-LD parser implementation("com.typesafe:config:1.4.3") // Typesafe config + implementation("com.apicatalog:titanium-json-ld:1.6.0") // JSON-LD 1.1 parser + implementation("com.apicatalog:titanium-rdf-api:1.0.0") // RDF API for the JSON-LD parser // === For tests === testImplementation(platform("org.junit:junit-bom:5.12.2")) // JUnit 5 BOM for dependency management diff --git a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormat.java b/src/main/java/fr/inria/corese/core/next/api/parser/RDFFormat.java similarity index 98% rename from src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormat.java rename to src/main/java/fr/inria/corese/core/next/api/parser/RDFFormat.java index 379fba182..ad7988ad7 100644 --- a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormat.java +++ b/src/main/java/fr/inria/corese/core/next/api/parser/RDFFormat.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.next.api.base.parser; +package fr.inria.corese.core.next.api.parser; import java.nio.charset.Charset; import java.util.*; diff --git a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormats.java b/src/main/java/fr/inria/corese/core/next/api/parser/RDFFormats.java similarity index 99% rename from src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormats.java rename to src/main/java/fr/inria/corese/core/next/api/parser/RDFFormats.java index d8d99d8b0..f073fa898 100644 --- a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFFormats.java +++ b/src/main/java/fr/inria/corese/core/next/api/parser/RDFFormats.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.next.api.base.parser; +package fr.inria.corese.core.next.api.parser; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; diff --git a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java b/src/main/java/fr/inria/corese/core/next/api/parser/RDFParser.java similarity index 95% rename from src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java rename to src/main/java/fr/inria/corese/core/next/api/parser/RDFParser.java index cf8da275c..2977bf158 100644 --- a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParser.java +++ b/src/main/java/fr/inria/corese/core/next/api/parser/RDFParser.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.next.api.base.parser; +package fr.inria.corese.core.next.api.parser; import java.io.InputStream; import java.io.Reader; diff --git a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParserFactory.java b/src/main/java/fr/inria/corese/core/next/api/parser/RDFParserFactory.java similarity index 68% rename from src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParserFactory.java rename to src/main/java/fr/inria/corese/core/next/api/parser/RDFParserFactory.java index 950de673e..68f7c16c1 100644 --- a/src/main/java/fr/inria/corese/core/next/api/base/parser/RDFParserFactory.java +++ b/src/main/java/fr/inria/corese/core/next/api/parser/RDFParserFactory.java @@ -1,6 +1,7 @@ -package fr.inria.corese.core.next.api.base.parser; +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 { @@ -11,6 +12,6 @@ public interface RDFParserFactory { * @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); + RDFParser createRDFParser(RDFFormat format, Model model, ValueFactory factory); } diff --git a/src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/FromJsonLDConsumer.java b/src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/FromJsonLDConsumer.java new file mode 100644 index 000000000..e9ac7f3a4 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/FromJsonLDConsumer.java @@ -0,0 +1,55 @@ +package fr.inria.corese.core.next.impl.parser.jsonld; + +import com.apicatalog.rdf.api.RdfConsumerException; +import com.apicatalog.rdf.api.RdfQuadConsumer; +import fr.inria.corese.core.next.api.*; + +public class FromJsonLDConsumer implements RdfQuadConsumer { + + private Model model; + private final ValueFactory valueFactory; + + FromJsonLDConsumer(Model model, ValueFactory valueFactory) { + this.model = model; + this.valueFactory = valueFactory; + } + + @Override + public RdfQuadConsumer quad(String subject, String predicate, String object, String datatype, String language, String direction, String graph) throws RdfConsumerException { + if(subject != null || predicate != null || object != null) { + // Subject + Resource subjectRes = null; + if(RdfQuadConsumer.isBlank(subject)) { + subjectRes = valueFactory.createBNode(subject); + } else { + subjectRes = valueFactory.createIRI(subject); + } + + // Property + IRI propertyRes = valueFactory.createIRI(predicate); + + // Object + Value objectRes = null; + if(RdfQuadConsumer.isValidObject(datatype, language, direction)) { + if(RdfQuadConsumer.isLiteral( datatype, language, direction)) { + if(language != null) { + objectRes = valueFactory.createLiteral(object, language); + } else { + if (datatype != null) { + IRI datatypeIRI = this.valueFactory.createIRI(datatype); + objectRes = valueFactory.createLiteral(object, datatypeIRI); + } else { + objectRes = valueFactory.createLiteral(object); + } + } + } else if(RdfQuadConsumer.isBlank(object)) { + objectRes = valueFactory.createBNode(object); + } else { + objectRes = valueFactory.createIRI(object); + } + } + this.model.add(subjectRes, propertyRes, objectRes); + } + return this; + } +} 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..321935e9a --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/JSONLDParser.java @@ -0,0 +1,70 @@ +package fr.inria.corese.core.next.impl.parser.jsonld; + +import com.apicatalog.jsonld.JsonLd; +import com.apicatalog.jsonld.JsonLdError; +import com.apicatalog.jsonld.document.JsonDocument; +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.impl.exception.ParsingErrorException; + +import java.io.InputStream; +import java.io.Reader; + +public class JSONLDParser implements RDFParser { + + private Model model; + private final ValueFactory valueFactory; + + public JSONLDParser(Model model, ValueFactory factory) { + this.model = model; + this.valueFactory = factory; + } + + @Override + public RDFFormat getRDFFormat() { + return RDFFormats.JSON_LD; + } + + @Override + public void parse(InputStream in) throws ParsingErrorException { + try { + JsonDocument document = JsonDocument.of(in); + JsonLd.toRdf(document).provide(new FromJsonLDConsumer(this.model, this.valueFactory)); + } catch (JsonLdError e) { + throw new ParsingErrorException(e); + } + } + + @Override + public void parse(InputStream in, String baseURI) throws ParsingErrorException { + try { + JsonDocument document = JsonDocument.of(in); + JsonLd.toRdf(document).provide(new FromJsonLDConsumer(this.model, this.valueFactory)); + } catch (JsonLdError e) { + throw new ParsingErrorException(e); + } + } + + @Override + public void parse(Reader reader) throws ParsingErrorException { + try { + JsonDocument document = JsonDocument.of(reader); + JsonLd.toRdf(document).provide(new FromJsonLDConsumer(this.model, this.valueFactory)); + } catch (JsonLdError e) { + throw new ParsingErrorException(e); + } + } + + @Override + public void parse(Reader reader, String baseURI) { + try { + JsonDocument document = JsonDocument.of(reader); + JsonLd.toRdf(document).provide(new FromJsonLDConsumer(this.model, this.valueFactory)); + } catch (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..94235d0af --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/JSONLDParserFactory.java @@ -0,0 +1,18 @@ +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 { + @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..08a7b5d29 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 titanium.json.ld; requires fr.inria.corese.corese_core; + requires titanium.rdf.api; exports fr.inria.corese.core.load; exports fr.inria.corese.core.load.result; From c0cb9c088fe184f93c671b9405acc74b024c7f6f Mon Sep 17 00:00:00 2001 From: Pierre Maillot Date: Thu, 12 Jun 2025 17:13:12 +0200 Subject: [PATCH 6/8] Json parser redone ffrom scratch using Jsonld-java --- build.gradle.kts | 10 +-- .../parser/jsonld/FromJsonLDConsumer.java | 55 ------------- .../next/impl/parser/jsonld/JSONLDParser.java | 80 ++++++++++++------- .../parser/jsonld/JSONLDParserFactory.java | 11 +++ src/main/java/module-info.java | 4 +- .../impl/parser/jsonld/JSONLDParserTest.java | 54 +++++++++++++ 6 files changed, 124 insertions(+), 90 deletions(-) delete mode 100644 src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/FromJsonLDConsumer.java create mode 100644 src/test/java/fr/inria/corese/core/next/impl/parser/jsonld/JSONLDParserTest.java diff --git a/build.gradle.kts b/build.gradle.kts index 130a53d70..0735f6033 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -117,11 +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 - - // === JSON-LD === - implementation("com.apicatalog:titanium-json-ld:1.6.0") // JSON-LD 1.1 parser - implementation("com.apicatalog:titanium-rdf-api:1.0.0") // RDF API for the JSON-LD parser + 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) @@ -154,6 +151,9 @@ 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 + automaticModule("com.apicatalog:titanium-json-ld", "titanium.json.ld") // Module for Titanium JSON-LD + automaticModule("com.apicatalog:titanium-rdf-api", "titanium.rdf.api") // Module for Titanium RDF API + } diff --git a/src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/FromJsonLDConsumer.java b/src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/FromJsonLDConsumer.java deleted file mode 100644 index e9ac7f3a4..000000000 --- a/src/main/java/fr/inria/corese/core/next/impl/parser/jsonld/FromJsonLDConsumer.java +++ /dev/null @@ -1,55 +0,0 @@ -package fr.inria.corese.core.next.impl.parser.jsonld; - -import com.apicatalog.rdf.api.RdfConsumerException; -import com.apicatalog.rdf.api.RdfQuadConsumer; -import fr.inria.corese.core.next.api.*; - -public class FromJsonLDConsumer implements RdfQuadConsumer { - - private Model model; - private final ValueFactory valueFactory; - - FromJsonLDConsumer(Model model, ValueFactory valueFactory) { - this.model = model; - this.valueFactory = valueFactory; - } - - @Override - public RdfQuadConsumer quad(String subject, String predicate, String object, String datatype, String language, String direction, String graph) throws RdfConsumerException { - if(subject != null || predicate != null || object != null) { - // Subject - Resource subjectRes = null; - if(RdfQuadConsumer.isBlank(subject)) { - subjectRes = valueFactory.createBNode(subject); - } else { - subjectRes = valueFactory.createIRI(subject); - } - - // Property - IRI propertyRes = valueFactory.createIRI(predicate); - - // Object - Value objectRes = null; - if(RdfQuadConsumer.isValidObject(datatype, language, direction)) { - if(RdfQuadConsumer.isLiteral( datatype, language, direction)) { - if(language != null) { - objectRes = valueFactory.createLiteral(object, language); - } else { - if (datatype != null) { - IRI datatypeIRI = this.valueFactory.createIRI(datatype); - objectRes = valueFactory.createLiteral(object, datatypeIRI); - } else { - objectRes = valueFactory.createLiteral(object); - } - } - } else if(RdfQuadConsumer.isBlank(object)) { - objectRes = valueFactory.createBNode(object); - } else { - objectRes = valueFactory.createIRI(object); - } - } - this.model.add(subjectRes, propertyRes, objectRes); - } - return this; - } -} 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 index 321935e9a..c8a7efb2d 100644 --- 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 @@ -1,26 +1,34 @@ package fr.inria.corese.core.next.impl.parser.jsonld; -import com.apicatalog.jsonld.JsonLd; -import com.apicatalog.jsonld.JsonLdError; -import com.apicatalog.jsonld.document.JsonDocument; -import fr.inria.corese.core.next.api.Model; -import fr.inria.corese.core.next.api.ValueFactory; +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 Model model; + 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(true); } @Override @@ -31,40 +39,56 @@ public RDFFormat getRDFFormat() { @Override public void parse(InputStream in) throws ParsingErrorException { try { - JsonDocument document = JsonDocument.of(in); - JsonLd.toRdf(document).provide(new FromJsonLDConsumer(this.model, this.valueFactory)); - } catch (JsonLdError e) { - throw new ParsingErrorException(e); + Object input = JsonUtils.fromInputStream(in); + RDFDataset output = (RDFDataset)JsonLdProcessor.toRDF(input, this.commonOptions); + logger.debug("JSON-LD output: {}", output); + for(String gName: output.graphNames()) { + logger.debug("Graph name: {}", gName); + for(RDFDataset.Quad q : output.getQuads(gName)) { + logger.debug("Quad: {}", q); + 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 { + object = valueFactory.createLiteral(q.getObject().getValue()); + } + + if(gName.equals("@default")) { + model.add(subject, predicate, object); + } else { + IRI graph = valueFactory.createIRI(gName); + model.add(subject, predicate, object, graph); + } + } + } + } catch (IOException | JsonLdError e) { + throw new ParsingErrorException(e); } } @Override public void parse(InputStream in, String baseURI) throws ParsingErrorException { - try { - JsonDocument document = JsonDocument.of(in); - JsonLd.toRdf(document).provide(new FromJsonLDConsumer(this.model, this.valueFactory)); - } catch (JsonLdError e) { - throw new ParsingErrorException(e); - } + } @Override public void parse(Reader reader) throws ParsingErrorException { - try { - JsonDocument document = JsonDocument.of(reader); - JsonLd.toRdf(document).provide(new FromJsonLDConsumer(this.model, this.valueFactory)); - } catch (JsonLdError e) { - throw new ParsingErrorException(e); - } + } @Override public void parse(Reader reader, String baseURI) { - try { - JsonDocument document = JsonDocument.of(reader); - JsonLd.toRdf(document).provide(new FromJsonLDConsumer(this.model, this.valueFactory)); - } catch (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 index 94235d0af..d7eeae5b8 100644 --- 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 @@ -8,6 +8,17 @@ 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) { diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 08a7b5d29..999115852 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -13,8 +13,8 @@ requires org.json; // requires org.apache.commons.lang3; requires org.slf4j; - requires titanium.json.ld; requires fr.inria.corese.corese_core; - requires titanium.rdf.api; + 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..d42b4361f --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/impl/parser/jsonld/JSONLDParserTest.java @@ -0,0 +1,54 @@ +package fr.inria.corese.core.next.impl.parser.jsonld; + +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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +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 + void testParseInputStream() { + String sampleJsonLD = "{\n" + + " \"@context\": {\n" + + " \"name\": \"http://xmlns.com/foaf/0.1/name\",\n" + + " \"knows\": \"http://xmlns.com/foaf/0.1/knows\"\n" + + " },\n" + + " \"@id\": \"http://me.markus-lanthaler.com/\",\n" + + " \"name\": \"Markus Lanthaler\",\n" + + " \"knows\": [\n" + + " {\n" + + " \"name\": \"Dave Longley\"\n" + + " }\n" + + " ]\n" + + "}"; + CoreseModel model = new CoreseModel(); + RDFParser parser = JSONLDParserFactory.getInstance().createRDFParser(RDFFormats.JSON_LD, model, new CoreseAdaptedValueFactory()); + parser.parse(new ByteArrayInputStream(sampleJsonLD.getBytes())); + + assertNotEquals(0, model.size()); + } + + @Test + void testParseInputStreamString() { + } + + @Test + void testParseReader() { + } + + @Test + void testParseReaderString() { + } +} From 72bf0b747586532516696ddb986b99ff4764f607 Mon Sep 17 00:00:00 2001 From: Pierre Maillot Date: Fri, 13 Jun 2025 16:07:46 +0200 Subject: [PATCH 7/8] Unit tests --- build.gradle.kts | 2 - .../next/impl/parser/jsonld/JSONLDParser.java | 80 ++++-- .../impl/parser/jsonld/JSONLDParserTest.java | 268 ++++++++++++++++-- 3 files changed, 299 insertions(+), 51 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0735f6033..033c60b50 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -151,8 +151,6 @@ 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 - automaticModule("com.apicatalog:titanium-json-ld", "titanium.json.ld") // Module for Titanium JSON-LD - automaticModule("com.apicatalog:titanium-rdf-api", "titanium.rdf.api") // Module for Titanium RDF API } 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 index c8a7efb2d..f8ed47bcd 100644 --- 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 @@ -28,7 +28,8 @@ public class JSONLDParser implements RDFParser { public JSONLDParser(Model model, ValueFactory factory) { this.model = model; this.valueFactory = factory; - commonOptions.setCompactArrays(true); + commonOptions.setCompactArrays(false); + commonOptions.setBase(null); } @Override @@ -36,59 +37,78 @@ public RDFFormat getRDFFormat() { return RDFFormats.JSON_LD; } - @Override - public void parse(InputStream in) throws ParsingErrorException { + private void parseFromJsonInput(Object input, String baseURI) { try { - Object input = JsonUtils.fromInputStream(in); + if(baseURI != null) { + commonOptions.setBase(baseURI); + } RDFDataset output = (RDFDataset)JsonLdProcessor.toRDF(input, this.commonOptions); logger.debug("JSON-LD output: {}", output); for(String gName: output.graphNames()) { logger.debug("Graph name: {}", gName); for(RDFDataset.Quad q : output.getQuads(gName)) { - logger.debug("Quad: {}", q); - Resource subject = null; - if (q.getSubject().isIRI()) { - subject = valueFactory.createIRI(q.getSubject().getValue()); - } else { - subject = valueFactory.createBNode(q.getSubject().getValue()); - } + logger.debug("Quad: {}", q); + 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 { - object = valueFactory.createLiteral(q.getObject().getValue()); - } - - if(gName.equals("@default")) { - model.add(subject, predicate, object); - } else { - IRI graph = valueFactory.createIRI(gName); - model.add(subject, predicate, object, graph); - } + 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 { + object = valueFactory.createLiteral(q.getObject().getValue()); + } + + if(gName.equals(jsonldjavadefaultGraphName)) { + model.add(subject, predicate, object); + } else { + IRI graph = valueFactory.createIRI(gName); + model.add(subject, predicate, object, graph); + } } } - } catch (IOException | JsonLdError e) { + if(baseURI != null) { + commonOptions.setBase(null); + } + } catch (JsonLdError e) { throw new ParsingErrorException(e); } } @Override - public void parse(InputStream in, String baseURI) throws ParsingErrorException { + 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/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 index d42b4361f..d65e744d6 100644 --- 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 @@ -1,5 +1,9 @@ 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; @@ -7,9 +11,10 @@ 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.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class JSONLDParserTest { @Test @@ -18,37 +23,262 @@ void testGetRDFFormat() { assertEquals(RDFFormats.JSON_LD, parser.getRDFFormat()); } + /** + * Test method for {@link JSONLDParser#parse(java.io.InputStream)}. No relative IRIs in this test. + */ @Test - void testParseInputStream() { - String sampleJsonLD = "{\n" + - " \"@context\": {\n" + - " \"name\": \"http://xmlns.com/foaf/0.1/name\",\n" + - " \"knows\": \"http://xmlns.com/foaf/0.1/knows\"\n" + - " },\n" + - " \"@id\": \"http://me.markus-lanthaler.com/\",\n" + - " \"name\": \"Markus Lanthaler\",\n" + - " \"knows\": [\n" + - " {\n" + - " \"name\": \"Dave Longley\"\n" + - " }\n" + - " ]\n" + - "}"; + 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())); - assertNotEquals(0, model.size()); + 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 - void testParseInputStreamString() { + 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 - void testParseReader() { + 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 - void testParseReaderString() { + 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", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "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": "2012-04-09T00:00:00", + "@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#generatedAt"); + 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"); + + // prov:generatedAtTime "2012-04-09T00:00:00"^^xsd:dateTime . + assertTrue(model.contains(graphIRI, generatedAt, generatedAtValue)); + // { + // 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)); + //} } } From a33d70bbf0506110aaa67745fe49f8986d9dcb09 Mon Sep 17 00:00:00 2001 From: Pierre Maillot Date: Mon, 16 Jun 2025 11:27:24 +0200 Subject: [PATCH 8/8] fix literal typing during parsing --- .../next/impl/parser/jsonld/JSONLDParser.java | 15 +++++++++++---- .../impl/parser/jsonld/JSONLDParserTest.java | 16 ++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) 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 index f8ed47bcd..e2fb6439a 100644 --- 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 @@ -43,11 +43,8 @@ private void parseFromJsonInput(Object input, String baseURI) { commonOptions.setBase(baseURI); } RDFDataset output = (RDFDataset)JsonLdProcessor.toRDF(input, this.commonOptions); - logger.debug("JSON-LD output: {}", output); for(String gName: output.graphNames()) { - logger.debug("Graph name: {}", gName); for(RDFDataset.Quad q : output.getQuads(gName)) { - logger.debug("Quad: {}", q); Resource subject = null; if (q.getSubject().isIRI()) { subject = valueFactory.createIRI(q.getSubject().getValue()); @@ -62,8 +59,18 @@ private void parseFromJsonInput(Object input, String baseURI) { 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 { - object = valueFactory.createLiteral(q.getObject().getValue()); + throw new ParsingErrorException("Unknown object type: " + q.getObject()); } if(gName.equals(jsonldjavadefaultGraphName)) { 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 index d65e744d6..42894cc47 100644 --- 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 @@ -17,6 +17,7 @@ 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()); @@ -220,15 +221,17 @@ public void testParseJSONLDWithGraphs() { { "@context": { "generatedAt": { - "@id": "http://www.w3.org/ns/prov#generatedAtTime", - "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + "@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": "2012-04-09T00:00:00", + "generatedAt": { + "@value": "2012-04-09T00:00:00", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, "@graph": [ { "@id": "http://manu.sporny.org/about#manu", @@ -251,7 +254,7 @@ public void testParseJSONLDWithGraphs() { 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#generatedAt"); + 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"); @@ -263,8 +266,7 @@ public void testParseJSONLDWithGraphs() { IRI personType = new CoreseAdaptedValueFactory().createIRI("http://xmlns.com/foaf/0.1/Person"); IRI namePredicate = new CoreseAdaptedValueFactory().createIRI("http://xmlns.com/foaf/0.1/name"); - // prov:generatedAtTime "2012-04-09T00:00:00"^^xsd:dateTime . - assertTrue(model.contains(graphIRI, generatedAt, generatedAtValue)); + // { // a foaf:Person; assertTrue(model.contains(manuIRI, typeIRI, personType, graphIRI)); @@ -280,5 +282,7 @@ public void testParseJSONLDWithGraphs() { // foaf:knows . assertTrue(model.contains(greggIRI, knowsPredicate, manuIRI, graphIRI)); //} + // prov:generatedAtTime "2012-04-09T00:00:00"^^xsd:dateTime . + assertTrue(model.contains(graphIRI, generatedAt, generatedAtValue)); } }