diff --git a/build.gradle.kts b/build.gradle.kts index a7a028f5f..1f6c052a6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,6 +16,7 @@ plugins { id("org.sonarqube") version "6.1.0.5360" // SonarQube integration id("com.intershop.gradle.javacc") version "5.0.1" // JavaCC plugin for parsing JavaCC files id("antlr") // Antlr plugin for generating parsers from grammar files + `eclipse` // Eclipse plugin for IDE integration } // SonarQube configuration diff --git a/src/main/java/fr/inria/corese/core/next/api/IPrefixHandler.java b/src/main/java/fr/inria/corese/core/next/api/IPrefixHandler.java new file mode 100644 index 000000000..8a205fba5 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/api/IPrefixHandler.java @@ -0,0 +1,180 @@ +package fr.inria.corese.core.next.api; + +import java.util.Map; +import java.util.Set; + +/** + * Interface for unified prefix/namespace handling across all RDF formats. + */ +public interface IPrefixHandler { + + /** + * Gets the namespace IRI for a given prefix. + * + * @param prefix the prefix to look + * @return the namespace IRI + * or null if the prefix is not registered + * @throws IllegalArgumentException if prefix is null + */ + String getNamespace(String prefix); + + /** + * Gets a prefix for a given namespace IRI. + * If multiple prefixes are mapped to the same namespace, + * the result is implementation-dependent. + * + * @param namespace the namespace IRI to look + * @return the prefix (e.g., "ex"), or null if the namespace is not registered + * @throws IllegalArgumentException if namespace is null + */ + String getPrefix(String namespace); + + /** + * Checks if a prefix is registered. + * + * @param prefix the prefix to check + * @return true if the prefix exists in mappings, false otherwise + */ + boolean hasPrefix(String prefix); + + /** + * Returns all registered prefixes. + * Order of iteration is implementation-dependent but should be consistent + * (typically insertion order). + * + * @return an immutable set of all prefixes (including empty prefix if registered) + */ + Set getPrefixes(); + + /** + * Returns all registered namespace IRIs. + * + * @return an immutable set of all namespace IRIs + */ + Set getNamespaces(); + + /** + * Returns all namespace mappings as an immutable map. + * + * @return a map where keys are prefixes and values are namespace IRIs + */ + Map getPrefixMap(); + + /** + * Returns all namespace objects as an immutable set. + * Each Namespace object contains both prefix and IRI. + * + * @return a set of Namespace objects + */ + Set getNamespaceObjects(); + + /** + * Sets or updates a prefix mapping. + * If the prefix already exists, its old namespace is unregistered. + * + * @param prefix the prefix + * @param namespace the namespace IRI + * @throws IllegalArgumentException if prefix or namespace is invalid/null + * @throws IllegalArgumentException if prefix doesn't match XML NCName rules + * (except empty string which is always valid) + */ + void setPrefix(String prefix, String namespace); + + /** + * Sets a namespace using a Namespace object. + * Equivalent to setPrefix(namespace.getPrefix(), namespace.getName()). + * + * @param namespace the Namespace object to register + * @throws IllegalArgumentException if namespace is null or invalid + */ + void setNamespace(Namespace namespace); + + /** + * Removes a prefix mapping. + * + * @param prefix the prefix to remove + * @return true if the prefix was registered and removed, false otherwise + */ + boolean removePrefix(String prefix); + + /** + * Removes all prefix mappings. + * This does not affect the default namespace. + */ + void clear(); + + /** + * Gets the default namespace (used when no prefix is specified). + * + * @return the default namespace IRI, or null if not set + */ + String getDefaultNamespace(); + + /** + * Sets the default namespace. + * This is typically used when parsing/serializing prefixed names without a prefix. + * + * @param namespace the default namespace IRI, or null to unset + */ + void setDefaultNamespace(String namespace); + + /** + * Expands a prefixed name to a full IRI. + * + * @param prefixedName the prefixed name to expand (e.g., "ex:name") + * @return the expanded full IRI, or null if prefix is not registered + * @throws IllegalArgumentException if prefixedName is null + */ + String expandPrefix(String prefixedName); + + /** + * Compresses a full IRI to a prefixed name if possible. + * Uses the longest matching namespace prefix. + * + * @param iri the full IRI to compress + * @return the prefixed name if a matching namespace is found, + * otherwise the original IRI + * @throws IllegalArgumentException if iri is null + */ + String compressIRI(String iri); + + /** + * Checks if a prefix is valid according to XML NCName rules. + * + * @param prefix the prefix to validate + * @return true if the prefix is valid, false otherwise + */ + boolean isValidPrefix(String prefix); + + /** + * Copies all prefix mappings from another handler. + * Existing mappings in this handler are overwritten if prefixes match. + * + * @param other the handler to copy from + * @throws IllegalArgumentException if other is null + */ + void copyFrom(IPrefixHandler other); + + /** + * Returns the number of registered prefix mappings. + * + * @return the count of prefix-namespace pairs + */ + int size(); + + /** + * Checks if there are no registered prefix mappings. + * + * @return true if size() == 0, false otherwise + */ + boolean isEmpty(); + + /** + * Creates a deep copy of this handler. + * The returned handler is independent and can be modified without + * affecting the original. + * + * @return a new IPrefixHandler instance with same mappings + */ + IPrefixHandler clone(); +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/next/impl/common/prefix/PrefixHandler.java b/src/main/java/fr/inria/corese/core/next/impl/common/prefix/PrefixHandler.java new file mode 100644 index 000000000..7ecca954f --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/common/prefix/PrefixHandler.java @@ -0,0 +1,437 @@ +package fr.inria.corese.core.next.impl.common.prefix; + +import fr.inria.corese.core.next.api.IPrefixHandler; +import fr.inria.corese.core.next.api.Namespace; +import fr.inria.corese.core.next.impl.common.vocabulary.*; + +import java.io.Serial; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * Unified prefix handler for managing namespace prefix mappings across all RDF formats. + */ +public class PrefixHandler implements IPrefixHandler, Cloneable { + + /** + * Map of prefix to namespace URI. + */ + private ConcurrentHashMap prefixToNamespace; + + /** + * Map of namespace URI to prefix (for reverse lookup). + */ + private ConcurrentHashMap namespaceToPrefix; + + /** + * The default namespace + */ + private String defaultNamespace; + + /** + * Creates a new PrefixHandler. + * + * @param includeStandardVocabularies if true, initializes with standard W3C vocabularies + * (rdf, rdfs, xsd, owl, foaf) + */ + public PrefixHandler(boolean includeStandardVocabularies) { + this.prefixToNamespace = new ConcurrentHashMap<>(); + this.namespaceToPrefix = new ConcurrentHashMap<>(); + this.defaultNamespace = null; + + if (includeStandardVocabularies) { + initializeStandardVocabularies(); + } + } + + /** + * Initializes the handler with standard W3C vocabulary prefixes by using the + * dedicated Vocabulary enum classes. + */ + private void initializeStandardVocabularies() { + List>> vocabularyClasses = Arrays.asList( + RDF.class, + RDFS.class, + XSD.class, + OWL.class, + FOAF.class + ); + + for (Class> vocabClass : vocabularyClasses) { + Enum[] constants = vocabClass.getEnumConstants(); + if (constants.length > 0) { + Vocabulary vocabInstance = (Vocabulary) constants[0]; + setPrefix(vocabInstance.getPreferredPrefix(), vocabInstance.getNamespace()); + } + } + } + + /** + * Sets or updates a prefix mapping. + * Validates that the prefix matches XML NCName rules. + * + * @param prefix the prefix + * @param namespace the namespace URI + * @throws IllegalArgumentException if prefix is invalid or namespace is null + */ + @Override + public void setPrefix(String prefix, String namespace) { + if (!isValidPrefix(prefix)) { + throw new IllegalArgumentException( + "Invalid prefix format: '" + prefix + + "' (must be empty or match [a-zA-Z_][-a-zA-Z0-9_]*)"); + } + if (namespace == null) { + throw new IllegalArgumentException("Namespace cannot be null"); + } + + String oldNamespace = prefixToNamespace.get(prefix); + if (oldNamespace != null && !oldNamespace.equals(namespace)) { + namespaceToPrefix.remove(oldNamespace); + } + + prefixToNamespace.put(prefix, namespace); + namespaceToPrefix.put(namespace, prefix); + } + + /** + * Sets a namespace using a Namespace object. + * + * @param namespace the Namespace object to register + * @throws IllegalArgumentException if namespace is null + */ + @Override + public void setNamespace(Namespace namespace) { + if (namespace == null) { + throw new IllegalArgumentException("Namespace cannot be null"); + } + setPrefix(namespace.getPrefix(), namespace.getName()); + } + + /** + * Gets the namespace URI for a given prefix. + * + * @param prefix the prefix to look up + * @return the namespace URI, or null if the prefix is not registered + */ + @Override + public String getNamespace(String prefix) { + return prefixToNamespace.get(prefix); + } + + /** + * Gets the prefix for a given namespace URI. + * + * @param namespace the namespace URI to look up + * @return the prefix, or null if the namespace is not registered + */ + @Override + public String getPrefix(String namespace) { + return namespaceToPrefix.get(namespace); + } + + /** + * Checks if a prefix is registered. + * + * @param prefix the prefix to check + * @return true if the prefix is registered, false otherwise + */ + @Override + public boolean hasPrefix(String prefix) { + return prefixToNamespace.containsKey(prefix); + } + + /** + * Gets the default namespace + * + * @return the default namespace URI, or null if not set + */ + @Override + public String getDefaultNamespace() { + return defaultNamespace; + } + + /** + * Sets the default namespace. + * + * @param namespace the default namespace IRI, or null to unset + */ + @Override + public void setDefaultNamespace(String namespace) { + this.defaultNamespace = namespace; + } + + /** + * Removes a prefix mapping. + * + * @param prefix the prefix to remove + * @return true if the prefix was removed, false if it didn't exist + */ + @Override + public boolean removePrefix(String prefix) { + String namespace = prefixToNamespace.remove(prefix); + if (namespace != null) { + namespaceToPrefix.remove(namespace); + return true; + } + return false; + } + + /** + * Removes all prefix mappings. + */ + @Override + public void clear() { + prefixToNamespace.clear(); + namespaceToPrefix.clear(); + defaultNamespace = null; + } + + /** + * Returns all registered prefixes. + * + * @return an immutable set of all prefixes + */ + @Override + public Set getPrefixes() { + return Set.copyOf(prefixToNamespace.keySet()); + } + + /** + * Returns all registered namespaces (namespace URIs). + * + * @return an immutable set of all namespace URIs + */ + @Override + public Set getNamespaces() { + return Set.copyOf(namespaceToPrefix.keySet()); + } + + /** + * Returns all prefix mappings as an unmodifiable map. + * + * @return an unmodifiable map where keys are prefixes and values are namespace URIs + */ + @Override + public Map getPrefixMap() { + return Collections.unmodifiableMap(new HashMap<>(prefixToNamespace)); + } + + /** + * Returns all namespace objects as an immutable set. + * Each Namespace object contains both prefix and IRI. + * + * @return a set of Namespace objects + */ + @Override + public Set getNamespaceObjects() { + return namespaceToPrefix.entrySet().stream() + .map(e -> new SimpleNamespace(e.getValue(), e.getKey())) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + /** + * Returns the number of registered prefix mappings. + * + * @return the number of mappings + */ + @Override + public int size() { + return prefixToNamespace.size(); + } + + /** + * Checks if there are no registered prefix mappings. + * + * @return true if no mappings exist, false otherwise + */ + @Override + public boolean isEmpty() { + return prefixToNamespace.isEmpty(); + } + + /** + * Expands a prefixed name to a full IRI. + * + * @param prefixedName the prefixed name + * @return the full IRI, or null if the prefix is not registered + * @throws IllegalArgumentException if prefixedName is null or doesn't contain ":" + */ + @Override + public String expandPrefix(String prefixedName) { + if (prefixedName == null) { + throw new IllegalArgumentException("Prefixed name cannot be null"); + } + + int colonIndex = prefixedName.indexOf(':'); + + if (colonIndex == -1) { + if (defaultNamespace != null) { + return defaultNamespace + prefixedName; + } + return null; + } + + String prefix = prefixedName.substring(0, colonIndex); + String localName = prefixedName.substring(colonIndex + 1); + + String namespace = getNamespace(prefix); + if (namespace == null) { + return null; + } + + return namespace + localName; + } + + /** + * Compresses a full IRI to a prefixed name if possible. + * + * @param iri the full IRI to compress + * @return the prefixed name if a matching namespace is found, otherwise the original IRI + * @throws IllegalArgumentException if iri is null + */ + @Override + public String compressIRI(String iri) { + if (iri == null) { + throw new IllegalArgumentException("IRI cannot be null"); + } + + String bestPrefix = null; + int bestLength = 0; + + for (String namespace : namespaceToPrefix.keySet()) { + if (iri.startsWith(namespace) && namespace.length() > bestLength) { + bestPrefix = namespaceToPrefix.get(namespace); + bestLength = namespace.length(); + } + } + + if (bestPrefix != null) { + String localName = iri.substring(bestLength); + return bestPrefix + ":" + localName; + } + + return iri; + } + + /** + * Checks if a prefix is valid according to XML NCName rules. + * Empty string "" is considered valid (for Turtle default prefix) + * + * @param prefix the prefix to validate + * @return true if the prefix is valid, false otherwise + */ + @Override + public boolean isValidPrefix(String prefix) { + if (prefix == null) { + return false; + } + if (prefix.isEmpty()) { + return true; + } + return prefix.matches("[a-zA-Z_][-a-zA-Z0-9_]*"); + } + + /** + * Copies all prefix mappings from another handler. + * Existing mappings in this handler are overwritten if prefixes match. + * + * @param other the handler to copy from + * @throws IllegalArgumentException if other is null + */ + @Override + public void copyFrom(IPrefixHandler other) { + if (other == null) { + throw new IllegalArgumentException("Source handler cannot be null"); + } + + new HashMap<>(other.getPrefixMap()).forEach(this::setPrefix); + + String otherDefault = other.getDefaultNamespace(); + if (otherDefault != null) { + this.setDefaultNamespace(otherDefault); + } + } + + /** + * Creates a deep copy of this handler. + * The returned handler is independent and can be modified without + * affecting the original. + * + * @return a new PrefixHandler instance with same mappings + */ + @Override + public PrefixHandler clone() { + try { + PrefixHandler cloned = (PrefixHandler) super.clone(); + cloned.prefixToNamespace = new ConcurrentHashMap<>(this.prefixToNamespace); + cloned.namespaceToPrefix = new ConcurrentHashMap<>(this.namespaceToPrefix); + return cloned; + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("PrefixHandler{"); + boolean first = true; + for (String prefix : prefixToNamespace.keySet()) { + if (!first) { + sb.append(", "); + } + sb.append('"').append(prefix).append("\": \"").append(prefixToNamespace.get(prefix)).append('"'); + first = false; + } + if (defaultNamespace != null) { + sb.append(", default: \"").append(defaultNamespace).append('"'); + } + sb.append("}"); + return sb.toString(); + } + + /** + * Simple immutable implementation of Namespace interface. + * Used internally to create Namespace objects from prefix-URI pairs. + */ + public record SimpleNamespace(String prefix, String name) implements Namespace { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * Compact constructor for validation + */ + public SimpleNamespace { + Objects.requireNonNull(prefix, "Prefix cannot be null"); + Objects.requireNonNull(name, "Name cannot be null"); + } + + @Override + public String getPrefix() { + return prefix; + } + + @Override + public String getName() { + return name; + } + @SuppressWarnings("NullableProblems") + @Override + public int compareTo(Namespace o) { + Objects.requireNonNull(o); + int cmp = this.name.compareTo(o.getName()); + if (cmp != 0) { + return cmp; + } + return this.prefix.compareTo(o.getPrefix()); + } + + @SuppressWarnings("NullableProblems") + @Override + public String toString() { + return prefix + ":" + name; + } + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/parser/common/AbstractTurtleTriGListener.java b/src/main/java/fr/inria/corese/core/next/impl/io/parser/common/AbstractTurtleTriGListener.java index 54a34f313..3b403d3b6 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/parser/common/AbstractTurtleTriGListener.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/parser/common/AbstractTurtleTriGListener.java @@ -2,6 +2,7 @@ import fr.inria.corese.core.next.api.*; import fr.inria.corese.core.next.impl.common.literal.XSD; +import fr.inria.corese.core.next.impl.common.prefix.PrefixHandler; import fr.inria.corese.core.next.impl.common.util.IRIUtils; import fr.inria.corese.core.next.impl.common.vocabulary.RDF; import fr.inria.corese.core.next.impl.exception.ParsingErrorException; @@ -9,8 +10,6 @@ import java.net.URI; import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.Map; /** * Base class for RDF parsers (Turtle, TriG) providing common functionality. @@ -22,7 +21,8 @@ public abstract class AbstractTurtleTriGListener { public final Model model; public final ValueFactory factory; - public final Map prefixMap = new HashMap<>(); + public final PrefixHandler prefixHandler; + public String baseURI; public Resource currentSubject; @@ -41,6 +41,8 @@ public AbstractTurtleTriGListener(Model model, ValueFactory factory, String base this.model = model; this.factory = factory; this.baseURI = baseURI; + this.prefixHandler = new PrefixHandler(true); + initializeBasePrefix(); } @@ -49,7 +51,7 @@ public AbstractTurtleTriGListener(Model model, ValueFactory factory, String base */ public void initializeBasePrefix() { if (this.baseURI != null && !this.baseURI.isEmpty()) { - prefixMap.put(ParserConstants.EMPTY_STRING, this.baseURI); + prefixHandler.setPrefix(ParserConstants.EMPTY_STRING, this.baseURI); model.setNamespace(ParserConstants.EMPTY_STRING, this.baseURI); } } @@ -74,8 +76,7 @@ public String extractAndUnescapeIRI(String text) { public void updateBaseURI(String newBase) { this.baseURI = resolveIRIAgainstBase(newBase); validateIRI(this.baseURI); - - prefixMap.put(ParserConstants.EMPTY_STRING, this.baseURI); + prefixHandler.setPrefix(ParserConstants.EMPTY_STRING, this.baseURI); model.setNamespace(ParserConstants.EMPTY_STRING, this.baseURI); } @@ -88,7 +89,7 @@ public void updateBaseURI(String newBase) { public void registerPrefix(String prefix, String iri) { String resolvedIRI = resolveIRIAgainstBase(iri); validateIRI(resolvedIRI); - prefixMap.put(prefix, resolvedIRI); + prefixHandler.setPrefix(prefix, resolvedIRI); model.setNamespace(prefix, resolvedIRI); explicitlyDeclaredPrefixes.add(prefix); @@ -111,7 +112,7 @@ public String resolveIRI(String raw) { } if (raw.equals(ParserConstants.COLON)) { - String ns = prefixMap.get(ParserConstants.EMPTY_STRING); + String ns = prefixHandler.getNamespace(ParserConstants.EMPTY_STRING); return ns != null ? ns : getEffectiveBaseURI(); } @@ -127,16 +128,9 @@ public String resolveIRI(String raw) { String prefix = parts[0]; String localName = parts[1]; - if (prefix.isEmpty() && !explicitlyDeclaredPrefixes.contains("")) { - throw new ParsingErrorException( - "Syntax error: prefixed name ':' + '" + localName + "' used but ':' prefix was never declared. " + - "Use @prefix : to declare the empty prefix." - ); - } - - if (prefixMap.containsKey(prefix)) { + if (prefixHandler.hasPrefix(prefix)) { localName = unescapeIRI(localName); - String ns = prefixMap.get(prefix); + String ns = prefixHandler.getNamespace(prefix); if (ns != null) { String result = ns + localName; validateIRI(result); @@ -694,4 +688,14 @@ public enum NumericType { */ DOUBLE } + + /** + * Returns the prefix handler for this listener. + * Allows external access to discovered prefixes. + * + * @return the PrefixHandler instance + */ + public PrefixHandler getPrefixHandler() { + return prefixHandler; + } } \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/parser/jsonld/JSONLDParser.java b/src/main/java/fr/inria/corese/core/next/impl/io/parser/jsonld/JSONLDParser.java index b6d085762..527f6e54c 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/parser/jsonld/JSONLDParser.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/parser/jsonld/JSONLDParser.java @@ -1,9 +1,5 @@ package fr.inria.corese.core.next.impl.io.parser.jsonld; -import java.io.InputStream; -import java.io.Reader; -import java.net.URI; - import com.apicatalog.jsonld.JsonLdError; import com.apicatalog.jsonld.JsonLdOptions; import com.apicatalog.jsonld.document.Document; @@ -11,21 +7,20 @@ import com.apicatalog.jsonld.processor.ToRdfProcessor; import com.apicatalog.rdf.api.RdfConsumerException; import com.apicatalog.rdf.api.RdfQuadConsumer; - -import fr.inria.corese.core.next.api.IRI; -import fr.inria.corese.core.next.api.Model; -import fr.inria.corese.core.next.api.Resource; -import fr.inria.corese.core.next.api.Statement; -import fr.inria.corese.core.next.api.Value; -import fr.inria.corese.core.next.api.ValueFactory; +import fr.inria.corese.core.next.api.*; import fr.inria.corese.core.next.api.base.io.RDFFormat; import fr.inria.corese.core.next.api.base.io.parser.AbstractRDFParser; import fr.inria.corese.core.next.api.io.IOOptions; import fr.inria.corese.core.next.impl.common.literal.XSD; +import fr.inria.corese.core.next.impl.common.prefix.PrefixHandler; import fr.inria.corese.core.next.impl.common.util.IRIUtils; import fr.inria.corese.core.next.impl.exception.ParsingErrorException; import fr.inria.corese.core.next.impl.io.common.JSONLDOptions; +import java.io.InputStream; +import java.io.Reader; +import java.net.URI; + /** * Parser for JSON-LD RDF files. This parser is based on the Titanium JSON-LD library. * @@ -36,6 +31,11 @@ public class JSONLDParser extends AbstractRDFParser { private static final String JSONLD_JAVA_DEFAULT_GRAPH = "@default"; + /** + * Prefix handler for managing namespace prefixes. + */ + private final PrefixHandler prefixHandler; + /** * Constructor for JSONLDParser that initializes the model and value factory. * @@ -55,6 +55,7 @@ public JSONLDParser(Model model, ValueFactory factory) { */ public JSONLDParser(Model model, ValueFactory factory, IOOptions config) { super(model, factory, config); + this.prefixHandler = new PrefixHandler(true); } @Override @@ -62,6 +63,15 @@ public RDFFormat getRDFFormat() { return RDFFormat.JSONLD; } + /** + * Returns the prefix handler containing namespace prefixes discovered during parsing. + * + * @return the PrefixHandler instance + */ + public PrefixHandler getPrefixHandler() { + return prefixHandler; + } + /** * Parse the given input stream as JSON-LD. * If baseURI is null, the base URI defined in the option for this parser will be used. @@ -175,4 +185,4 @@ public RdfQuadConsumer quad(String subject, String predicate, String object, Str } }; } -} +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLParser.java b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLParser.java index 5ff514a29..5ad590077 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLParser.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfxml/RDFXMLParser.java @@ -1,9 +1,13 @@ package fr.inria.corese.core.next.impl.io.parser.rdfxml; -import fr.inria.corese.core.next.api.*; +import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.api.Model; +import fr.inria.corese.core.next.api.Resource; +import fr.inria.corese.core.next.api.ValueFactory; import fr.inria.corese.core.next.api.base.io.RDFFormat; import fr.inria.corese.core.next.api.base.io.parser.AbstractRDFParser; import fr.inria.corese.core.next.api.io.IOOptions; +import fr.inria.corese.core.next.impl.common.prefix.PrefixHandler; import fr.inria.corese.core.next.impl.common.vocabulary.RDF; import fr.inria.corese.core.next.impl.exception.ParsingErrorException; import fr.inria.corese.core.next.impl.io.parser.rdfxml.context.RDFXMLContext; @@ -47,6 +51,11 @@ public class RDFXMLParser extends AbstractRDFParser { private final RDFXMLStatementEmitter emitter; + /** + * Prefix handler for managing namespace prefixes discovered during XML parsing. + */ + private final PrefixHandler prefixHandler; + /** * Creates a new parser with a target RDF model and factory. * @@ -68,6 +77,7 @@ public RDFXMLParser(Model model, ValueFactory factory, IOOptions config) { super(model, factory, config); this.ctx = new RDFXMLContext(getModel(), getValueFactory()); this.emitter = new RDFXMLStatementEmitter(model, factory); + this.prefixHandler = new PrefixHandler(true); } @Override @@ -75,6 +85,15 @@ public RDFFormat getRDFFormat() { return format; } + /** + * Returns the prefix handler containing namespace prefixes discovered during parsing. + * + * @return the PrefixHandler instance + */ + public PrefixHandler getPrefixHandler() { + return prefixHandler; + } + @Override public void parse(InputStream in, String baseURI) throws ParsingErrorException { parse(new InputStreamReader(in, StandardCharsets.UTF_8), baseURI); diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractGraphSerializer.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractGraphSerializer.java index a7801bd36..83c88aa40 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractGraphSerializer.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/base/AbstractGraphSerializer.java @@ -1,45 +1,23 @@ package fr.inria.corese.core.next.impl.io.serialization.base; +import fr.inria.corese.core.next.api.*; +import fr.inria.corese.core.next.api.io.serialization.RDFSerializer; +import fr.inria.corese.core.next.impl.common.prefix.PrefixHandler; +import fr.inria.corese.core.next.impl.common.vocabulary.*; +import fr.inria.corese.core.next.impl.exception.SerializationException; +import fr.inria.corese.core.next.impl.io.serialization.option.*; +import fr.inria.corese.core.next.impl.io.serialization.util.SerializationConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.BufferedWriter; import java.io.IOException; -import java.io.UncheckedIOException; import java.io.Writer; import java.net.URI; import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.TreeMap; +import java.util.*; import java.util.stream.Collectors; -import fr.inria.corese.core.next.impl.common.vocabulary.*; -import fr.inria.corese.core.next.impl.exception.ParsingErrorException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import fr.inria.corese.core.next.api.IRI; -import fr.inria.corese.core.next.api.Literal; -import fr.inria.corese.core.next.api.Model; -import fr.inria.corese.core.next.api.Resource; -import fr.inria.corese.core.next.api.Statement; -import fr.inria.corese.core.next.api.Value; -import fr.inria.corese.core.next.api.io.serialization.RDFSerializer; -import fr.inria.corese.core.next.impl.exception.SerializationException; -import fr.inria.corese.core.next.impl.io.serialization.option.AbstractSerializerOption; -import fr.inria.corese.core.next.impl.io.serialization.option.AbstractTFamilyOption; -import fr.inria.corese.core.next.impl.io.serialization.option.BlankNodeStyleEnum; -import fr.inria.corese.core.next.impl.io.serialization.option.LiteralDatatypePolicyEnum; -import fr.inria.corese.core.next.impl.io.serialization.option.PrefixOrderingEnum; -import fr.inria.corese.core.next.impl.io.serialization.util.SerializationConstants; - /** * Abstract base class for RDF serializers based on TriG and Turtle syntax. * This class contains the common logic for serializing RDF models @@ -105,8 +83,14 @@ private AbstractTFamilyOption getTFamilyOption() { */ private void initializePrefixes() { if (option instanceof AbstractTFamilyOption && getTFamilyOption().usePrefixes()) { - for (Map.Entry entry : getTFamilyOption().getCustomPrefixes().entrySet()) { - addPrefixMapping(entry.getValue(), entry.getKey()); + AbstractTFamilyOption tFamilyOption = getTFamilyOption(); + PrefixHandler prefixHandler = tFamilyOption.getPrefixHandler(); + + for (String prefix : prefixHandler.getPrefixes()) { + String namespace = prefixHandler.getNamespace(prefix); + if (namespace != null) { + addPrefixMapping(namespace, prefix); + } } } } @@ -385,11 +369,11 @@ protected void writeLiteral(Writer writer, Literal literal) throws IOException { String value = literal.stringValue(); boolean useTripleQuotes = false; - if (option instanceof AbstractSerializerOption) { - useTripleQuotes = getTFamilyOption().shouldUseTripleQuotes(value); + if (option instanceof AbstractTFamilyOption) { + useTripleQuotes = getTFamilyOption().useMultilineLiterals() && + (value.contains(SerializationConstants.LINE_FEED) || value.contains(SerializationConstants.CARRIAGE_RETURN) || value.contains("\"\"\"")); } - if (useTripleQuotes) { writer.write(String.format("\"\"\"%s\"\"\"", escapeMultilineLiteralString(value))); } else { diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractTFamilyOption.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractTFamilyOption.java index ca15e9787..735ec7edf 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractTFamilyOption.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/option/AbstractTFamilyOption.java @@ -1,9 +1,8 @@ package fr.inria.corese.core.next.impl.io.serialization.option; +import fr.inria.corese.core.next.impl.common.prefix.PrefixHandler; import fr.inria.corese.core.next.impl.io.serialization.util.SerializationConstants; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -37,7 +36,8 @@ public abstract class AbstractTFamilyOption extends AbstractSerializerOption { * A map of custom URI prefixes to be used for serialization, in addition to or instead of * auto-declared prefixes. Useful for enforcing specific prefix names or when {@code autoDeclarePrefixes} is false. */ - protected final Map customPrefixes; // Used for CUSTOM ordering or if autoDeclarePrefixes=false + protected final PrefixHandler prefixHandler; + /** * Whether compact triple syntax (e.g., using ';' for subject/predicate reuse and ',' for object lists) * should be used. This significantly reduces file size and improves readability for formats like Turtle. @@ -111,7 +111,7 @@ protected AbstractTFamilyOption(AbstractTFamilyBuilder builder) { this.usePrefixes = builder.usePrefixes; this.autoDeclarePrefixes = builder.autoDeclarePrefixes; this.prefixOrdering = Objects.requireNonNull(builder.prefixOrdering, "Prefix ordering cannot be null"); - this.customPrefixes = Collections.unmodifiableMap(new HashMap<>(Objects.requireNonNull(builder.customPrefixes, "Custom prefixes map cannot be null"))); + this.prefixHandler = Objects.requireNonNull(builder.prefixHandler, "PrefixHandler cannot be null"); this.useCompactTriples = builder.useCompactTriples; this.useRdfTypeShortcut = builder.useRdfTypeShortcut; this.useCollections = builder.useCollections; @@ -130,7 +130,6 @@ protected AbstractTFamilyOption(AbstractTFamilyBuilder builder) { } } - /** * Checks if prefix declarations should be used for compact IRIs. * @@ -161,10 +160,10 @@ public PrefixOrderingEnum getPrefixOrdering() { /** * Returns an unmodifiable map of custom URI prefixes. * - * @return A map where keys are prefix names and values are namespace URIs. + * @return The {@link PrefixHandler} managing all prefix mappings. */ - public Map getCustomPrefixes() { - return customPrefixes; + public PrefixHandler getPrefixHandler() { + return prefixHandler; } /** @@ -186,7 +185,7 @@ public boolean useRdfTypeShortcut() { } /** - * Checks if Turtle collection syntax `( item1 item2 )` should be used for `rdf:List` structures. + * Checks if Turtle collection syntax should be used for `rdf:List` structures. * * @return {@code true} if collection syntax is enabled, {@code false} otherwise. */ @@ -204,7 +203,7 @@ public BlankNodeStyleEnum getBlankNodeStyle() { } /** - * Checks if multi-line literal syntax (triple quotes) should be used. + * Checks if multi-line literal syntax should be used. * * @return {@code true} if multi-line literals are enabled, {@code false} otherwise. */ @@ -266,53 +265,20 @@ public boolean sortPredicates() { return sortPredicates; } - /** - * Determines if triple quotes should be used for a given literal value. - * This is typically true if multi-line literals are enabled and the value contains newline characters. - * - * @param literalValue The string value of the literal. - * @return {@code true} if triple quotes should be used, {@code false} otherwise. - */ - public boolean shouldUseTripleQuotes(String literalValue) { - return useMultilineLiterals && (literalValue.contains(SerializationConstants.LINE_FEED) || literalValue.contains(SerializationConstants.CARRIAGE_RETURN)); - } - - /** - * Checks if output optimization features (compact triples, subject grouping, pretty-printing) are enabled. - * - * @return {@code true} if any optimization feature is enabled, {@code false} otherwise. - */ - public boolean shouldOptimizeOutput() { - return useCompactTriples || groupBySubject || prettyPrint; - } - - /** - * Checks if inline blank node syntax (`[]`) should be used. - * This is typically true if anonymous blank node style is chosen and compact triples are enabled. - * - * @return {@code true} if inline blank nodes should be used, {@code false} otherwise. - */ - public boolean shouldUseInlineBlankNodes() { - return blankNodeStyle == BlankNodeStyleEnum.ANONYMOUS && useCompactTriples; - } - /** * An abstract base builder for {@link AbstractTFamilyOption}. * This builder provides methods for setting Turtle Trig serialization configuration options. - * It extends {@link AbstractSerializerOption.AbstractBuilder} and uses a recursive type * parameter (`S`) to allow concrete subclass builders to return their own specific type, * enabling fluent API chaining. * * @param The type of the concrete builder extending this abstract builder. */ - public abstract static class AbstractTFamilyBuilder> - extends AbstractSerializerOption.AbstractBuilder { + public abstract static class AbstractTFamilyBuilder> extends AbstractBuilder { protected boolean usePrefixes = true; protected boolean autoDeclarePrefixes = true; protected PrefixOrderingEnum prefixOrdering = PrefixOrderingEnum.ALPHABETICAL; - protected final Map customPrefixes = new HashMap<>(); - + protected PrefixHandler prefixHandler = new PrefixHandler(true); protected boolean useCompactTriples = true; protected boolean useRdfTypeShortcut = true; // Default to false for complexity, specific formats can override @@ -332,7 +298,7 @@ public abstract static class AbstractTFamilyBuilder prefixes) { + public S addPrefixes(Map prefixes) { Objects.requireNonNull(prefixes, "Prefixes map cannot be null"); - this.customPrefixes.putAll(prefixes); + prefixes.forEach(this.prefixHandler::setPrefix); return self(); } @@ -518,8 +496,8 @@ public S sortPredicates(boolean sortPredicates) { * Builds and returns a new {@link AbstractTFamilyOption} instance with the current builder settings. * This method must be implemented by concrete builder subclasses to return their specific configuration type. * - * @return A new {@code AbstractTFamilyConfig} instance or a subclass instance. + * @return A new {@code AbstractTFamilyOption} instance or a subclass instance. */ public abstract AbstractTFamilyOption build(); } -} +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerOption.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerOption.java index bf35b0372..b6855b61b 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerOption.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerOption.java @@ -1,16 +1,11 @@ package fr.inria.corese.core.next.impl.io.serialization.rdfxml; -import fr.inria.corese.core.next.impl.common.vocabulary.OWL; -import fr.inria.corese.core.next.impl.common.vocabulary.RDF; -import fr.inria.corese.core.next.impl.common.vocabulary.RDFS; -import fr.inria.corese.core.next.impl.common.vocabulary.XSD; +import fr.inria.corese.core.next.impl.common.prefix.PrefixHandler; import fr.inria.corese.core.next.impl.io.serialization.option.AbstractSerializerOption; import fr.inria.corese.core.next.impl.io.serialization.option.LiteralDatatypePolicyEnum; import fr.inria.corese.core.next.impl.io.serialization.option.PrefixOrderingEnum; import fr.inria.corese.core.next.impl.io.serialization.util.SerializationConstants; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -19,7 +14,7 @@ * This class extends {@link AbstractSerializerOption} directly as RDF/XML has * distinct serialization characteristics not shared by the Turtle or N-Family formats. * - *

Use the {@link Builder} class to create instances of {@code XmlConfig}. + *

Use the {@link Builder} class to create instances of {@code RDFXMLSerializerOption}. * A predefined default configuration is available via {@link #defaultConfig()}.

*/ public class RDFXMLSerializerOption extends AbstractSerializerOption { @@ -40,11 +35,10 @@ public class RDFXMLSerializerOption extends AbstractSerializerOption { */ protected final PrefixOrderingEnum prefixOrdering; /** - * A map of custom URI prefixes to be used for serialization, in addition to or instead of - * auto-declared prefixes. Useful for enforcing specific prefix names or when {@code autoDeclarePrefixes} is false. - * Keys are prefixes, values are namespace URIs. + * The prefix handler for managing namespace prefix mappings. + * Provides unified prefix management across all RDF formats. */ - protected final Map customPrefixes; + protected final PrefixHandler prefixHandler; /** * Whether human-readable formatting with indentation and newlines (pretty-printing) is enabled. * This makes the output easier for humans to read and debug, but increases file size slightly. @@ -88,7 +82,7 @@ protected RDFXMLSerializerOption(Builder builder) { this.usePrefixes = builder.usePrefixes; this.autoDeclarePrefixes = builder.autoDeclarePrefixes; this.prefixOrdering = Objects.requireNonNull(builder.prefixOrdering, "Prefix ordering cannot be null"); - this.customPrefixes = Collections.unmodifiableMap(new HashMap<>(Objects.requireNonNull(builder.customPrefixes, "Custom prefixes map cannot be null"))); + this.prefixHandler = Objects.requireNonNull(builder.prefixHandler, "PrefixHandler cannot be null"); this.prettyPrint = builder.prettyPrint; this.indent = Objects.requireNonNull(builder.indent, "Indentation string cannot be null"); this.maxLineLength = builder.maxLineLength; @@ -126,12 +120,23 @@ public PrefixOrderingEnum getPrefixOrdering() { } /** - * Returns an unmodifiable map of custom URI prefixes. + * Returns the prefix handler for managing namespace prefix mappings. + * + * @return The {@link PrefixHandler} instance. + */ + public PrefixHandler getPrefixHandler() { + return prefixHandler; + } + + /** + * Returns an unmodifiable map of custom URI prefixes for backward compatibility. * * @return A map where keys are prefix names and values are namespace URIs. + * @deprecated Use {@link #getPrefixHandler()} instead for full prefix management capabilities. */ + @Deprecated public Map getCustomPrefixes() { - return customPrefixes; + return prefixHandler.getPrefixMap(); } /** @@ -188,17 +193,16 @@ public boolean useMultilineLiterals() { return useMultilineLiterals; } - /** * Public Builder for {@link RDFXMLSerializerOption}. - * Provides a fluent API for constructing {@code XmlConfig} instances with default values + * Provides a fluent API for constructing {@code RDFXMLSerializerOption} instances with default values * specific to the RDF/XML format. */ public static class Builder extends AbstractSerializerOption.AbstractBuilder { protected boolean usePrefixes = true; protected boolean autoDeclarePrefixes = true; protected PrefixOrderingEnum prefixOrdering = PrefixOrderingEnum.ALPHABETICAL; - protected final Map customPrefixes = new HashMap<>(); + protected PrefixHandler prefixHandler = new PrefixHandler(true); protected boolean prettyPrint = true; protected String indent = SerializationConstants.DEFAULT_INDENTATION; protected int maxLineLength = 0; @@ -216,14 +220,8 @@ public Builder() { trailingDot(false); // No trailing dot in RDF/XML stableBlankNodeIds(true); // Good for reproducible RDF/XML outputs escapeUnicode(false); // Usually direct UTF-8 for RDF/XML, not unicode escapes - - addCustomPrefix(RDF.getVocabularyPreferredPrefix(), RDF.getVocabularyNamespace()); - addCustomPrefix(RDFS.getVocabularyPreferredPrefix(), RDFS.getVocabularyNamespace()); - addCustomPrefix(XSD.getVocabularyPreferredPrefix(), XSD.getVocabularyNamespace()); - addCustomPrefix(OWL.getVocabularyPreferredPrefix(), OWL.getVocabularyNamespace()); } - /** * Sets whether prefix declarations should be used for compact IRIs. * @@ -259,17 +257,29 @@ public Builder prefixOrdering(PrefixOrderingEnum prefixOrdering) { } /** - * Adds a custom prefix mapping to be used for serialization. + * Sets the PrefixHandler to use for managing prefix mappings. + * + * @param prefixHandler The {@link PrefixHandler} to use. Must not be null. + * @return The builder instance for fluent chaining. + * @throws NullPointerException if prefixHandler is null. + */ + public Builder prefixHandler(PrefixHandler prefixHandler) { + this.prefixHandler = Objects.requireNonNull(prefixHandler, "PrefixHandler cannot be null"); + return self(); + } + + /** + * Adds a custom prefix mapping to the PrefixHandler. * * @param prefix The prefix name (e.g., "ex"). Must not be null. * @param namespace The namespace URI Must not be null. * @return The builder instance for fluent chaining. * @throws NullPointerException if prefix or namespace is null. */ - public Builder addCustomPrefix(String prefix, String namespace) { + public Builder addPrefix(String prefix, String namespace) { Objects.requireNonNull(prefix, "Prefix name cannot be null"); Objects.requireNonNull(namespace, "Namespace URI cannot be null"); - this.customPrefixes.put(prefix, namespace); + this.prefixHandler.setPrefix(prefix, namespace); return self(); } @@ -280,9 +290,9 @@ public Builder addCustomPrefix(String prefix, String namespace) { * @return The builder instance for fluent chaining. * @throws NullPointerException if the provided map is null. */ - public Builder addCustomPrefixes(Map prefixes) { + public Builder addPrefixes(Map prefixes) { Objects.requireNonNull(prefixes, "Prefixes map cannot be null"); - this.customPrefixes.putAll(prefixes); + prefixes.forEach(this.prefixHandler::setPrefix); return self(); } @@ -356,7 +366,7 @@ public Builder useMultilineLiterals(boolean useMultilineLiterals) { /** * Builds and returns a new {@link RDFXMLSerializerOption} instance with the current builder settings. * - * @return A new {@code XmlConfig} instance. + * @return A new {@code RDFXMLSerializerOption} instance. */ @Override public RDFXMLSerializerOption build() { @@ -369,7 +379,7 @@ public RDFXMLSerializerOption build() { * This provides a convenient way to get a standard RDF/XML configuration without * manually building it. * - * @return A {@code XmlConfig} instance with default settings. + * @return A {@code RDFXMLSerializerOption} instance with default settings. */ public static RDFXMLSerializerOption defaultConfig() { return new Builder().build(); diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializerOptions.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializerOptions.java index a1a63e06f..02633159c 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializerOptions.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializerOptions.java @@ -1,15 +1,7 @@ package fr.inria.corese.core.next.impl.io.serialization.trig; -import fr.inria.corese.core.next.impl.common.vocabulary.OWL; -import fr.inria.corese.core.next.impl.common.vocabulary.RDF; -import fr.inria.corese.core.next.impl.common.vocabulary.RDFS; -import fr.inria.corese.core.next.impl.common.vocabulary.XSD; import fr.inria.corese.core.next.impl.io.serialization.option.AbstractTFamilyOption; import fr.inria.corese.core.next.impl.io.serialization.option.BlankNodeStyleEnum; -import fr.inria.corese.core.next.impl.io.serialization.util.SerializationConstants; - -import java.util.HashMap; -import java.util.Map; /** * Configuration for TriG serialization format. @@ -44,12 +36,6 @@ public Builder() { blankNodeStyle(BlankNodeStyleEnum.NAMED); useCollections(false); - Map commonTriGPrefixes = new HashMap<>(); - commonTriGPrefixes.put(RDF.getVocabularyPreferredPrefix(), RDF.getVocabularyNamespace()); - commonTriGPrefixes.put(RDFS.getVocabularyPreferredPrefix(), RDFS.getVocabularyNamespace()); - commonTriGPrefixes.put(XSD.getVocabularyPreferredPrefix(), XSD.getVocabularyNamespace()); - commonTriGPrefixes.put(OWL.getVocabularyPreferredPrefix(), OWL.getVocabularyNamespace()); - addCustomPrefixes(commonTriGPrefixes); } diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerOptions.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerOptions.java index 80aecf81b..bfbc71624 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerOptions.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerOptions.java @@ -1,15 +1,7 @@ package fr.inria.corese.core.next.impl.io.serialization.turtle; -import fr.inria.corese.core.next.impl.common.vocabulary.OWL; -import fr.inria.corese.core.next.impl.common.vocabulary.RDF; -import fr.inria.corese.core.next.impl.common.vocabulary.RDFS; -import fr.inria.corese.core.next.impl.common.vocabulary.XSD; import fr.inria.corese.core.next.impl.io.serialization.option.AbstractTFamilyOption; import fr.inria.corese.core.next.impl.io.serialization.option.BlankNodeStyleEnum; -import fr.inria.corese.core.next.impl.io.serialization.util.SerializationConstants; - -import java.util.HashMap; -import java.util.Map; /** * Configuration for Turtle serialization format. @@ -45,14 +37,6 @@ public Builder() { useCollections(true); blankNodeStyle(BlankNodeStyleEnum.ANONYMOUS); - Map commonTurtlePrefixes = new HashMap<>(); - commonTurtlePrefixes.put(RDF.getVocabularyPreferredPrefix(), RDF.getVocabularyNamespace()); - commonTurtlePrefixes.put(RDFS.getVocabularyPreferredPrefix(), RDFS.getVocabularyNamespace()); - commonTurtlePrefixes.put(XSD.getVocabularyPreferredPrefix(), XSD.getVocabularyNamespace()); - commonTurtlePrefixes.put(OWL.getVocabularyPreferredPrefix(), OWL.getVocabularyNamespace()); - addCustomPrefixes(commonTurtlePrefixes); - - } /** diff --git a/src/test/java/fr/inria/corese/core/next/impl/common/prefix/PrefixHandlerTest.java b/src/test/java/fr/inria/corese/core/next/impl/common/prefix/PrefixHandlerTest.java new file mode 100644 index 000000000..322905c10 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/impl/common/prefix/PrefixHandlerTest.java @@ -0,0 +1,348 @@ +package fr.inria.corese.core.next.impl.common.prefix; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for PrefixHandler. + */ +@DisplayName("Unit Tests for PrefixHandler") +class PrefixHandlerTest { + + private PrefixHandler handler; + + /** + * Initializes a new {@link PrefixHandler} instance before each test. + * The handler is set to start without loading standard vocabularies. + */ + @BeforeEach + void setUp() { + handler = new PrefixHandler(false); + } + + /** + * Tests basic prefix setting and namespace retrieval. + */ + @Test + @DisplayName("Should set and retrieve a prefix") + void testSetAndGetPrefix() { + handler.setPrefix("ex", "http://example.org/"); + assertEquals("http://example.org/", handler.getNamespace("ex")); + } + + /** + * Tests setting and retrieving multiple distinct prefixes. + */ + @Test + @DisplayName("Should handle multiple prefixes") + void testSetAndGetMultiplePrefixes() { + handler.setPrefix("ex", "http://example.org/"); + handler.setPrefix("foaf", "http://xmlns.com/foaf/0.1/"); + + assertEquals("http://example.org/", handler.getNamespace("ex")); + assertEquals("http://xmlns.com/foaf/0.1/", handler.getNamespace("foaf")); + } + + /** + * Tests that requesting a non-existent prefix returns null. + */ + @Test + @DisplayName("Should return null for a non-existent prefix") + void testGetNonExistentPrefix() { + assertNull(handler.getNamespace("unknown")); + } + + + /** + * Tests for {@link IllegalArgumentException} when setting a null prefix. + */ + @Test + @DisplayName("Should throw IllegalArgumentException if prefix is null") + void testSetPrefixNullPrefix() { + assertThrows(IllegalArgumentException.class, () -> { + handler.setPrefix(null, "http://example.org/"); + }); + } + + /** + * Tests for {@link IllegalArgumentException} when setting a null namespace. + */ + @Test + @DisplayName("Should throw IllegalArgumentException if namespace is null") + void testSetPrefixNullNamespace() { + assertThrows(IllegalArgumentException.class, () -> { + handler.setPrefix("ex", null); + }); + } + + + /** + * Tests the reverse lookup functionality, getting the prefix from a namespace URI. + */ + @Test + @DisplayName("Should get prefix for a namespace (reverse lookup)") + void testGetPrefixForNamespace() { + handler.setPrefix("foaf", "http://xmlns.com/foaf/0.1/"); + assertEquals("foaf", handler.getPrefix("http://xmlns.com/foaf/0.1/")); + } + + + /** + * Tests the removal of a prefix and ensures both forward and reverse mappings are cleared. + */ + @Test + @DisplayName("Should remove a prefix and its reverse mapping") + void testRemovePrefix() { + handler.setPrefix("ex", "http://example.org/"); + assertTrue(handler.removePrefix("ex")); + + assertNull(handler.getNamespace("ex")); + assertNull(handler.getPrefix("http://example.org/")); + } + + + /** + * Tests the {@code clear} method to ensure all mappings are removed. + */ + @Test + @DisplayName("Should completely clear the handler") + void testClear() { + handler.setPrefix("ex", "http://example.org/"); + handler.setPrefix("foaf", "http://xmlns.com/foaf/0.1/"); + + handler.clear(); + + assertTrue(handler.isEmpty()); + assertEquals(0, handler.size()); + } + + /** + * Tests the retrieval of the set of all registered prefixes. + */ + @Test + @DisplayName("Should return the set of all prefixes") + void testGetPrefixes() { + handler.setPrefix("ex", "http://example.org/"); + handler.setPrefix("foaf", "http://xmlns.com/foaf/0.1/"); + + var prefixes = handler.getPrefixes(); + assertEquals(2, prefixes.size()); + assertTrue(prefixes.contains("ex")); + assertTrue(prefixes.contains("foaf")); + } + + /** + * Tests the retrieval of the set of all registered namespaces. + */ + @Test + @DisplayName("Should return the set of all namespaces") + void testGetNamespaces() { + handler.setPrefix("ex", "http://example.org/"); + handler.setPrefix("foaf", "http://xmlns.com/foaf/0.1/"); + + var namespaces = handler.getNamespaces(); + assertEquals(2, namespaces.size()); + assertTrue(namespaces.contains("http://example.org/")); + assertTrue(namespaces.contains("http://xmlns.com/foaf/0.1/")); + } + + /** + * Tests the correct count of registered prefix mappings. + */ + @Test + @DisplayName("Should return the correct size") + void testSize() { + assertEquals(0, handler.size()); + + handler.setPrefix("ex", "http://example.org/"); + assertEquals(1, handler.size()); + + handler.setPrefix("foaf", "http://xmlns.com/foaf/0.1/"); + assertEquals(2, handler.size()); + } + + /** + * Tests the {@code isEmpty} method in various states. + */ + @Test + @DisplayName("Should check if the handler is empty") + void testIsEmpty() { + assertTrue(handler.isEmpty()); + + handler.setPrefix("ex", "http://example.org/"); + assertFalse(handler.isEmpty()); + + handler.clear(); + assertTrue(handler.isEmpty()); + } + + + /** + * Tests the successful expansion of a prefixed name to a full IRI. + */ + @Test + @DisplayName("Should expand a prefixed name to a full IRI") + void testExpandPrefix() { + handler.setPrefix("foaf", "http://xmlns.com/foaf/0.1/"); + + String expanded = handler.expandPrefix("foaf:Person"); + assertEquals("http://xmlns.com/foaf/0.1/Person", expanded); + } + + /** + * Tests expansion using the empty string as a prefix (default namespace). + */ + @Test + @DisplayName("Should handle expansion with an empty prefix") + void testExpandPrefixWithEmptyPrefix() { + handler.setPrefix("", "http://example.org/"); + + String expanded = handler.expandPrefix(":localName"); + assertEquals("http://example.org/localName", expanded); + } + + /** + * Tests that expansion of a name with an unknown prefix returns null. + */ + @Test + @DisplayName("Should return null for unknown prefix expansion") + void testExpandPrefixUnknownPrefix() { + String expanded = handler.expandPrefix("unknown:term"); + assertNull(expanded); + } + + /** + * Tests for {@link IllegalArgumentException} when expanding a null input string. + */ + @Test + @DisplayName("Should throw IllegalArgumentException if expansion input is null") + void testExpandPrefixNullInput() { + assertThrows(IllegalArgumentException.class, () -> { + handler.expandPrefix(null); + }); + } + + /** + * Tests the successful compression of a full IRI to a prefixed name. + */ + @Test + @DisplayName("Should compress an IRI to a prefixed name") + void testCompressIRI() { + handler.setPrefix("foaf", "http://xmlns.com/foaf/0.1/"); + + String compressed = handler.compressIRI("http://xmlns.com/foaf/0.1/Person"); + assertEquals("foaf:Person", compressed); + } + + /** + * Tests for {@link IllegalArgumentException} when compressing a null input string. + */ + @Test + @DisplayName("Should throw IllegalArgumentException if compression input is null") + void testCompressIRINullInput() { + assertThrows(IllegalArgumentException.class, () -> { + handler.compressIRI(null); + }); + } + + /** + * Tests that standard W3C vocabularies (rdf, rdfs, xsd, owl) are included + * when the handler is constructed with {@code true}. + */ + @Test + @DisplayName("Should include all standard vocabularies (RDF, RDFS, XSD, OWL, FOAF)") + void testStandardVocabulariesIncluded() { + PrefixHandler handlerWithStd = new PrefixHandler(true); + + assertNotNull(handlerWithStd.getNamespace("rdf"), "RDF should be included."); + assertNotNull(handlerWithStd.getNamespace("rdfs"), "RDFS should be included."); + assertNotNull(handlerWithStd.getNamespace("xsd"), "XSD should be included."); + assertNotNull(handlerWithStd.getNamespace("owl"), "OWL should be included."); + } + + /** + * Tests that standard vocabularies are *not* included when the handler is constructed with {@code false}. + */ + @Test + @DisplayName("Should not include standard vocabularies if disabled") + void testStandardVocabulariesNotIncluded() { + PrefixHandler handlerNoStd = new PrefixHandler(false); + + assertNull(handlerNoStd.getNamespace("rdf"), "RDF should not be included."); + assertNull(handlerNoStd.getNamespace("rdfs"), "RDFS should not be included."); + assertTrue(handlerNoStd.isEmpty(), "Handler should be empty."); + } + + /** + * Tests that a standard vocabulary prefix (like rdf) expands correctly after inclusion. + */ + @Test + @DisplayName("Should allow expansion of included standard vocabularies") + void testStandardVocabularyExpansion() { + PrefixHandler handlerWithStd = new PrefixHandler(true); + + String expanded = handlerWithStd.expandPrefix("rdf:type"); + assertEquals("http://www.w3.org/1999/02/22-rdf-syntax-ns#type", expanded); + } + + + /** + * Tests that all prefix mappings from a source handler are correctly copied to the current handler. + */ + @Test + @DisplayName("Should copy all mappings from another handler") + void testCopyFrom() { + PrefixHandler source = new PrefixHandler(false); + source.setPrefix("ex", "http://example.org/"); + source.setPrefix("foaf", "http://xmlns.com/foaf/0.1/"); + + handler.copyFrom(source); + + assertEquals("http://example.org/", handler.getNamespace("ex")); + assertEquals("http://xmlns.com/foaf/0.1/", handler.getNamespace("foaf")); + assertEquals(2, handler.size()); + } + + /** + * Tests for {@link IllegalArgumentException} when copying from a null handler. + */ + @Test + @DisplayName("Should throw IllegalArgumentException if copy source is null") + void testCopyFromNull() { + assertThrows(IllegalArgumentException.class, () -> { + handler.copyFrom(null); + }); + } + + /** + * Tests that existing mappings in the destination handler are preserved when copying new mappings. + */ + @Test + @DisplayName("Should preserve existing mappings during copy") + void testCopyFromPreservesExisting() { + handler.setPrefix("existing", "http://existing.org/"); + + PrefixHandler source = new PrefixHandler(false); + source.setPrefix("ex", "http://example.org/"); + + handler.copyFrom(source); + + assertEquals("http://existing.org/", handler.getNamespace("existing")); + assertEquals("http://example.org/", handler.getNamespace("ex")); + assertEquals(2, handler.size()); + } + + + /** + * Tests the {@code toString} representation when the handler is empty. + */ + @Test + @DisplayName("Should display correct toString() output for an empty handler") + void testToStringEmpty() { + String str = handler.toString(); + assertEquals("PrefixHandler{}", str); + } +} \ No newline at end of file diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerTest.java index ef79f532f..06bd476cd 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/RDFXMLSerializerTest.java @@ -3,10 +3,10 @@ import fr.inria.corese.core.next.api.Model; import fr.inria.corese.core.next.api.Statement; import fr.inria.corese.core.next.impl.common.vocabulary.XSD; +import fr.inria.corese.core.next.impl.exception.SerializationException; import fr.inria.corese.core.next.impl.io.serialization.TestStatementFactory; import fr.inria.corese.core.next.impl.io.serialization.option.LiteralDatatypePolicyEnum; import fr.inria.corese.core.next.impl.io.serialization.option.PrefixOrderingEnum; -import fr.inria.corese.core.next.impl.exception.SerializationException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -56,7 +56,7 @@ void shouldSerializeSimpleIriTriple() throws SerializationException { RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() .autoDeclarePrefixes(true) .usePrefixes(true) - .addCustomPrefix("foaf", "http://xmlns.com/foaf/0.1/") + .addPrefix("foaf", "http://xmlns.com/foaf/0.1/") .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) .build(); @@ -90,7 +90,7 @@ void shouldHandleBlankNodeSubject() throws SerializationException { RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() .stableBlankNodeIds(true) - .addCustomPrefix("foaf", "http://xmlns.com/foaf/0.1/") + .addPrefix("foaf", "http://xmlns.com/foaf/0.1/") .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) .build(); @@ -123,7 +123,7 @@ void shouldHandleBlankNodeObject() throws SerializationException { RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() .stableBlankNodeIds(true) - .addCustomPrefix("dc", "http://purl.org/dc/elements/1.1/") + .addPrefix("dc", "http://purl.org/dc/elements/1.1/") .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) .build(); @@ -156,7 +156,7 @@ void shouldSerializeLiteralWithStringDatatypeMinimalPolicy() throws Serializatio RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() .literalDatatypePolicy(LiteralDatatypePolicyEnum.MINIMAL) - .addCustomPrefix("foaf", "http://xmlns.com/foaf/0.1/") + .addPrefix("foaf", "http://xmlns.com/foaf/0.1/") .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) .build(); @@ -187,8 +187,8 @@ void shouldSerializeLiteralWithCustomDatatypeMinimalPolicy() throws Serializatio RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() .literalDatatypePolicy(LiteralDatatypePolicyEnum.MINIMAL) - .addCustomPrefix("ex", "http://example.org/vocabulary/") - .addCustomPrefix("xsd", "http://www.w3.org/2001/XMLSchema#") + .addPrefix("ex", "http://example.org/vocabulary/") + .addPrefix("xsd", "http://www.w3.org/2001/XMLSchema#") .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) .build(); @@ -218,7 +218,7 @@ void shouldSerializeLiteralWithLanguage() throws SerializationException { when(mockModel.stream()).thenReturn(Stream.of(stmt)); RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() - .addCustomPrefix("dc", "http://purl.org/dc/elements/1.1/") + .addPrefix("dc", "http://purl.org/dc/elements/1.1/") .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) .build(); @@ -255,8 +255,8 @@ void shouldRespectPrefixOrderingDefault() throws SerializationException { when(mockModel.stream()).thenReturn(Stream.of(stmt1, stmt2)); RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() - .addCustomPrefix("exorg", "http://ex.org/") - .addCustomPrefix("excom", "http://ex.com/") + .addPrefix("exorg", "http://ex.org/") + .addPrefix("excom", "http://ex.com/") .prefixOrdering(PrefixOrderingEnum.USAGE_ORDER) .sortSubjects(false) .build(); @@ -298,7 +298,7 @@ void shouldSortSubjectsAlphabetically() throws SerializationException { RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() .sortSubjects(true) - .addCustomPrefix("ex", "http://ex.org/") + .addPrefix("ex", "http://ex.org/") .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) .build(); @@ -364,7 +364,7 @@ void shouldEscapeXmlContentValues() throws SerializationException { RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() .literalDatatypePolicy(LiteralDatatypePolicyEnum.ALWAYS_TYPED) - .addCustomPrefix("ex", "http://example.org/") + .addPrefix("ex", "http://example.org/") .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) .build(); @@ -469,7 +469,7 @@ void shouldNotGenerateStableBlankNodeIds() throws SerializationException { RDFXMLSerializerOption testConfig = new RDFXMLSerializerOption.Builder() .stableBlankNodeIds(false) .sortSubjects(true) - .addCustomPrefix("ex", "http://example.org/") + .addPrefix("ex", "http://example.org/") .prefixOrdering(PrefixOrderingEnum.ALPHABETICAL) .build(); diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/XmlConfigTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/XmlConfigTest.java index fa4d4932a..126406e5e 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/XmlConfigTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/rdfxml/XmlConfigTest.java @@ -1,5 +1,6 @@ package fr.inria.corese.core.next.impl.io.serialization.rdfxml; +import fr.inria.corese.core.next.impl.common.prefix.PrefixHandler; import fr.inria.corese.core.next.impl.common.vocabulary.OWL; import fr.inria.corese.core.next.impl.common.vocabulary.RDF; import fr.inria.corese.core.next.impl.common.vocabulary.RDFS; @@ -33,13 +34,18 @@ void defaultConfig_shouldReturnExpectedDefaults() { assertTrue(config.autoDeclarePrefixes(), "Default autoDeclarePrefixes should be true for XML"); assertEquals(PrefixOrderingEnum.ALPHABETICAL, config.getPrefixOrdering(), "Default prefixOrdering should be ALPHABETICAL for XML"); - Map expectedPrefixes = new HashMap<>(); - expectedPrefixes.put(RDF.getVocabularyPreferredPrefix(), RDF.getVocabularyNamespace()); - expectedPrefixes.put(RDFS.getVocabularyPreferredPrefix(), RDFS.getVocabularyNamespace()); - expectedPrefixes.put(XSD.getVocabularyPreferredPrefix(), XSD.getVocabularyNamespace()); - expectedPrefixes.put(OWL.getVocabularyPreferredPrefix(), OWL.getVocabularyNamespace()); - assertEquals(expectedPrefixes.size(), config.getCustomPrefixes().size(), "Default custom prefixes size mismatch"); - assertTrue(config.getCustomPrefixes().entrySet().containsAll(expectedPrefixes.entrySet()), "Default custom prefixes should contain common RDF prefixes"); + // Vérifier les préfixes standards via PrefixHandler + PrefixHandler prefixHandler = config.getPrefixHandler(); + assertNotNull(prefixHandler, "PrefixHandler should not be null"); + assertTrue(prefixHandler.hasPrefix(RDF.getVocabularyPreferredPrefix()), "Should contain rdf prefix"); + assertTrue(prefixHandler.hasPrefix(RDFS.getVocabularyPreferredPrefix()), "Should contain rdfs prefix"); + assertTrue(prefixHandler.hasPrefix(XSD.getVocabularyPreferredPrefix()), "Should contain xsd prefix"); + assertTrue(prefixHandler.hasPrefix(OWL.getVocabularyPreferredPrefix()), "Should contain owl prefix"); + + assertEquals(RDF.getVocabularyNamespace(), prefixHandler.getNamespace(RDF.getVocabularyPreferredPrefix())); + assertEquals(RDFS.getVocabularyNamespace(), prefixHandler.getNamespace(RDFS.getVocabularyPreferredPrefix())); + assertEquals(XSD.getVocabularyNamespace(), prefixHandler.getNamespace(XSD.getVocabularyPreferredPrefix())); + assertEquals(OWL.getVocabularyNamespace(), prefixHandler.getNamespace(OWL.getVocabularyPreferredPrefix())); assertTrue(config.prettyPrint(), "Default prettyPrint should be true for XML"); assertEquals(SerializationConstants.DEFAULT_INDENTATION, config.getIndent(), "Default indent should be " + SerializationConstants.DEFAULT_INDENTATION); @@ -83,18 +89,58 @@ void builder_shouldAllowOverridingPrefixOrdering() { } @Test - @DisplayName("Builder should allow adding custom prefixes") + @DisplayName("Builder should allow adding custom prefixes via PrefixHandler") void builder_shouldAllowAddingCustomPrefixes() { String customPrefix = "my"; String customNamespace = "http://my.example.org/"; + RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() - .addCustomPrefix(customPrefix, customNamespace) + .addPrefix(customPrefix, customNamespace) .build(); - assertTrue(config.getCustomPrefixes().containsKey(customPrefix), "Custom prefix should be added"); - assertEquals(customNamespace, config.getCustomPrefixes().get(customPrefix), "Custom prefix namespace should be correct"); - assertTrue(config.getCustomPrefixes().containsKey("rdf")); - assertTrue(config.getCustomPrefixes().containsKey("xsd")); + PrefixHandler prefixHandler = config.getPrefixHandler(); + assertTrue(prefixHandler.hasPrefix(customPrefix), "Custom prefix should be added"); + assertEquals(customNamespace, prefixHandler.getNamespace(customPrefix), "Custom prefix namespace should be correct"); + + // Vérifier que les préfixes standards sont toujours présents + assertTrue(prefixHandler.hasPrefix("rdf")); + assertTrue(prefixHandler.hasPrefix("xsd")); + } + + @Test + @DisplayName("Builder should allow setting custom PrefixHandler") + void builder_shouldAllowSettingCustomPrefixHandler() { + PrefixHandler customHandler = new PrefixHandler(false); // sans vocabulaires standards + customHandler.setPrefix("ex", "http://example.org/"); + customHandler.setPrefix("custom", "http://custom.org/"); + + RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + .prefixHandler(customHandler) + .build(); + + PrefixHandler resultHandler = config.getPrefixHandler(); + assertEquals(customHandler, resultHandler, "PrefixHandler should be the custom one"); + assertTrue(resultHandler.hasPrefix("ex")); + assertTrue(resultHandler.hasPrefix("custom")); + assertFalse(resultHandler.hasPrefix("rdf")); // Pas de vocabulaires standards + } + + @Test + @DisplayName("Builder should allow adding multiple prefixes at once") + void builder_shouldAllowAddingMultiplePrefixes() { + Map customPrefixes = new HashMap<>(); + customPrefixes.put("ex", "http://example.org/"); + customPrefixes.put("custom", "http://custom.org/"); + + RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + .addPrefixes(customPrefixes) + .build(); + + PrefixHandler prefixHandler = config.getPrefixHandler(); + assertTrue(prefixHandler.hasPrefix("ex")); + assertTrue(prefixHandler.hasPrefix("custom")); + assertEquals("http://example.org/", prefixHandler.getNamespace("ex")); + assertEquals("http://custom.org/", prefixHandler.getNamespace("custom")); } @Test @@ -227,5 +273,20 @@ void builder_shouldAllowOverridingIncludeContext() { assertTrue(config.includeContext(), "includeContext should be overridden to true"); } + @Test + @DisplayName("Builder should maintain default prefixes when adding custom ones") + void builder_shouldMaintainDefaultPrefixesWhenAddingCustomOnes() { + RDFXMLSerializerOption config = new RDFXMLSerializerOption.Builder() + .addPrefix("ex", "http://example.org/") + .build(); -} + PrefixHandler prefixHandler = config.getPrefixHandler(); + // Vérifier que les préfixes standards sont toujours là + assertTrue(prefixHandler.hasPrefix("rdf")); + assertTrue(prefixHandler.hasPrefix("xsd")); + assertTrue(prefixHandler.hasPrefix("rdfs")); + assertTrue(prefixHandler.hasPrefix("owl")); + // Et le nouveau préfixe custom + assertTrue(prefixHandler.hasPrefix("ex")); + } +} \ No newline at end of file diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializerOptionsTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializerOptionsTest.java index 7a9c58499..bc8e9496d 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializerOptionsTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/trig/TriGSerializerOptionsTest.java @@ -1,5 +1,6 @@ package fr.inria.corese.core.next.impl.io.serialization.trig; +import fr.inria.corese.core.next.impl.common.prefix.PrefixHandler; import fr.inria.corese.core.next.impl.common.vocabulary.OWL; import fr.inria.corese.core.next.impl.common.vocabulary.RDF; import fr.inria.corese.core.next.impl.common.vocabulary.RDFS; @@ -34,13 +35,18 @@ void defaultConfig_shouldReturnExpectedDefaults() { assertEquals(BlankNodeStyleEnum.NAMED, config.getBlankNodeStyle(), "Default blankNodeStyle should be NAMED for TriG"); assertFalse(config.useCollections(), "Default useCollections should be false for TriG"); - Map expectedPrefixes = new HashMap<>(); - expectedPrefixes.put(RDF.getVocabularyPreferredPrefix(), RDF.getVocabularyNamespace()); - expectedPrefixes.put(RDFS.getVocabularyPreferredPrefix(), RDFS.getVocabularyNamespace()); - expectedPrefixes.put(XSD.getVocabularyPreferredPrefix(), XSD.getVocabularyNamespace()); - expectedPrefixes.put(OWL.getVocabularyPreferredPrefix(), OWL.getVocabularyNamespace()); - assertEquals(expectedPrefixes.size(), config.getCustomPrefixes().size(), "Default custom prefixes size mismatch"); - assertTrue(config.getCustomPrefixes().entrySet().containsAll(expectedPrefixes.entrySet()), "Default custom prefixes should contain common RDF prefixes"); + PrefixHandler prefixHandler = config.getPrefixHandler(); + assertNotNull(prefixHandler, "PrefixHandler should not be null"); + + assertTrue(prefixHandler.hasPrefix(RDF.getVocabularyPreferredPrefix()), "Should contain rdf prefix"); + assertTrue(prefixHandler.hasPrefix(RDFS.getVocabularyPreferredPrefix()), "Should contain rdfs prefix"); + assertTrue(prefixHandler.hasPrefix(XSD.getVocabularyPreferredPrefix()), "Should contain xsd prefix"); + assertTrue(prefixHandler.hasPrefix(OWL.getVocabularyPreferredPrefix()), "Should contain owl prefix"); + + assertEquals(RDF.getVocabularyNamespace(), prefixHandler.getNamespace(RDF.getVocabularyPreferredPrefix())); + assertEquals(RDFS.getVocabularyNamespace(), prefixHandler.getNamespace(RDFS.getVocabularyPreferredPrefix())); + assertEquals(XSD.getVocabularyNamespace(), prefixHandler.getNamespace(XSD.getVocabularyPreferredPrefix())); + assertEquals(OWL.getVocabularyNamespace(), prefixHandler.getNamespace(OWL.getVocabularyPreferredPrefix())); assertTrue(config.usePrefixes(), "Default usePrefixes should be true"); assertTrue(config.autoDeclarePrefixes(), "Default autoDeclarePrefixes should be true"); @@ -88,20 +94,6 @@ void builder_shouldAllowOverridingUseCollections() { assertTrue(config.useCollections(), "useCollections should be overridden to true"); } - @Test - @DisplayName("Builder should allow adding custom prefixes") - void builder_shouldAllowAddingCustomPrefixes() { - String customPrefix = "my"; - String customNamespace = "http://my.example.org/"; - TriGSerializerOptions config = TriGSerializerOptions.builder() - .addCustomPrefix(customPrefix, customNamespace) - .build(); - - assertTrue(config.getCustomPrefixes().containsKey(customPrefix), "Custom prefix should be added"); - assertEquals(customNamespace, config.getCustomPrefixes().get(customPrefix), "Custom prefix namespace should be correct"); - assertTrue(config.getCustomPrefixes().containsKey("rdf")); - assertTrue(config.getCustomPrefixes().containsKey("xsd")); - } @Test @DisplayName("Builder should allow overriding usePrefixes") diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerOptionsTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerOptionsTest.java index 06c4e5f0d..cf2c9eece 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerOptionsTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerOptionsTest.java @@ -1,5 +1,6 @@ package fr.inria.corese.core.next.impl.io.serialization.turtle; +import fr.inria.corese.core.next.impl.common.prefix.PrefixHandler; import fr.inria.corese.core.next.impl.common.vocabulary.OWL; import fr.inria.corese.core.next.impl.common.vocabulary.RDF; import fr.inria.corese.core.next.impl.common.vocabulary.RDFS; @@ -33,13 +34,18 @@ void defaultConfig_shouldReturnExpectedDefaults() { assertTrue(config.useCollections(), "Default useCollections should be true for Turtle"); assertEquals(BlankNodeStyleEnum.ANONYMOUS, config.getBlankNodeStyle(), "Default blankNodeStyle should be ANONYMOUS for Turtle"); - Map expectedPrefixes = new HashMap<>(); - expectedPrefixes.put(RDF.getVocabularyPreferredPrefix(), RDF.getVocabularyNamespace()); - expectedPrefixes.put(RDFS.getVocabularyPreferredPrefix(), RDFS.getVocabularyNamespace()); - expectedPrefixes.put(XSD.getVocabularyPreferredPrefix(), XSD.getVocabularyNamespace()); - expectedPrefixes.put(OWL.getVocabularyPreferredPrefix(), OWL.getVocabularyNamespace()); - assertEquals(expectedPrefixes.size(), config.getCustomPrefixes().size(), "Default custom prefixes size mismatch"); - assertTrue(config.getCustomPrefixes().entrySet().containsAll(expectedPrefixes.entrySet()), "Default custom prefixes should contain common RDF prefixes"); + PrefixHandler prefixHandler = config.getPrefixHandler(); + assertNotNull(prefixHandler, "PrefixHandler should not be null"); + + assertTrue(prefixHandler.hasPrefix(RDF.getVocabularyPreferredPrefix()), "Should contain rdf prefix"); + assertTrue(prefixHandler.hasPrefix(RDFS.getVocabularyPreferredPrefix()), "Should contain rdfs prefix"); + assertTrue(prefixHandler.hasPrefix(XSD.getVocabularyPreferredPrefix()), "Should contain xsd prefix"); + assertTrue(prefixHandler.hasPrefix(OWL.getVocabularyPreferredPrefix()), "Should contain owl prefix"); + + assertEquals(RDF.getVocabularyNamespace(), prefixHandler.getNamespace(RDF.getVocabularyPreferredPrefix())); + assertEquals(RDFS.getVocabularyNamespace(), prefixHandler.getNamespace(RDFS.getVocabularyPreferredPrefix())); + assertEquals(XSD.getVocabularyNamespace(), prefixHandler.getNamespace(XSD.getVocabularyPreferredPrefix())); + assertEquals(OWL.getVocabularyNamespace(), prefixHandler.getNamespace(OWL.getVocabularyPreferredPrefix())); assertTrue(config.usePrefixes(), "Default usePrefixes should be true"); @@ -80,20 +86,7 @@ void builder_shouldAllowOverridingBlankNodeStyle() { assertEquals(BlankNodeStyleEnum.NAMED, config.getBlankNodeStyle(), "blankNodeStyle should be overridden to NAMED"); } - @Test - @DisplayName("Builder should allow adding custom prefixes") - void builder_shouldAllowAddingCustomPrefixes() { - String customPrefix = "my"; - String customNamespace = "http://my.example.org/"; - TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() - .addCustomPrefix(customPrefix, customNamespace) - .build(); - assertTrue(config.getCustomPrefixes().containsKey(customPrefix), "Custom prefix should be added"); - assertEquals(customNamespace, config.getCustomPrefixes().get(customPrefix), "Custom prefix namespace should be correct"); - assertTrue(config.getCustomPrefixes().containsKey("rdf")); - assertTrue(config.getCustomPrefixes().containsKey("xsd")); - } @Test @DisplayName("Builder should allow overriding usePrefixes") diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerTest.java index ee495ed38..d4d2b2f99 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/serialization/turtle/TurtleSerializerTest.java @@ -3,6 +3,7 @@ import fr.inria.corese.core.next.api.*; import fr.inria.corese.core.next.impl.common.literal.RDF; import fr.inria.corese.core.next.impl.common.literal.XSD; +import fr.inria.corese.core.next.impl.common.prefix.PrefixHandler; import fr.inria.corese.core.next.impl.io.parser.ParserFactory; import fr.inria.corese.core.next.impl.io.serialization.SerializerFactory; import fr.inria.corese.core.next.impl.io.serialization.TestStatementFactory; @@ -216,12 +217,15 @@ void testLiteralWithExplicitXsdStringType() throws SerializationException, IOExc StringWriter writer = new StringWriter(); + PrefixHandler prefixHandler = new PrefixHandler(true); + prefixHandler.setPrefix("data", "http://example.org/data/"); + prefixHandler.setPrefix("dc", "http://purl.org/dc/elements/1.1/"); TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .literalDatatypePolicy(LiteralDatatypePolicyEnum.ALWAYS_TYPED) .usePrefixes(true) .autoDeclarePrefixes(true) - .addCustomPrefix("dc", "http://purl.org/dc/elements/1.1/") + .prefixHandler(prefixHandler) .build(); TurtleSerializer turtleSerializer = new TurtleSerializer(mockModel, config);