diff --git a/src/main/java/fr/inria/corese/core/next/api/Namespace.java b/src/main/java/fr/inria/corese/core/next/api/Namespace.java index 76a7f3a52..28e62fa9c 100644 --- a/src/main/java/fr/inria/corese/core/next/api/Namespace.java +++ b/src/main/java/fr/inria/corese/core/next/api/Namespace.java @@ -5,7 +5,7 @@ /** * Represents a namespace with a prefix and the start of an IRI as its name. */ -public interface Namespace extends Serializable, Comparable { +public interface Namespace extends Serializable { /** * @return The prefix of the namespace. @@ -15,7 +15,7 @@ public interface Namespace extends Serializable, Comparable { /** * @return The name of the namespace, which is the start of an IRI. */ - String getName(); + String getNamespace(); /** * @param o diff --git a/src/main/java/fr/inria/corese/core/next/api/base/io/RDFFormat.java b/src/main/java/fr/inria/corese/core/next/api/base/io/RDFFormat.java index 14970afa1..46606499a 100644 --- a/src/main/java/fr/inria/corese/core/next/api/base/io/RDFFormat.java +++ b/src/main/java/fr/inria/corese/core/next/api/base/io/RDFFormat.java @@ -47,7 +47,7 @@ public class RDFFormat extends FileFormat { public static final RDFFormat RDFXML = new RDFFormat( "RDF/XML", List.of("rdf", "xml"), - List.of("application/rdf+xml"), + List.of("application/rdf+xml", "application/xml", "text/xml"), true, false); @@ -65,10 +65,10 @@ public class RDFFormat extends FileFormat { false, true); - public static final RDFFormat RDFa = new RDFFormat( - "RDFa", - List.of("html", "xhtml"), - List.of("text/html", "application/xhtml+xml"), + public static final RDFFormat RDFA = new RDFFormat( + "RDFa 1.1", + List.of("xhtml", "svg", "html"), + List.of("application/xhtml+xml", "image/svg+xml", "application/xml", "text/xml", "text/html"), true, false); @@ -158,7 +158,7 @@ public static Optional byMimeType(String mimeType) { * @return An unmodifiable List of all RdfFormat constants. */ public static List all() { - return List.of(TURTLE, NTRIPLES, NQUADS, JSONLD, RDFXML, TRIG, RDFC_1_0, RDFa); + return List.of(JSONLD, NQUADS, NTRIPLES, RDFA, RDFC_1_0, RDFXML, TRIG, TURTLE); } @Override diff --git a/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractModel.java b/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractModel.java index b0b94d275..3da788dc3 100644 --- a/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractModel.java +++ b/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractModel.java @@ -34,7 +34,7 @@ public Model unmodifiable() { public Namespace setNamespace(String prefix, String name) { Optional existing = getNamespace(prefix); - if (!existing.isPresent() || !existing.get().getName().equals(name)) { + if (!existing.isPresent() || !existing.get().getNamespace().equals(name)) { Namespace namespace = new ModelNamespace(prefix, name); setNamespace(namespace); return namespace; diff --git a/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractNamespace.java b/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractNamespace.java index 795c7959b..8c15b1b4d 100644 --- a/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractNamespace.java +++ b/src/main/java/fr/inria/corese/core/next/api/base/model/AbstractNamespace.java @@ -1,7 +1,6 @@ package fr.inria.corese.core.next.api.base.model; import java.io.Serial; -import java.util.Comparator; import java.util.Objects; import fr.inria.corese.core.next.api.Namespace; @@ -19,22 +18,6 @@ public abstract class AbstractNamespace implements Namespace { @Serial private static final long serialVersionUID = 1L; - /** - * Comparator that orders namespaces by prefix, then by URI. - * Null values are ordered first. - */ - private static final Comparator ORDERING = Comparator.nullsFirst( - Comparator.comparing(Namespace::getPrefix) - .thenComparing(Namespace::getName)); - - /** - * Compares this namespace to another based on prefix and name. - */ - @Override - public int compareTo(Namespace other) { - return ORDERING.compare(this, other); - } - /** * Checks equality based on prefix and name. */ @@ -48,7 +31,7 @@ public boolean equals(Object object) { } Namespace ns = (Namespace) object; return Objects.equals(getPrefix(), ns.getPrefix()) - && Objects.equals(getName(), ns.getName()); + && Objects.equals(getNamespace(), ns.getNamespace()); } /** @@ -56,7 +39,7 @@ public boolean equals(Object object) { */ @Override public int hashCode() { - return Objects.hash(getPrefix(), getName()); + return Objects.hash(getPrefix(), getNamespace()); } /** @@ -64,6 +47,6 @@ public int hashCode() { */ @Override public String toString() { - return getPrefix() + " :: " + getName(); + return getPrefix() + " :: " + getNamespace(); } } 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 index 9e2722fcd..a862494ae 100644 --- 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 @@ -118,7 +118,7 @@ public void setNamespace(Namespace namespace) { if (namespace == null) { throw new IllegalArgumentException("Namespace cannot be null"); } - setPrefix(namespace.getPrefix(), namespace.getName()); + setPrefix(namespace.getPrefix(), namespace.getNamespace()); } /** @@ -441,14 +441,14 @@ public String getPrefix() { } @Override - public String getName() { + public String getNamespace() { return name; } + @SuppressWarnings("NullableProblems") - @Override public int compareTo(Namespace o) { Objects.requireNonNull(o); - int cmp = this.name.compareTo(o.getName()); + int cmp = this.name.compareTo(o.getNamespace()); if (cmp != 0) { return cmp; } diff --git a/src/main/java/fr/inria/corese/core/next/impl/common/vocabulary/RDFa.java b/src/main/java/fr/inria/corese/core/next/impl/common/vocabulary/RDFa.java new file mode 100644 index 000000000..a25b74f26 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/common/vocabulary/RDFa.java @@ -0,0 +1,63 @@ +package fr.inria.corese.core.next.impl.common.vocabulary; + +import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.impl.common.BasicIRI; +import fr.inria.corese.core.next.impl.exception.IncorrectFormatException; + +public enum RDFa implements Vocabulary { + + PGClass("PGClass"), + Pattern("Pattern"), + PrefixOrTermMapping("PrefixOrTermMapping"), + DocumentError("DocumentError"), + Info("Info"), + PrefixRedefinition("PrefixRedefinition"), + UnresolvedCURIE("UnresolvedCURIE"), + UnresolvedTerm("UnresolvedTerm"), + VocabReferenceError("VocabReferenceError"), + context("context"), + copy("copy"), + prefix("prefix"), + term("term"), + uri("uri"), + usesVocabulary("usesVocabulary"), + vocabulary("vocabulary"), + Error("Error"), + PrefixMapping("PrefixMapping"), + TermMapping("TermMapping"), + Warning("Warning"); + + private final IRI iri; + + /** + * Constructor for the RDFa vocabulary enum. + * + * @param localName the local name of the IRI + * @throws IncorrectFormatException if the namespace and the local name do not form a correct IRI + */ + RDFa(String localName) { + this.iri = new BasicIRI(getNamespace(), localName); + } + @Override + public IRI getIRI() { + return this.iri; + } + + @Override + public String getNamespace() { + return getVocabularyNamespace(); + } + + @Override + public String getPreferredPrefix() { + return getVocabularyPreferredPrefix(); + } + + public static String getVocabularyNamespace() { + return "http://www.w3.org/ns/rdfa#"; + } + + public static String getVocabularyPreferredPrefix() { + return "rdfa"; + } +} diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/parser/ParserFactory.java b/src/main/java/fr/inria/corese/core/next/impl/io/parser/ParserFactory.java index 29f5b11fb..0293f1ff0 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/parser/ParserFactory.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/parser/ParserFactory.java @@ -53,7 +53,7 @@ public RDFParser createRDFParser(RDFFormat format, Model model, ValueFactory fac return new TriGParser(model, factory, config); } else if(format == RDFFormat.RDFC_1_0) { return new NQuadsParser(model, factory, config); - } else if (format == RDFFormat.RDFa) { + } else if (format == RDFFormat.RDFA) { return new RDFaParser(model, factory, config); } throw new IllegalArgumentException("Unsupported format: " + format); @@ -80,7 +80,7 @@ public RDFParser createRDFParser(RDFFormat format, Model model, ValueFactory fac return new RDFXMLParser(model, factory); } else if (format == RDFFormat.TRIG) { return new TriGParser(model, factory); - } else if (format == RDFFormat.RDFa) { + } else if (format == RDFFormat.RDFA) { return new RDFaParser(model, factory); } throw new IllegalArgumentException("Unsupported format: " + format); diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/RDFaEvaluationContext.java b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/RDFaEvaluationContext.java deleted file mode 100644 index 088cf6c6a..000000000 --- a/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/RDFaEvaluationContext.java +++ /dev/null @@ -1,176 +0,0 @@ -package fr.inria.corese.core.next.impl.io.parser.rdfa; - -import fr.inria.corese.core.next.api.IRI; -import fr.inria.corese.core.next.api.Resource; -import fr.inria.corese.core.next.api.Value; -import fr.inria.corese.core.next.impl.io.parser.rdfa.model.RDFaIncompleteStatement; - -import java.util.*; - -/** - * This class is to be used during the evaluation of an HTML file to generate triples during the DOM traversal. - * @see RDFa recommandation - */ -public class RDFaEvaluationContext { - - /** - * This will usually be the URL of the document being processed, but it could be some other URL, set by some other mechanism, such as the XHTML base element. The important thing is that it establishes a URL against which relative paths can be resolved. - */ - private IRI baseIri; - - /** - * The initial value will be the same as the initial value of [base], but it will usually change during the course of processing. - */ - private Resource parentSubjectResource ; - - /** - * In some situations the object of a statement becomes the subject of any nested statements, and this property is used to convey this value. Note that this value may be a bnode, since in some situations a number of nested statements are grouped together on one bnode. This means that the bnode must be set in the containing statement and passed down, and this property is used to convey this value. - */ - private Resource parentObjectResource = null; - - /** - * An index of locally defined IRI prefixes - */ - private Map uriMappings = new HashMap<>(); - - /** - * Set of statement in the process of building. - */ - private Set incompleteStatement = new HashSet<>(); - - /** - * The language of the document. Note that there is no default language. - */ - private String language = null; - - public RDFaEvaluationContext(IRI baseIri) { - this.baseIri = baseIri; - this.parentSubjectResource = baseIri; - } - - public RDFaEvaluationContext(IRI baseIri, IRI parentSubjectResource) { - this.baseIri = baseIri; - this.parentSubjectResource = parentSubjectResource; - } - - public RDFaEvaluationContext(RDFaEvaluationContext context) { - this.baseIri = context.baseIri; - this.parentSubjectResource = context.parentSubjectResource; - this.parentObjectResource = context.parentObjectResource; - this.uriMappings = new HashMap<>(context.uriMappings); - this.incompleteStatement = new HashSet<>(context.incompleteStatement); - this.language = context.language; - } - - public IRI baseIri() { - return baseIri; - } - - public RDFaEvaluationContext baseIri(IRI baseIri) { - this.baseIri = baseIri; - return this; - } - - public RDFaEvaluationContext incompleteStatements(Set incompleteStatement) { - this.incompleteStatement = new HashSet<>(incompleteStatement); - return this; - } - - public Iterator getIncompleteStatementIterator() { - return this.incompleteStatement.iterator(); - } - - public RDFaEvaluationContext addStatementWithoutSubject(IRI property, Value object) { - RDFaIncompleteStatement newStatement = new RDFaIncompleteStatement(property); - newStatement.setObject(object); - this.incompleteStatement.add(newStatement); - return this; - } - - public RDFaEvaluationContext addStatementWithoutObject(Resource subject, IRI property) { - RDFaIncompleteStatement newStatement = new RDFaIncompleteStatement(property); - newStatement.setSubject(subject); - this.incompleteStatement.add(newStatement); - return this; - } - - public void clearIncompleteStatements() { - this.incompleteStatement.clear(); - } - - public Resource parentSubjectResource() { - return parentSubjectResource; - } - - public RDFaEvaluationContext parentSubjectResource(Resource parentSubjectResource) { - this.parentSubjectResource = parentSubjectResource; - return this; - } - - public Resource parentObjectResource() { - return parentObjectResource; - } - - public RDFaEvaluationContext parentObjectResource(Resource parentObjectResource) { - this.parentObjectResource = parentObjectResource; - return this; - } - - public Map uriMappings() { - return uriMappings; - } - - public RDFaEvaluationContext uriMappings(Map uriMappings) { - this.uriMappings = uriMappings; - return this; - } - - public boolean hasUriMapping(String prefix) { - return this.uriMappings.containsKey(prefix); - } - - /** - * @param prefix the prefix WITHOUT ":" - * @return the IRI associated to the prefix in this context - */ - public IRI uriMapping(String prefix) { - return this.uriMappings.get(prefix); - } - - public void addUriMapping(String prefix, IRI prefixIri) { - this.uriMappings.put(prefix, prefixIri); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - - sb.append("BaseURI: ").append(this.baseIri.stringValue()).append(" "); - sb.append("Mappings: ["); - this.uriMappings.forEach((key, value) -> sb.append("(").append(key).append(", ").append(value.stringValue()).append(") ")); - sb.append("] "); - if(this.parentSubjectResource != null) { - sb.append("Subject:").append(this.parentSubjectResource.stringValue()).append(" "); - } else { - sb.append("Subject:").append((Object) null).append(" "); - } - if(this.parentObjectResource != null) { - sb.append("Object: ").append(this.parentObjectResource.stringValue()).append(" "); - } else { - sb.append("Object: ").append((Object) null).append(" "); - } - if(! this.incompleteStatement.isEmpty()) { - sb.append(this.incompleteStatement.size()).append(" incomplete statements."); - } - - return sb.toString(); - } - - public String getLanguage() { - return language; - } - - public void setLanguage(String language) { - this.language = language; - } -} diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/RDFaParser.java b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/RDFaParser.java index c601825e5..03cd432e3 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/RDFaParser.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/RDFaParser.java @@ -8,17 +8,18 @@ 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; -import fr.inria.corese.core.next.impl.io.common.IOConstants; -import fr.inria.corese.core.next.impl.io.parser.rdfa.model.RDFaIncompleteStatement; -import fr.inria.corese.core.next.impl.io.parser.util.ParserConstants; -import org.apache.commons.io.input.ReaderInputStream; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Attribute; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; +import fr.inria.corese.core.next.impl.io.parser.rdfa.model.*; +import fr.inria.corese.core.next.impl.io.serialization.util.SerializationConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.DefaultHandler; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; @@ -26,25 +27,33 @@ import java.util.*; /** - * RDFa parser. This parser will load the RDF data stored as RDFa in an HTML page. Its inner implementation is based on the jsoup library. It loads the html page as DOM and process it following the recommended algorithm in the RDFa recommendation. + * SAX-based RDFa 1.1 parser. + * + *

This parser processes XML+RDFa documents (XHTMl, SVG, etc.) using the SAX streaming API. + * It follows the W3C recommendation, using buffers to replace DOM traversal.

+ * + *

+ * This parser does NOT support vocabulary expansion + *

*/ public class RDFaParser extends AbstractRDFParser { + private static final Logger logger = LoggerFactory.getLogger(RDFaParser.class); + private static final String BASE_TAG = "base"; + private static final String XMLNS_PREFIX = "xmlns"; - private static final String REL_ATTR = "rel"; - private static final String REV_ATTR = "rev"; - private static final String CONTENT_ATTR = "content"; - private static final String HREF_ATTR = "href"; - private static final String SRC_ATTR = "src"; - private static final String ABOUT_ATTR = "about"; - private static final String PROPERTY_ATTR = "property"; - private static final String RESOURCE_ATTR = "resource"; - private static final String DATATYPE_ATTR = "datatype"; - private static final String TYPEOF_ATTR = "typeof"; - private static final String LANG_ATTR = "xml:lang"; + private String baseIri = SerializationConstants.getDefaultBaseURI(); - private static final String XMLNS_PREFIX = "xmlns"; + /** + * An index of IRI prefixes + */ + private Map iriMappings = new HashMap<>(); + + /** + * Buffer/Pile of local value to adapt the parsing algorithm to SAX processing + */ + private final LinkedList processingContexts = new LinkedList<>(); public RDFaParser(Model model, ValueFactory factory) { this(model, factory, new RDFaParserOptions.Builder().build()); @@ -52,311 +61,637 @@ public RDFaParser(Model model, ValueFactory factory) { public RDFaParser(Model model, ValueFactory factory, IOOptions config) { super(model, factory, config); - } + if (getConfig() instanceof BaseIRIOptions baseIRIOptions) { + this.baseIri = baseIRIOptions.getBaseIRI(); - @Override - public RDFFormat getRDFFormat() { - return RDFFormat.RDFa; + } + // Initializing the iri mappings with the default prefixes as defined by https://www.w3.org/TR/rdfa-core/#xmlrdfaconformance + for (RDFaInitialPrefixes prefixObject : RDFaInitialPrefixes.values()) { + this.addIriMapping(prefixObject.getPrefix(), getValueFactory().createIRI(prefixObject.getNamespace())); + } } + @Override public void parse(InputStream in) { - if(getConfig() instanceof BaseIRIOptions baseIRIOptions) { - String baseIRI = baseIRIOptions.getBaseIRI(); - parse(new InputStreamReader(in, StandardCharsets.UTF_8), baseIRI); - } else { - parse(new InputStreamReader(in, StandardCharsets.UTF_8), null); - } + parse(new InputStreamReader(in, StandardCharsets.UTF_8), this.baseIri); } @Override public void parse(InputStream in, String baseURIString) { - try { - Document document = Jsoup.parse(in, null, baseURIString); + this.baseIri = baseURIString; + parse(new InputStreamReader(in, StandardCharsets.UTF_8), baseURIString); + } - IRI baseIri = getValueFactory().createIRI(baseURIString); - processDocument(document, baseIri); + @Override + public RDFFormat getRDFFormat() { + return RDFFormat.RDFA; + } + + @Override + public void parse(Reader reader, String baseURI) { + try { + this.baseIri = baseURI; + + SAXParserFactory factory = SAXParserFactory.newInstance(); + SAXParser saxParser = factory.newSAXParser(); + InputSource inputSource = new InputSource(reader); + saxParser.parse(inputSource, new XMLSaxHandler()); + } catch (IOException e) { + throw new ParsingErrorException("Failed to parse XML+RDFa input stream: " + e.getMessage(), e); } catch (Exception e) { - throw new ParsingErrorException("Error during parsing of HTML document", e); + throw new ParsingErrorException("Unexpected error during XML+RDFa parsing: " + e.getMessage(), e); } } + private void addPrefix(String prefix, String uri) { + IRI prefixIRI = getValueFactory().createIRI(uri); + this.addIriMapping(prefix, prefixIRI); + } + /** - * Intermediary function to configure the processing of a document using some basic HTML traversal to determine if a baseIri has been defined in the document. - * If the baseIri in argument is the Corese default base IRI, the value stored in the document is used instead. - * - * @param document Jsoup HTML document to be processed - * @param baseIri An IRI object + * Handles character data between XML elements + * Accumulate the characters in all local values in the pile */ - private void processDocument(Document document, IRI baseIri) { - // If the base Iri in argument is not the default baseIri, then we take it, else we use the one in the document - if (baseIri.stringValue().equals(IOConstants.getDefaultBaseURI())) { - // Looking for the node in the document - IRI baseIriFromXml = baseIri; - Iterator baseElementIterator = document.stream().filter(element -> element.nameIs(BASE_TAG)).iterator(); - while (baseElementIterator.hasNext()) { - Element baseElement = baseElementIterator.next(); - Attribute baseElementHrefAttribute = baseElement.attribute(HREF_ATTR); - if (baseElementHrefAttribute != null) { - String baseIriString = baseElementHrefAttribute.getValue(); - baseIriFromXml = getValueFactory().createIRI(baseIriString); + private void handleCharacters(char[] ch, int start, int length) { + for (RDFaProcessingContext value : this.processingContexts) { + value.addCharacters(ch, start, length); + } + } + + private void clearAllCharactersBuffers() { + for (RDFaProcessingContext value : this.processingContexts) { + value.clearCharacters(); + } + } + + /* + * The algorithm in W3C recommendation is based on DOM processing, but this implementation is made in SAX. + * To reconcile both approaches, the "local values" are stored in a pile of ProcessingContext. The IRI mapping are shared independently. + * All operations except the ones that create literals are done ine this function. + */ + private void startProcessElement(String uri, String localName, String qName, Attributes attrs) { + + // 1 First, the local values are initialized + RDFaProcessingContext processingContext = null; + if(this.processingContexts.size() > 1) { // Not a root element + processingContext = new RDFaProcessingContext(currentProcessingContext().getEvaluationContext()); + processingContext.setRootElement(false); + this.setIriMappings(this.getIriMappings()); + // 13. Next, all elements that are children of the current element are processed using the rules described here, using a new evaluation context, initialized as follows: + // If the skip element flag is 'true' then the new evaluation context is a copy of the current context that was passed in to this level of processing, with the language and list of IRI mappings values replaced with the local values; + if (this.currentProcessingContext().isSkipElement()) { + processingContext.setEvaluationContext(new RDFaEvaluationContext(currentProcessingContext().getEvaluationContext())); + processingContext.getEvaluationContext().setLanguage(this.currentProcessingContext().getCurrentLanguage()); + // Otherwise, the values are: + } else { + Resource oldParentSubject = currentProcessingContext().getEvaluationContext().getParentSubjectResource(); + // the base is set to the base value of the current evaluation context; + processingContext.setEvaluationContext(new RDFaEvaluationContext(currentProcessingContext().getEvaluationContext().getBaseIri())); + // the parent subject is set to the value of new subject, if non-null, or the value of the parent subject of the current evaluation context; + processingContext.getEvaluationContext().setParentSubjectResource(this.currentProcessingContext().getNewSubject()); + // the parent object is set to value of current object resource, if non-null, or the value of new subject, if non-null, or the value of the parent subject of the current evaluation context; + if (this.currentProcessingContext().getCurrentObjectResource() != null) { + processingContext.getEvaluationContext().setParentObjectResource(this.currentProcessingContext().getCurrentObjectResource()); + } else if (this.currentProcessingContext().getNewSubject() != null) { + processingContext.getEvaluationContext().setParentObjectResource(this.currentProcessingContext().getNewSubject()); + } else { + processingContext.getEvaluationContext().setParentObjectResource(oldParentSubject); } + // the list of incomplete triples is set to the local list of incomplete triples; + processingContext.getEvaluationContext().setIncompleteStatements(this.currentProcessingContext().getIncompleteStatements()); + // the list mapping is set to the local list mapping; + processingContext.getEvaluationContext().setListMappings(this.currentProcessingContext().getListMappings()); + // language is set to the value of current language. + processingContext.getEvaluationContext().setLanguage(this.currentProcessingContext().getCurrentLanguage()); + // the default vocabulary is set to the value of the local default vocabulary. + processingContext.getEvaluationContext().setDefaultVocabulary(this.currentProcessingContext().getDefaultVocabulary()); } + } else { + // This is the start of the document + RDFaEvaluationContext startingContext = getNewContext(getValueFactory().createIRI(this.baseIri)); + initializeEvaluationContextMappings(startingContext); + startingContext.setParentSubjectResource(startingContext.getBaseIri()); + startingContext.setParentObjectResource(null); + startingContext.setLanguage(null); + startingContext.setDefaultVocabulary(null); + processingContext = new RDFaProcessingContext(startingContext); + processingContext.setRootElement(true); + } + processingContext.setElementName(qName); + processingContext.setElementAttributes(attrs); + this.processingContexts.addFirst(processingContext); + if(! this.currentProcessingContext().getElementName().equals(qName)) { + throw new ParsingErrorException("Start process element "+ qName +" is not paired with the right context" + this.currentProcessingContext()); + } - baseIri = this.getValueFactory().createIRI(baseIriFromXml.stringValue()); + // HTML-specific base element + if (qName.equals(BASE_TAG) + && isAttributePresent(RDFaAttributes.HREF)) { + Resource resourceBase = getAttributeResourceValue(RDFaAttributes.HREF); + if (resourceBase.isIRI()) { + currentProcessingContext().getEvaluationContext().setBaseIri((IRI) resourceBase); + } } - for (Element element : document.children()) { - processElement(element, new RDFaEvaluationContext(baseIri)); + // 2. The current element is examined for any change to the default vocabulary via @vocab. If @vocab is present and contains a value, the local default vocabulary is updated according to the section on CURIE and IRI Processing. If the value is empty, then the local default vocabulary MUST be reset to the Host Language defined default (if any). + if (isAttributePresent(RDFaAttributes.VOCAB) + && !getAttributeStringValue(RDFaAttributes.VOCAB).isEmpty()) { + this.currentProcessingContext().setDefaultVocabulary(getAttributeStringValue(RDFaAttributes.VOCAB)); } - } - /** - * @param element Current element - * @param context Active context - * @param recursive Processing generally continues recursively through the entire tree of elements available. However, if an author indicates that some branch of the tree should be treated as an XML literal, no further processing should take place on that branch, and setting this flag to false would have that effect. - * @param skipElement Flag thet indicates whether the [current element] can safely be ignored since it has no relevant RDFa attributes. Note that descendant elements will still be processed. - * @see RDFa processing in details - */ - private void processElement(Element element, RDFaEvaluationContext context, boolean recursive, boolean skipElement) { - - // 1. First, the local values are initialized - Resource newSubject = null; - Resource currentObject = null; - Literal currentObjectLiteral = null; - Map currentMappings = context.uriMappings(); - Set incompleteStatementSet = new HashSet<>(); - String language = context.getLanguage(); - - // 2. Next the [current element] is parsed for [URI mapping]s and these are added to the [local list of URI mappings]. Note that a [URI mapping] will simply overwrite any current mapping in the list that has the same name; - // Looking for namespace declarations - // Namespace declaration are done using the XML namespace declaration mechanism, that can be seen as an attributes prefixed by "xmlns" and looks like this: "xmlns:prefix=namespace" - Iterator itAttribute = element.attributes().iterator(); - while(itAttribute.hasNext()) { - Attribute attribute = itAttribute.next(); - if (attribute.getKey().startsWith(XMLNS_PREFIX)) { - String prefixName = attribute.localName(); - IRI prefixNamespace = getValueFactory().createIRI(attribute.getValue(), ""); - context.addUriMapping(prefixName, prefixNamespace); + // 3. The current element is examined for IRI mappings and these are added to the local list of IRI mappings. Note that an IRI mapping will simply overwrite any current mapping in the list that has the same name; + this.currentProcessingContext().getElementAttributes().forEach((String attribute, String attributeValue) -> { + if (attribute.startsWith(XMLNS_PREFIX + ":")) { + String prefixName = attribute.replace(XMLNS_PREFIX + ":", ""); + IRI prefixNamespace = getValueFactory().createIRI(attributeValue, ""); + this.addIriMapping(prefixName, prefixNamespace); } + }); + if (isAttributePresent(RDFaAttributes.PREFIX) + && !getAttributeStringValue(RDFaAttributes.PREFIX).isEmpty()) { + String prefixDeclaration = getAttributeStringValue(RDFaAttributes.PREFIX); + this.addIriMappings(getPrefixesFromDeclaration(prefixDeclaration)); } - // 3. The [current element] is also parsed for any language information, and if present, [current language] is set accordingly; - if (element.attribute(LANG_ATTR) != null) { - String langString = element.attr(LANG_ATTR); - language = langString; + // 4. The current element is also parsed for any language information, and if present, current language is set accordingly; + // Host Languages that incorporate RDFa MAY provide a mechanism for specifying the natural language of an element and its contents (e.g., XML provides the general-purpose XML attribute @xml:lang). + if (isAttributePresent(RDFaAttributes.LANG_ALT) + && !getAttributeStringValue(RDFaAttributes.LANG_ALT).isEmpty()) { + this.currentProcessingContext().setCurrentLanguage(getAttributeStringValue(RDFaAttributes.LANG_ALT)); + } else if (isAttributePresent(RDFaAttributes.LANG) + && !getAttributeStringValue(RDFaAttributes.LANG).isEmpty()) { + this.currentProcessingContext().setCurrentLanguage(getAttributeStringValue(RDFaAttributes.LANG)); } - // 4. If the [current element] contains no @rel or @rev attribute, then the next step is to establish a value for [new subject]. Any of the attributes that can carry a resource can set [new subject]; - if(element.attribute(REL_ATTR) == null && element.attribute(REV_ATTR) == null) { - // [new subject] is set to the URI obtained from the first match from the following rules: - if (element.attribute(ABOUT_ATTR) != null) { // by using the URI from @about, if present, obtained according to the section on CURIE and URI Processing; - Optional newSubjectResource = getResourceFromElementAttribute(element, ABOUT_ATTR, context); - if (newSubjectResource.isPresent()) { - newSubject = newSubjectResource.get(); + // 5. If the current element contains no @rel or @rev attribute, then the next step is to establish a value for new subject. This step has two possible alternatives. + if (!isAttributePresent(RDFaAttributes.REL) + && !isAttributePresent(RDFaAttributes.REV)) { + // 5.1. If the current element contains the @property attribute, but does not contain either the @content or @datatype attributes, then + if (isAttributePresent(RDFaAttributes.PROPERTY) + && !getAttributeStringValue(RDFaAttributes.PROPERTY).isEmpty() + && !isAttributePresent(RDFaAttributes.CONTENT) + && !isAttributePresent(RDFaAttributes.DATATYPE) + && ((isAttributePresent(RDFaAttributes.ABOUT) + && ! getAttributeStringValue(RDFaAttributes.ABOUT).isEmpty()) + || this.currentProcessingContext().isRootElement() + || currentProcessingContext().getEvaluationContext().getParentObjectResource() != null)) { + // new subject is set to the resource obtained from the first match from the following rule: + // by using the resource from @about, if present, obtained according to the section on CURIE and IRI Processing; + if (isAttributePresent(RDFaAttributes.ABOUT)) { + this.currentProcessingContext().setNewSubject(getAttributeResourceValue(RDFaAttributes.ABOUT)); + // otherwise, if the element is the root element of the document, then act as if there is an empty @about present, and process it according to the rule for @about, above; + } else if (this.currentProcessingContext().isRootElement()) { + this.currentProcessingContext().setNewSubject(currentProcessingContext().getEvaluationContext().getBaseIri()); + // otherwise, if parent object is present, new subject is set to the value of parent object. + } else if (currentProcessingContext().getEvaluationContext().getParentObjectResource() != null) { + this.currentProcessingContext().setNewSubject(currentProcessingContext().getEvaluationContext().getParentObjectResource()); } - } else if (element.attribute(SRC_ATTR) != null) { // otherwise, by using the URI from @src, if present, obtained according to the section on CURIE and URI Processing. - Optional newSubjectResource = getResourceFromElementAttribute(element, SRC_ATTR, context); - if (newSubjectResource.isPresent()) { - newSubject = newSubjectResource.get(); + // If @typeof is present then typed resource is set to the resource obtained from the first match from the following rules: + if (isAttributePresent(RDFaAttributes.TYPEOF)) { + // by using the resource from @about, if present, obtained according to the section on CURIE and IRI Processing; + if (isAttributePresent(RDFaAttributes.ABOUT)) { + this.currentProcessingContext().setTypedResource(this.currentProcessingContext().getNewSubject()); + // otherwise, if the element is the root element of the document, then act as if there is an empty @about present and process it according to the previous rule; + } else if (this.currentProcessingContext().isRootElement()) { + Optional emptyAboutResource = resolveStringResource(""); + if (emptyAboutResource.isPresent()) { + this.currentProcessingContext().setTypedResource(emptyAboutResource.get()); + } else { + throw new ParsingErrorException("Expected to be able to generate typedResource from empty CURIE"); + } + // otherwise, + } else { + // by using the resource from @resource, if present, obtained according to the section on CURIE and IRI Processing; + if (isAttributePresent(RDFaAttributes.RESOURCE)) { + this.currentProcessingContext().setTypedResource(getAttributeResourceValue(RDFaAttributes.RESOURCE)); + // otherwise, by using the IRI from @href, if present, obtained according to the section on CURIE and IRI Processing; + } else if (isAttributePresent(RDFaAttributes.HREF)) { + this.currentProcessingContext().setTypedResource(getAttributeResourceValue(RDFaAttributes.HREF)); + // otherwise, by using the IRI from @src, if present, obtained according to the section on CURIE and IRI Processing; + } else if (isAttributePresent(RDFaAttributes.SRC)) { + this.currentProcessingContext().setTypedResource(getAttributeResourceValue(RDFaAttributes.SRC)); + // otherwise, the value of typed resource is set to a newly created bnode. + } else { + this.currentProcessingContext().setTypedResource(getValueFactory().createBNode()); + } + // The value of the current object resource is then set to the value of typed resource. + this.currentProcessingContext().setCurrentObjectResource(this.currentProcessingContext().getTypedResource()); + } } - } else if (element.attribute(RESOURCE_ATTR) != null) { // otherwise, by using the URI from @resource, if present, obtained according to the section on CURIE and URI Processing; - Optional newSubjectResource = getResourceFromElementAttribute(element, RESOURCE_ATTR, context); - if (newSubjectResource.isPresent()) { - newSubject = newSubjectResource.get(); + // 5.2. otherwise: + } else { + // If the element contains an @about, @href, @src, or @resource attribute, new subject is set to the resource obtained as follows: + if (isAttributePresent(RDFaAttributes.ABOUT) + || isAttributePresent(RDFaAttributes.HREF) + || isAttributePresent(RDFaAttributes.SRC) + || isAttributePresent(RDFaAttributes.RESOURCE)) { + // by using the resource from @about, if present, obtained according to the section on CURIE and IRI Processing; + if (isAttributePresent(RDFaAttributes.ABOUT)) { + this.currentProcessingContext().setNewSubject(getAttributeResourceValue(RDFaAttributes.ABOUT)); + // otherwise, by using the resource from @resource, if present, obtained according to the section on CURIE and IRI Processing; + } else if (isAttributePresent(RDFaAttributes.RESOURCE)) { + this.currentProcessingContext().setNewSubject(getAttributeResourceValue(RDFaAttributes.RESOURCE)); + // otherwise, by using the IRI from @href, if present, obtained according to the section on CURIE and IRI Processing; + } else if (isAttributePresent(RDFaAttributes.HREF)) { + this.currentProcessingContext().setNewSubject(getAttributeResourceValue(RDFaAttributes.HREF)); + // otherwise, by using the IRI from @src, if present, obtained according to the section on CURIE and IRI Processing. + } else if (isAttributePresent(RDFaAttributes.SRC)) { + this.currentProcessingContext().setNewSubject(getAttributeResourceValue(RDFaAttributes.SRC)); + } + // otherwise, if no resource is provided by a resource attribute, then the first match from the following rules will apply: + } else { + // if the element is the root element of the document, then act as if there is an empty @about present, and process it according to the rule for @about, above; + if (this.currentProcessingContext().isRootElement()) { + Optional emptyAboutResource = resolveStringResource(""); + if (emptyAboutResource.isPresent()) { + this.currentProcessingContext().setNewSubject(emptyAboutResource.get()); + } else { + throw new ParsingErrorException("Expected to be able to generate newSubject from empty CURIE"); + } + // otherwise, if @typeof is present, then new subject is set to be a newly created bnode; + } else if (isAttributePresent(RDFaAttributes.TYPEOF)) { + this.currentProcessingContext().setNewSubject(getValueFactory().createBNode()); + // otherwise, if parent object is present, new subject is set to the value of parent object. Additionally, if @property is not present then the skip element flag is set to 'true'. + } else if (currentProcessingContext().getEvaluationContext().getParentObjectResource() != null) { + this.currentProcessingContext().setNewSubject(currentProcessingContext().getEvaluationContext().getParentObjectResource()); + if (!isAttributePresent(RDFaAttributes.PROPERTY)) { + this.currentProcessingContext().setSkipElement(true); + } + } } - } else if (element.attribute(HREF_ATTR) != null) { // otherwise, by using the URI from @href, if present, obtained according to the section on CURIE and URI Processing. - Optional newSubjectResource = getResourceFromElementAttribute(element, HREF_ATTR, context); - if (newSubjectResource.isPresent()) { - newSubject = newSubjectResource.get(); + // Finally, if @typeof is present, set the typed resource to the value of new subject. + if (isAttributePresent(RDFaAttributes.TYPEOF)) { + this.currentProcessingContext().setTypedResource(this.currentProcessingContext().getNewSubject()); } - } else if (element.nameIs("body") || element.nameIs("head")) { // if the element is the head or body element then act as if there is an empty @about present, and process it according to the rule for @about, above; - newSubject = context.baseIri(); - } else if (element.attribute(TYPEOF_ATTR) != null) { // if @typeof is present, obtained according to the section on CURIE and URI Processing, then [new subject] is set to be a newly created [bnode]. - newSubject = this.getValueFactory().createBNode(); - } else if (context.parentObjectResource() != null) { // otherwise, if [parent object] is present, [new subject] is set to the value of [parent object]. Additionally, if @property is not present then the [skip element] flag is set to 'true'; - newSubject = context.parentObjectResource(); - if(element.attribute(PROPERTY_ATTR) == null) { - skipElement = true; - } } - } else { - // [new subject] is set to the URI obtained from the first match from the following rules: - if (element.attribute(ABOUT_ATTR) != null) { // by using the URI from @about, if present, obtained according to the section on CURIE and URI Processing; - Optional newSubjectResource = getResourceFromElementAttribute(element, ABOUT_ATTR, context); - if (newSubjectResource.isPresent()) { - newSubject = newSubjectResource.get(); - } - } else if (element.attribute(SRC_ATTR) != null) { // otherwise, by using the URI from @src, if present, obtained according to the section on CURIE and URI Processing. - Optional newSubjectResource = getResourceFromElementAttribute(element, SRC_ATTR, context); - if (newSubjectResource.isPresent()) { - newSubject = newSubjectResource.get(); + } + + // 6. If the current element does contain a @rel or @rev attribute, then the next step is to establish both a value for new subject and a value for current object resource: + if (isAttributePresent(RDFaAttributes.REL) + || isAttributePresent(RDFaAttributes.REV)) { + if (isAttributePresent(RDFaAttributes.ABOUT)) { + this.currentProcessingContext().setNewSubject(getAttributeResourceValue(RDFaAttributes.ABOUT)); + } + if (isAttributePresent(RDFaAttributes.TYPEOF)) { + this.currentProcessingContext().setTypedResource(this.currentProcessingContext().getNewSubject()); + } + if (this.currentProcessingContext().getNewSubject() == null) { + if (this.currentProcessingContext().isRootElement()) { + Optional emptyAboutResource = resolveStringResource(""); + if (emptyAboutResource.isPresent()) { + this.currentProcessingContext().setTypedResource(emptyAboutResource.get()); + } else { + throw new ParsingErrorException("Expected to be able to generate typedResource from empty CURIE"); + } + } else if (currentProcessingContext().getEvaluationContext().getParentObjectResource() != null) { + this.currentProcessingContext().setNewSubject(currentProcessingContext().getEvaluationContext().getParentObjectResource()); } - } else if (element.nameIs("body") || element.nameIs("head")) { // if the element is the head or body element then act as if there is an empty @about present, and process it according to the rule for @about, above; - newSubject = context.baseIri(); - } else if (element.attribute(TYPEOF_ATTR) != null) { // if @typeof is present, obtained according to the section on CURIE and URI Processing, then [new subject] is set to be a newly created [bnode]. - newSubject = this.getValueFactory().createBNode(); - } else if(context.parentObjectResource() != null) { // otherwise, if [parent object] is present, [new subject] is set to that. - newSubject = context.parentObjectResource(); } + if (isAttributePresent(RDFaAttributes.RESOURCE)) { + this.currentProcessingContext().setCurrentObjectResource(getAttributeResourceValue(RDFaAttributes.RESOURCE)); + } else if (isAttributePresent(RDFaAttributes.HREF)) { + this.currentProcessingContext().setCurrentObjectResource(getAttributeResourceValue(RDFaAttributes.HREF)); + } else if (isAttributePresent(RDFaAttributes.SRC)) { + this.currentProcessingContext().setCurrentObjectResource(getAttributeResourceValue(RDFaAttributes.SRC)); + } else if (isAttributePresent(RDFaAttributes.TYPEOF) + && !isAttributePresent(RDFaAttributes.ABOUT)) { + this.currentProcessingContext().setCurrentObjectResource(this.getValueFactory().createBNode()); + } + if (isAttributePresent(RDFaAttributes.TYPEOF) + && !isAttributePresent(RDFaAttributes.ABOUT) + && (this.currentProcessingContext().getCurrentObjectResource() == null + || this.currentProcessingContext().getCurrentObjectResource().isResource())) { + this.currentProcessingContext().setTypedResource(this.currentProcessingContext().getCurrentObjectResource()); + } + } + + // 7. If in any of the previous steps a typed resource was set to a non-null value, it is now used to provide a subject for type values; + if (this.currentProcessingContext().getTypedResource() != null + && isAttributePresent(RDFaAttributes.TYPEOF)) { + Resource typeIri = getAttributeResourceValue(RDFaAttributes.TYPEOF); + this.getModel().add(this.currentProcessingContext().getTypedResource(), RDF.type.getIRI(), typeIri); + } + + // 8. If in any of the previous steps a new subject was set to a non-null value different from the parent object; + if (this.currentProcessingContext().getNewSubject() != null && this.currentProcessingContext().getNewSubject() != currentProcessingContext().getEvaluationContext().getParentObjectResource()) { + this.currentProcessingContext().setListMappings(new HashMap<>()); + } - // Then the [current object resource] is set to the URI obtained from the first match from the following rules: - if (element.attribute(RESOURCE_ATTR) != null) { // by using the URI from @resource, if present, obtained according to the section on CURIE and URI Processing; - Optional newObjectResource = getResourceFromElementAttribute(element, RESOURCE_ATTR, context); - if (newObjectResource.isPresent()) { - currentObject = newObjectResource.get(); + // 9. If in any of the previous steps a current object resource was set to a non-null value, it is now used to generate triples and add entries to the local list mapping: + if (this.currentProcessingContext().getCurrentObjectResource() != null) { + if (isAttributePresent(RDFaAttributes.INLIST) + && isAttributePresent(RDFaAttributes.REL)) { + IRI relResource = (IRI) getAttributeResourceValue(RDFaAttributes.REL); + this.currentProcessingContext().addListMapping(relResource, this.currentProcessingContext().getCurrentObjectResource()); + } + if (!isAttributePresent(RDFaAttributes.INLIST)) { + if (isAttributePresent(RDFaAttributes.REL)) { + Resource relResource = getAttributeResourceValue(RDFaAttributes.REL); + if (relResource.isIRI()) { + this.getModel().add(this.currentProcessingContext().getNewSubject(), (IRI) relResource, this.currentProcessingContext().getCurrentObjectResource()); + } else { + throw new ParsingErrorException("Value of attribute @rel expected to be an IRI but was " + this.currentProcessingContext().getElementAttributes().get(RDFaAttributes.REL.getName())); + } } - } else if (element.attribute(HREF_ATTR) != null) { // otherwise, by using the URI from @href, if present, obtained according to the section on CURIE and URI Processing. - Optional newObjectResource = getResourceFromElementAttribute(element, RESOURCE_ATTR, context); - if (newObjectResource.isPresent()) { - currentObject = newObjectResource.get(); + if (isAttributePresent(RDFaAttributes.REV)) { + Resource revResource = getAttributeResourceValue(RDFaAttributes.REV); + if (!revResource.isIRI()) { + throw new ParsingErrorException("Value of attribute @rev expected to be an IRI but was " + getAttributeStringValue(RDFaAttributes.REV)); + } + if (!this.currentProcessingContext().getCurrentObjectResource().isResource()) { + throw new ParsingErrorException("object resource expected to be a resource but was " + this.currentProcessingContext().getCurrentObjectResource()); + } + this.getModel().add(this.currentProcessingContext().getCurrentObjectResource(), (IRI) revResource, this.currentProcessingContext().getNewSubject()); } } } - // 6. If in any of the previous steps a [new subject] was set to a non-null value, it is now used to provide a subject for type values; - if(newSubject != null) { - if(element.attribute(TYPEOF_ATTR) != null) { // One or more 'types' for the [new subject] can be set by using @typeof. If present, the attribute must contain one or more URIs, obtained according to the section on URI and CURIE Processing, each of which is used to generate a triple as follows: - Optional typeIri = getResourceFromElementAttribute(element, TYPEOF_ATTR, context); - if (typeIri.isPresent()) { - Statement stat = this.getValueFactory().createStatement(newSubject, RDF.type.getIRI(), typeIri.get()); - this.getModel().add(stat); + // 10. If however current object resource was set to null, but there are predicates present, then they must be stored as incomplete triples, pending the discovery of a subject that can be used as the object. Also, current object resource should be set to a newly created bnode (so that the incomplete triples have a subject to connect to if they are ultimately turned into triples); + if (this.currentProcessingContext().getCurrentObjectResource() == null + && (isAttributePresent(RDFaAttributes.REL) + ) || isAttributePresent(RDFaAttributes.REV)) { + if (this.currentProcessingContext().getIncompleteStatements() == null) { + this.currentProcessingContext().setIncompleteStatements(new HashSet<>()); + } + this.currentProcessingContext().setCurrentObjectResource(getValueFactory().createBNode()); + if (isAttributePresent(RDFaAttributes.REL)) { + if (!getAttributeResourceValue(RDFaAttributes.REL).isIRI()) { + throw new ParsingErrorException("Value of attribute @rel expected to be an IRI but was " + this.currentProcessingContext().getElementAttributes().get(RDFaAttributes.REL.getName())); + } + IRI relIRI = (IRI) getAttributeResourceValue(RDFaAttributes.REL); + if (isAttributePresent(RDFaAttributes.INLIST)) { + if (!this.currentProcessingContext().getListMappings().containsKey(relIRI)) { + this.currentProcessingContext().addListMappings(relIRI, new HashSet<>()); + } + this.currentProcessingContext().addIncompleteStatement(new RDFaIncompleteStatement(relIRI, RDFaIncompleteStatement.Direction.NONE)); } else { - throw new ParsingErrorException("Typeof statement uses unknown type " + element.attr(TYPEOF_ATTR)); + this.currentProcessingContext().addIncompleteStatement(new RDFaIncompleteStatement(relIRI, RDFaIncompleteStatement.Direction.FORWARD)); } + } else if (isAttributePresent(RDFaAttributes.REV)) { + if (!getAttributeResourceValue(RDFaAttributes.REV).isIRI()) { + throw new ParsingErrorException("Value of attribute @rev expected to be an IRI but was " + this.currentProcessingContext().getElementAttributes().get(RDFaAttributes.REV.getName())); + } + IRI revIRI = (IRI) getAttributeResourceValue(RDFaAttributes.REV); + this.currentProcessingContext().addIncompleteStatement(new RDFaIncompleteStatement(revIRI, RDFaIncompleteStatement.Direction.BACKWARD)); } } - // 7. If in any of the previous steps a [current object resource] was set to a non-null value, it is now used to generate triples: - if (currentObject != null && (element.attribute(REL_ATTR) != null || element.attribute(REV_ATTR) != null)) { - if(element.attribute(REL_ATTR) != null) { - Optional propertyOpt = getResourceFromElementAttribute(element, REL_ATTR, context); - if(propertyOpt.isPresent() && propertyOpt.get().isIRI()) { - IRI property = (IRI) propertyOpt.get(); - this.getModel().add(newSubject, property, currentObject); - } + // 12. If the skip element flag is 'false', and new subject was set to a non-null value, then any incomplete triples within the current context should be completed: + if (!this.currentProcessingContext().isSkipElement() + && this.currentProcessingContext().getNewSubject() != null) { + if (this.currentProcessingContext().getIncompleteStatements() == null) { + this.currentProcessingContext().setIncompleteStatements(new HashSet<>()); } - if(element.attribute(REV_ATTR) != null) { - Optional propertyOpt = getResourceFromElementAttribute(element, REL_ATTR, context); - if(propertyOpt.isPresent() && propertyOpt.get().isIRI() && currentObject.isResource()) { - IRI property = (IRI) propertyOpt.get(); - this.getModel().add(currentObject, property, newSubject); + for (RDFaIncompleteStatement incompleteStatement : currentProcessingContext().getEvaluationContext().getIncompleteStatements()) { + if (incompleteStatement.getDirection() == RDFaIncompleteStatement.Direction.NONE) { + this.currentProcessingContext().addListMapping(incompleteStatement.getPredicate(), this.currentProcessingContext().getNewSubject()); + } else if (incompleteStatement.getDirection() == RDFaIncompleteStatement.Direction.FORWARD) { + this.getModel().add(currentProcessingContext().getEvaluationContext().getParentSubjectResource(), incompleteStatement.getPredicate(), this.currentProcessingContext().getNewSubject()); + } else if (incompleteStatement.getDirection() == RDFaIncompleteStatement.Direction.BACKWARD) { + this.getModel().add(this.currentProcessingContext().getNewSubject(), incompleteStatement.getPredicate(), currentProcessingContext().getEvaluationContext().getParentSubjectResource()); } } } - // 8. If however [current object resource] was set to null, but there are predicates present, then they must be stored as [incomplete triple]s, pending the discovery of a subject that can be used as the object. Also, [current object resource] should be set to a newly created [bnode]; - if (currentObject == null && (element.attribute(REL_ATTR) != null || element.attribute(REV_ATTR) != null)) { - currentObject = getValueFactory().createBNode(); - if(element.attribute(REL_ATTR) != null) { - Optional propertyOpt = getResourceFromElementAttribute(element, REL_ATTR, context); - if(propertyOpt.isPresent() && propertyOpt.get().isIRI()) { - IRI property = (IRI) propertyOpt.get(); - RDFaIncompleteStatement statement = new RDFaIncompleteStatement(property); - incompleteStatementSet.add(statement); - } - } - if(element.attribute(REV_ATTR) != null) { - Optional propertyOpt = getResourceFromElementAttribute(element, REL_ATTR, context); - if(propertyOpt.isPresent() && propertyOpt.get().isIRI() && currentObject.isResource()) { - IRI property = (IRI) propertyOpt.get(); - RDFaIncompleteStatement statement = new RDFaIncompleteStatement(property); - statement.setBackward(); - incompleteStatementSet.add(statement); + Map> oldListMappings = currentProcessingContext().getEvaluationContext().getListMappings(); + + // 14. Finally, if there is one or more mapping in the local list mapping, list triples are generated as follows: + for (Map.Entry> listMapping : this.currentProcessingContext().getListMappings().entrySet()) { + IRI propertyIRI = listMapping.getKey(); + Set propertyList = listMapping.getValue(); + + if (!oldListMappings.containsKey(propertyIRI)) { + if (propertyList.isEmpty()) { + getModel().add(this.currentProcessingContext().getNewSubject(), propertyIRI, RDF.nil.getIRI()); + } else { + ArrayList bnodes = new ArrayList<>(); + for (int i = 0; i < propertyList.size(); i++) { + bnodes.add(getValueFactory().createBNode()); + } + int bnodeIndex = 0; + for (Value listElement : propertyList) { + BNode elementNode = bnodes.get(bnodeIndex); + Resource nextElementNode = RDF.nil.getIRI(); + if (bnodeIndex < bnodes.size() - 1) { + nextElementNode = bnodes.get(bnodeIndex + 1); + } + getModel().add(elementNode, RDF.first.getIRI(), listElement); + getModel().add(elementNode, RDF.rest.getIRI(), nextElementNode); + + bnodeIndex++; + } + getModel().add(this.currentProcessingContext().getNewSubject(), propertyIRI, bnodes.getFirst()); } } } - // 9. The next step of the iteration is to establish any [current object literal]; - if(element.attribute(PROPERTY_ATTR) != null) { // Predicates for the [current object literal] can be set by using @property. If present, one or more URIs are obtained according to the section on CURIE and URI Processing, and then the actual literal value is obtained as follows: - Optional propertyOpt = getResourceFromElementAttribute(element, PROPERTY_ATTR, context); - if(propertyOpt.isPresent() && propertyOpt.get().isIRI()) { - IRI property = (IRI)propertyOpt.get(); + } - IRI datatype = null; - if(element.attribute(DATATYPE_ATTR) != null && ! element.attr(DATATYPE_ATTR).isEmpty()) { - Optional datatypeOpt = getResourceFromElementAttribute(element, DATATYPE_ATTR, context); - if(datatypeOpt.isPresent() && datatypeOpt.get().isIRI() && ! datatypeOpt.get().equals(RDF.XMLLiteral.getIRI())) { - datatype = (IRI) datatypeOpt.get(); + /* + * Ths function will apply the operations for the creation of literals using the character buffer and remove the current top processing context from the pile. + */ + private void endProcessElement(String uri, String localName, String qName) { + if(! this.currentProcessingContext().getElementName().equals(qName)) { + throw new ParsingErrorException("End process element "+ qName +" is not paired with the right context" + this.currentProcessingContext()); + } + + // 11. The next step of the iteration is to establish any current property value; + if (isAttributePresent(RDFaAttributes.PROPERTY)) { + IRI propertyIRI = (IRI) getAttributeResourceValue(RDFaAttributes.PROPERTY); + // as a typed literal if @datatype is present, does not have an empty value according to the section on CURIE and IRI Processing, and is not set to XMLLiteral in the vocabulary http://www.w3.org/1999/02/22-rdf-syntax-ns#. + // The actual literal is either the value of @content (if present) or a string created by concatenating the value of all descendant text nodes, of the current element in turn. The final string includes the datatype IRI, as described in [RDF-SYNTAX-GRAMMAR], which will have been obtained according to the section on CURIE and IRI Processing. + if (isAttributePresent(RDFaAttributes.DATATYPE) + && getAttributeResourceValue(RDFaAttributes.DATATYPE).isIRI() + && getAttributeResourceValue(RDFaAttributes.DATATYPE) != RDF.XMLLiteral.getIRI()) { + IRI datatypeIRI = (IRI) getAttributeResourceValue(RDFaAttributes.DATATYPE); + if (isAttributePresent(RDFaAttributes.CONTENT)) { + String contentString = getAttributeStringValue(RDFaAttributes.CONTENT); + this.currentProcessingContext().setCurrentPropertyValue(getValueFactory().createLiteral(contentString, datatypeIRI)); + } else { + String contentString = this.currentProcessingContext().getCharacters().trim(); + if(! contentString.isEmpty()) { + this.currentProcessingContext().setCurrentPropertyValue(getValueFactory().createLiteral(contentString, datatypeIRI)); + this.clearAllCharactersBuffers(); } } - String value = element.text(); - if(element.attribute(CONTENT_ATTR) != null) { - value = element.attr(CONTENT_ATTR); + // otherwise, as a plain literal if @datatype is present but has an empty value according to the section on CURIE and IRI Processing. + // The actual literal is either the value of @content (if present) or a string created by concatenating the value of all descendant text nodes, of the current element in turn. + } else if (isAttributePresent(RDFaAttributes.DATATYPE) + && getAttributeStringValue(RDFaAttributes.DATATYPE).isEmpty()) { + IRI datatypeIRI = (IRI) getAttributeResourceValue(RDFaAttributes.DATATYPE); + if (isAttributePresent(RDFaAttributes.CONTENT)) { + String contentString = this.currentProcessingContext().getElementAttributes().get(RDFaAttributes.CONTENT.getName()); + this.currentProcessingContext().setCurrentPropertyValue(getValueFactory().createLiteral(contentString, datatypeIRI)); + } else { + String contentString = this.currentProcessingContext().getCharacters().trim(); + if(! contentString.isEmpty()) { + this.currentProcessingContext().setCurrentPropertyValue(getValueFactory().createLiteral(contentString, datatypeIRI)); + this.clearAllCharactersBuffers(); + } + } + // otherwise, as an XML literal if @datatype is present and is set to XMLLiteral in the vocabulary http://www.w3.org/1999/02/22-rdf-syntax-ns#. + // The value of the XML literal is a string created by serializing to text, all nodes that are descendants of the current element, i.e., not including the element itself, and giving it a datatype of XMLLiteral in the vocabulary http://www.w3.org/1999/02/22-rdf-syntax-ns#. The format of the resulting serialized content is as defined in Exclusive XML Canonicalization Version 1.0 [XML-EXC-C14N]. + + // otherwise, as a plain literal using the value of @content if @content is present. + } else if (isAttributePresent(RDFaAttributes.CONTENT)) { + String contentString = this.currentProcessingContext().getElementAttributes().get(RDFaAttributes.CONTENT.getName()); + this.currentProcessingContext().setCurrentPropertyValue(getValueFactory().createLiteral(contentString)); + // otherwise, if the @rel, @rev, and @content attributes are not present, as a resource obtained from one of the following: + // by using the resource from @resource, if present, obtained according to the section on CURIE and IRI Processing; + // otherwise, by using the IRI from @href, if present, obtained according to the section on CURIE and IRI Processing; + // otherwise, by using the IRI from @src, if present, obtained according to the section on CURIE and IRI Processing. + } else if (!isAttributePresent(RDFaAttributes.REL) + && !isAttributePresent(RDFaAttributes.REV) + && !isAttributePresent(RDFaAttributes.CONTENT) + && (isAttributePresent(RDFaAttributes.RESOURCE) + || isAttributePresent(RDFaAttributes.HREF) + || isAttributePresent(RDFaAttributes.SRC) + )) { + if (isAttributePresent(RDFaAttributes.RESOURCE)) { + this.currentProcessingContext().setCurrentPropertyValue(getAttributeResourceValue(RDFaAttributes.RESOURCE)); + } else if (isAttributePresent(RDFaAttributes.HREF)) { + this.currentProcessingContext().setCurrentPropertyValue(getAttributeResourceValue(RDFaAttributes.HREF)); + } else if (isAttributePresent(RDFaAttributes.SRC)) { + this.currentProcessingContext().setCurrentPropertyValue(getAttributeResourceValue(RDFaAttributes.SRC)); } - if(datatype != null) { - currentObjectLiteral = this.getValueFactory().createLiteral(value, datatype); - recursive = false; - } else if(language != null) { - currentObjectLiteral = this.getValueFactory().createLiteral(value, language); + // otherwise, if @typeof is present and @about is not, the value of typed resource. + } else if (isAttributePresent(RDFaAttributes.TYPEOF) + && !isAttributePresent(RDFaAttributes.ABOUT)) { + this.currentProcessingContext().setCurrentPropertyValue(this.currentProcessingContext().getTypedResource()); + // otherwise as a plain literal. + } else { + String contentString = this.currentProcessingContext().getCharacters().trim(); + if(! contentString.isEmpty()) { + // Additionally, if there is a value for current language then the value of the plain literal should include this language information, as described in [RDF-SYNTAX-GRAMMAR]. The actual literal is either the value of @content (if present) or a string created by concatenating the text content of each of the descendant elements of the current element in document order. + if (this.currentProcessingContext().getCurrentLanguage() != null + && !this.currentProcessingContext().getCurrentLanguage().isEmpty()) { + this.currentProcessingContext().setCurrentPropertyValue(getValueFactory().createLiteral(contentString, this.currentProcessingContext().getCurrentLanguage())); + } else { + this.currentProcessingContext().setCurrentPropertyValue(getValueFactory().createLiteral(contentString)); + } + this.clearAllCharactersBuffers(); + } + } + + // The current property value is then used with each predicate as follows: + // If the element also includes the @inlist attribute, the current property value is added to the local list mapping as follows: + if(this.currentProcessingContext().getCurrentPropertyValue() != null) { + if (isAttributePresent(RDFaAttributes.INLIST)) { + // if the local list mapping does not contain a list associated with the predicate IRI, instantiate a new list and add to local list mappings + if (!this.currentProcessingContext().getListMappings().containsKey(propertyIRI)) { + this.currentProcessingContext().addListMappings(propertyIRI, new HashSet<>()); + } + // add the current property value to the list associated with the predicate IRI in the local list mapping + this.currentProcessingContext().addListMapping(propertyIRI, this.currentProcessingContext().getCurrentPropertyValue()); + // Otherwise the current property value is used to generate a triple as follows: + // subject new subject + // predicate full IRI + // object current property value } else { - currentObjectLiteral = this.getValueFactory().createLiteral(value); + Statement statement = getValueFactory().createStatement(this.currentProcessingContext().getNewSubject(), propertyIRI, this.currentProcessingContext().getCurrentPropertyValue()); + this.getModel().add(statement); } - this.getModel().add(newSubject, property, currentObjectLiteral); } } - // 10. If the [skip element] flag is 'false', and [new subject] was set to a non-null value, then any [incomplete triple]s within the current context should be completed: - Iterator itStat = context.getIncompleteStatementIterator(); - while(itStat.hasNext()) { - RDFaIncompleteStatement statement = itStat.next(); - if(statement.isForward()) { - this.getModel().add(context.parentSubjectResource(), statement.getPredicate(), newSubject); - } else if (statement.isBackward()){ - this.getModel().add(newSubject, statement.getPredicate(), context.parentSubjectResource()); - } + this.processingContexts.pop(); + } + + /** + * Internal SAX handler that delegates to the parser's methods + */ + private class XMLSaxHandler extends DefaultHandler { + @Override + public void characters(char[] ch, int start, int length) { + RDFaParser.this.handleCharacters(ch, start, length); } - // 11. If the [recurse] flag is 'true', all elements that are children of the [current element] are processed using the rules described here, using a new [evaluation context], - if(recursive) { - if(skipElement) { - RDFaEvaluationContext newContext = new RDFaEvaluationContext(context); - newContext.setLanguage(language); - newContext.uriMappings(currentMappings); - context = newContext; - } else { - context = new RDFaEvaluationContext(context.baseIri()); - if(newSubject != null) { - context.parentObjectResource(newSubject); - } - if(currentObject != null) { - context.parentObjectResource(currentObject); - } - context.uriMappings(currentMappings); - context.incompleteStatements(incompleteStatementSet); - context.setLanguage(language); - } + @Override + public void startPrefixMapping(String prefix, String uri) { + RDFaParser.this.addPrefix(prefix, uri); + } - for (Element child : element.children()) { - processElement(child, context, recursive, skipElement); + @Override + public void startElement(String uri, String localName, String qName, Attributes attrs) { + startProcessElement(uri, localName, qName, attrs); + } + + @Override + public void endElement(String uri, String localName, String qName) { + endProcessElement(uri, localName, qName); + } + + @Override + public void error(SAXParseException e) { + throw new ParsingErrorException("Failed to parse XML+RDFa: " + e.getMessage(), e); + } + + @Override + public void fatalError(SAXParseException e) { + throw new ParsingErrorException("Failed to parse XML+RDFa: " + e.getMessage(), e); + } + + @Override + public void warning(SAXParseException e) { + logger.warn("Warning during parsing of XML+RDFa: ", e); + } + } + + private Map getPrefixesFromDeclaration(String declaration) { + String[] prefixArray = declaration.split(" "); + HashMap result = new HashMap<>(); + // prefix array should contain an even number of elements corresponding to prefix/namespace pairs + if (prefixArray.length % 2 != 0) { + throw new ParsingErrorException("Error during prefix extraction of " + declaration); + } + int numberOfPairs = prefixArray.length / 2; + for(int pairNumber = 0; pairNumber < numberOfPairs; pairNumber++) { + String prefix = prefixArray[pairNumber*2]; + if(! prefix.endsWith(":")) { + throw new ParsingErrorException("Expecting namespace prefix declaration to end with \":\", got " + prefix + " in declaration " + declaration); } + prefix = prefix.replaceAll(":$", ""); // Removing trailing : + IRI namespace = getValueFactory().createIRI(prefixArray[pairNumber*2 +1]); + + result.put(prefix, namespace); + } + return result; + } + + private Resource getAttributeResourceValue(RDFaAttributes attribute) { + String attributeValue = this.currentProcessingContext().getElementAttributes().get(attribute.getName()); + Optional resourceResolution = resolveStringResource(attributeValue); + if (resourceResolution.isPresent()) { + return resourceResolution.get(); + } else { + throw new ParsingErrorException("Could not parse @" + attribute.getName() + " value: " + attributeValue); } } + private boolean isAttributePresent(RDFaAttributes attribute) { + return this.currentProcessingContext().getElementAttributes().get(attribute.getName()) != null; + } + + private String getAttributeStringValue(RDFaAttributes attribute) { + return this.currentProcessingContext().getElementAttributes().get(attribute.getName()); + } + /** - * Surcharge function that initialize the flags and subject and objet to their initial values for processing + * Convenience accessor to the top of the processing contexts pile * - * @param element HTML element - * @param context current evaluation context */ - private void processElement(Element element, RDFaEvaluationContext context) { - processElement(element, context, true, false); - } - - @Override - public void parse(Reader reader, String baseURI) { - InputStream inputStream = new ReaderInputStream(reader, StandardCharsets.UTF_8); - parse(inputStream , baseURI); + private RDFaProcessingContext currentProcessingContext() { + return this.processingContexts.getFirst(); } /** - * Resolves the string representation of a resource found in attributes of an element, be it an IRI, CURIE or relative URI + * Resolves the string representation of a resource found in attributes of an element, be it an IRI, CURIE or relative URI * * @param stringResource the resource as stored in the attribute of the HTML element - * @param context the context of the element evalation * @return the full IRI if it is a relative IRI, full IRI or CURIE, nothing otherwise */ - private Optional resolveStringResource(String stringResource, RDFaEvaluationContext context) { + protected Optional resolveStringResource(String stringResource) { String resultString = stringResource; if (resultString.startsWith("[") && resultString.endsWith("]")) { resultString = resultString.replaceFirst("\\[", ""); @@ -369,14 +704,18 @@ private Optional resolveStringResource(String stringResource, RDFaEval String prefixString = resultString.substring(0, colonIndex); String localNameString = resultString.substring(colonIndex + 1); // Basic resolution following https://www.w3.org/TR/rdfa-syntax/#s_convertingcurietouri - if (context.hasUriMapping(prefixString)) { - IRI namespaceIRI = context.uriMapping(prefixString); + if (this.hasIriMapping(prefixString)) { + IRI namespaceIRI = this.getIriMapping(prefixString); + + return Optional.of(this.getValueFactory().createIRI(namespaceIRI.stringValue(), localNameString)); + } else if (this.getIriMappings().containsKey(prefixString)) { + IRI namespaceIRI = this.getIriMappings().get(prefixString); return Optional.of(this.getValueFactory().createIRI(namespaceIRI.stringValue(), localNameString)); } else if (prefixString.isEmpty()) { // CURIE is relative to the base URI - return Optional.of(this.getValueFactory().createIRI(context.baseIri().stringValue(), localNameString)); + return Optional.of(this.getValueFactory().createIRI(currentProcessingContext().getEvaluationContext().getBaseIri().stringValue(), localNameString)); } else { - throw new ParsingErrorException("CURIE " + stringResource + " uses unknown prefix"); + throw new ParsingErrorException("CURIE " + stringResource + " uses unknown prefix among " + this.getIriMappings().keySet() + " and " + this.getIriMappings().keySet()); } } else if (IRIUtils.isStandardIRI(resultString)) { // Full IRI return Optional.of(this.getValueFactory().createIRI(resultString)); @@ -385,29 +724,70 @@ private Optional resolveStringResource(String stringResource, RDFaEval int colonIndex = resultString.indexOf(":"); String localNameString = resultString.substring(colonIndex + 1); return Optional.of(this.getValueFactory().createBNode(localNameString)); - } else if (IRIUtils.isStandardIRI(context.baseIri().stringValue() + resultString)) { - String concatenatedRelativeUri = context.baseIri().stringValue() + resultString; - return Optional.of(getValueFactory().createIRI(concatenatedRelativeUri)); + } else if (IRIUtils.isStandardIRI(currentProcessingContext().getEvaluationContext().getBaseIri().stringValue() + resultString)) { + String concatenatedRelativeUri = currentProcessingContext().getEvaluationContext().getBaseIri().stringValue() + resultString; + return Optional.of(this.getValueFactory().createIRI(concatenatedRelativeUri)); + } else if(this.currentProcessingContext().getEvaluationContext().getTermMapping(resultString) != null) { + return Optional.of(this.currentProcessingContext().getEvaluationContext().getTermMapping(resultString)); } return Optional.empty(); } /** - * Equivalent to test if it has a colon, and it is not a blank node + * Equivalent to test if it contains a colon, and it is not a blank node * - * @param stringIri - * @return + * @param stringIri Attribute or text value + * @return true if it is a valid CURIE */ - private boolean stringUriIsCURIE(String stringIri) { + protected boolean stringUriIsCURIE(String stringIri) { int colonIndex = stringIri.indexOf(":"); return colonIndex > -1 && !stringIri.contains("://") && !stringIri.startsWith("_:") && !stringIri.startsWith("[_:"); } - private Optional getResourceFromElementAttribute(Element element, String attributeName, RDFaEvaluationContext context) { - if (element.attribute(attributeName) != null) { // otherwise, by using the URI from @resource, if present, obtained according to the section on CURIE and URI Processing; - String newSubjectString = element.attr(attributeName); - return resolveStringResource(newSubjectString, context); + private RDFaEvaluationContext getNewContext(IRI baseIRI) { + RDFaEvaluationContext result = new RDFaEvaluationContext(baseIRI); + initializeEvaluationContextMappings(result); + return result; + } + + private void initializeEvaluationContextMappings(RDFaEvaluationContext context) { + // https://www.w3.org/2011/rdfa-context/rdfa-1.1 sets a list of predefined terms mappings for RDFa contexts. + context.addTermMapping("describedby", getValueFactory().createIRI("http://www.w3.org/2007/05/powder-s#describedby")); + context.addTermMapping("license", getValueFactory().createIRI("http://www.w3.org/1999/xhtml/vocab#license")); + context.addTermMapping("role", getValueFactory().createIRI("http://www.w3.org/1999/xhtml/vocab#role")); + } + + private Map getIriMappings() { + return iriMappings; + } + + private void setIriMappings(Map iriMappings) { + this.iriMappings = iriMappings; + } + + private boolean hasIriMapping(String prefix) { + return this.iriMappings.containsKey(prefix); + } + + /** + * @param prefix the prefix WITHOUT ":" + * @return the IRI associated to the prefix in this context + */ + private IRI getIriMapping(String prefix) { + return this.iriMappings.get(prefix); + } + + private void addIriMapping(String prefix, IRI prefixIri) { + this.iriMappings.put(prefix, prefixIri); + } + + private void addIriMappings(Map otherMappings) { + if(otherMappings != null) { + this.iriMappings.putAll(otherMappings); } - return Optional.empty(); + } + + private void clearIriMappings() { + this.iriMappings.clear(); } } diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/model/RDFaAttributes.java b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/model/RDFaAttributes.java new file mode 100644 index 000000000..265712e3d --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/model/RDFaAttributes.java @@ -0,0 +1,30 @@ +package fr.inria.corese.core.next.impl.io.parser.rdfa.model; + +public enum RDFaAttributes { + ABOUT("about"), + BASE("base"), + CONTENT("content"), + DATATYPE("datatype"), + HREF("href"), + INLIST("inlist"), + PREFIX("prefix"), + PROPERTY("property"), + REL("rel"), + RESOURCE("resource"), + REV("rev"), + SRC("src"), + TYPEOF("typeof"), + VOCAB("vocab"), + LANG("lang"), + LANG_ALT("xml:lang"); + + private final String name; + + RDFaAttributes(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/model/RDFaEvaluationContext.java b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/model/RDFaEvaluationContext.java new file mode 100644 index 000000000..8071de201 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/model/RDFaEvaluationContext.java @@ -0,0 +1,176 @@ +package fr.inria.corese.core.next.impl.io.parser.rdfa.model; + +import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.api.Resource; +import fr.inria.corese.core.next.api.Value; + +import java.util.*; + +/** + * This class is to be used during the evaluation of an HTML file to generate triples during the DOM traversal. + * @see RDFa recommandation + */ +public class RDFaEvaluationContext { + /** + * The base. This will usually be the IRI of the document being processed, but it could be some other IRI, set by some other mechanism, such as the (X)HTML base element. The important thing is that it establishes an IRI against which relative paths can be resolved. + */ + private IRI baseIri; + + /** + * The initial value will be the same as the initial value of [base], but it will usually change during the course of processing. + */ + private Resource parentSubjectResource ; + + /** + * In some situations the object of a statement becomes the subject of any nested statements, and this property is used to convey this value. Note that this value may be a bnode, since in some situations a number of nested statements are grouped together on one bnode. This means that the bnode must be set in the containing statement and passed down, and this property is used to convey this value. + */ + private Resource parentObjectResource = null; + + /** + * Set of statements in the process of building. + */ + private Set incompleteStatements = new HashSet<>(); + + /** + * The language of the document. Note that there is no default language. + */ + private String language = null; + /** + * A list mapping that associates IRIs with lists. + */ + private Map> listMappings = new HashMap<>(); + /** + * The term mappings, a list of terms and their associated IRIs. This specification does not define an initial list. Host Languages MAY define an initial list. + */ + private Map termMappings = new HashMap<>(); + + /** + * The default vocabulary, a value to use as the prefix IRI when a term unknown to the RDFa Processor is used. This specification does not define an initial setting for the default vocabulary. Host Languages MAY define an initial setting. + */ + private String defaultVocabulary = null; + + public RDFaEvaluationContext(IRI baseIri) { + this.baseIri = baseIri; + } + + public RDFaEvaluationContext(RDFaEvaluationContext context) { + this.baseIri = context.baseIri; + this.defaultVocabulary = context.defaultVocabulary; + this.incompleteStatements = new HashSet<>(context.incompleteStatements); + this.language = context.language; + this.listMappings = new HashMap<>(context.listMappings); + this.parentObjectResource = context.parentObjectResource; + this.parentSubjectResource = context.parentSubjectResource; + this.termMappings = new HashMap<>(context.termMappings); + } + + public IRI getBaseIri() { + return baseIri; + } + + public void setBaseIri(IRI baseIri) { + this.baseIri = baseIri; + } + + public Resource getParentSubjectResource() { + return parentSubjectResource; + } + + public void setParentSubjectResource(Resource parentSubjectResource) { + this.parentSubjectResource = parentSubjectResource; + } + + public Resource getParentObjectResource() { + return parentObjectResource; + } + + public void setParentObjectResource(Resource parentObjectResource) { + this.parentObjectResource = parentObjectResource; + } + + public Set getIncompleteStatements() { + return incompleteStatements; + } + + public void setIncompleteStatements(Set incompleteStatement) { + this.incompleteStatements = incompleteStatement; + } + + public Iterator getIncompleteStatementIterator() { + return this.incompleteStatements.iterator(); + } + + public void addStatementWithoutSubject(IRI property, Value object) { + RDFaIncompleteStatement newStatement = new RDFaIncompleteStatement(property); + newStatement.setObject(object); + this.incompleteStatements.add(newStatement); + } + + public void addStatementWithoutObject(Resource subject, IRI property) { + RDFaIncompleteStatement newStatement = new RDFaIncompleteStatement(property); + newStatement.setSubject(subject); + this.incompleteStatements.add(newStatement); + } + + public void clearIncompleteStatements() { + this.incompleteStatements.clear(); + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getDefaultVocabulary() { + return defaultVocabulary; + } + + public void setDefaultVocabulary(String defaultVocabulary) { + this.defaultVocabulary = defaultVocabulary; + } + + public void addTermMapping(String term, IRI iri) { + this.termMappings.put(term, iri); + } + + public IRI getTermMapping(String term) { + return this.termMappings.get(term); + } + + public Map getTermMappings() { + return this.termMappings; + } + + public Map> getListMappings() { + return listMappings; + } + + public void setListMappings(Map> listMappings) { + this.listMappings = listMappings; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append("BaseURI: ").append(this.getBaseIri().stringValue()).append(" "); + if(this.getParentSubjectResource() != null) { + sb.append("Subject:").append(this.getParentSubjectResource().stringValue()).append(" "); + } else { + sb.append("Subject:").append((Object) null).append(" "); + } + if(this.getParentObjectResource() != null) { + sb.append("Object: ").append(this.getParentObjectResource().stringValue()).append(" "); + } else { + sb.append("Object: ").append((Object) null).append(" "); + } + if(! this.getIncompleteStatements().isEmpty()) { + sb.append(this.getIncompleteStatements().size()).append(" incomplete statements."); + } + + return sb.toString(); + } +} diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/model/RDFaIncompleteStatement.java b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/model/RDFaIncompleteStatement.java index d30a7fe54..d023a6e7d 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/model/RDFaIncompleteStatement.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/model/RDFaIncompleteStatement.java @@ -13,7 +13,8 @@ public class RDFaIncompleteStatement { public enum Direction { FORWARD, - BACKWARD + BACKWARD, + NONE } private Resource subject = null; @@ -124,10 +125,16 @@ public String toString() { @Override public boolean equals(Object o) { + if (this == o) { + return true; + } if(! (o instanceof RDFaIncompleteStatement oStat)) { return false; } - return oStat.getSubject() == this.getSubject() && oStat.getPredicate() == this.getPredicate() && oStat.getObject() == this.getObject() && oStat.getDirection() == this.getDirection(); + return oStat.getSubject().equals(this.getSubject()) + && oStat.getPredicate().equals(this.getPredicate()) + && oStat.getObject().equals(this.getObject()) + && oStat.getDirection().equals(this.getDirection()); } @Override diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/model/RDFaInitialPrefixes.java b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/model/RDFaInitialPrefixes.java new file mode 100644 index 000000000..64b81e987 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/model/RDFaInitialPrefixes.java @@ -0,0 +1,121 @@ +package fr.inria.corese.core.next.impl.io.parser.rdfa.model; + +import fr.inria.corese.core.next.api.Namespace; + +/** + * https://www.w3.org/2011/rdfa-context/rdfa-1.1 sets a list of predefined prefixes for RDFa contexts. + */ +public enum RDFaInitialPrefixes implements Namespace { + + // as "https://www.w3.org/ns/activitystreams#" + AS("as", "https://www.w3.org/ns/activitystreams#"), + // cc "http://creativecommons.org/ns#" + CC("cc", "http://creativecommons.org/ns#"), + // csvw "http://www.w3.org/ns/csvw#" + CSVW("csvw", "http://www.w3.org/ns/csvw#"), + // ctag "http://commontag.org/ns#" + CTAG("ctag", "http://commontag.org/ns#"), + // dc "http://purl.org/dc/terms/" + DC("dc", "http://purl.org/dc/terms/"), + // dc11 "http://purl.org/dc/elements/1.1/" + DC11("dc11", "http://purl.org/dc/elements/1.1/"), + // dcat "http://www.w3.org/ns/dcat#" + DCAT("dcat", "http://www.w3.org/ns/dcat#"), + // dcterms "http://purl.org/dc/terms/" + DCTERMS("dcterms", "http://purl.org/dc/terms/"), + // dqv "http://www.w3.org/ns/dqv#" + DQV("dqv", "http://www.w3.org/ns/dqv#"), + // duv "https://www.w3.org/ns/duv#" + DUV("duv", "https://www.w3.org/ns/duv#"), + // foaf "http://xmlns.com/foaf/0.1/" + FOAF(fr.inria.corese.core.next.impl.common.vocabulary.FOAF.getVocabularyPreferredPrefix(), fr.inria.corese.core.next.impl.common.vocabulary.FOAF.getVocabularyNamespace()), + // gr "http://purl.org/goodrelations/v1#" + GR("gr", "http://purl.org/goodrelations/v1#"), + // grddl "http://www.w3.org/2003/g/data-view#" + GRDDL("grddl", "http://www.w3.org/2003/g/data-view#"), + // ical "http://www.w3.org/2002/12/cal/icaltzd#" + ICAL("ical", "http://www.w3.org/2002/12/cal/icaltzd#"), + // jsonld "http://www.w3.org/ns/json-ld#" + JSONLD("jsonld", "http://www.w3.org/ns/json-ld#"), + // ldp "http://www.w3.org/ns/ldp#" + LDP("ldp", "http://www.w3.org/ns/ldp#"), + // ma "http://www.w3.org/ns/ma-ont#" + MA("ma", "http://www.w3.org/ns/ma-ont#"), + // oa "http://www.w3.org/ns/oa#" + OA("oa", "http://www.w3.org/ns/oa#"), + // odrl "http://www.w3.org/ns/odrl/2/" + ODRL("odrl", "http://www.w3.org/ns/odrl/2/"), + // og "http://ogp.me/ns#" + OG("og", "http://ogp.me/ns#"), + // org "http://www.w3.org/ns/org#" + ORG("org", "http://www.w3.org/ns/org#"), + // owl "http://www.w3.org/2002/07/owl#" + OWL(fr.inria.corese.core.next.impl.common.vocabulary.OWL.getVocabularyPreferredPrefix(), fr.inria.corese.core.next.impl.common.vocabulary.OWL.getVocabularyNamespace()), + // prov "http://www.w3.org/ns/prov#" + PROV("prov", "http://www.w3.org/ns/prov#"), + // qb "http://purl.org/linked-data/cube#" + QB("qb", "http://purl.org/linked-data/cube#"), + // rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#" + RDF(fr.inria.corese.core.next.impl.common.vocabulary.RDF.getVocabularyPreferredPrefix(), fr.inria.corese.core.next.impl.common.vocabulary.RDF.getVocabularyNamespace()), + // rdfa "http://www.w3.org/ns/rdfa#" + RDFA(fr.inria.corese.core.next.impl.common.vocabulary.RDFa.getVocabularyPreferredPrefix(), fr.inria.corese.core.next.impl.common.vocabulary.RDFa.getVocabularyNamespace()), + // rdfs "http://www.w3.org/2000/01/rdf-schema#" + RDFS(fr.inria.corese.core.next.impl.common.vocabulary.RDFS.getVocabularyPreferredPrefix(), fr.inria.corese.core.next.impl.common.vocabulary.RDFS.getVocabularyNamespace()), + // rev "http://purl.org/stuff/rev#" + REV("rev", "http://purl.org/stuff/rev#"), + // rif "http://www.w3.org/2007/rif#" + RIF("rif", "http://www.w3.org/2007/rif#"), + // rr "http://www.w3.org/ns/r2rml#" + RR("rr", "http://www.w3.org/ns/r2rml#"), + // schema "http://schema.org/" + SCHEMA("schema", "http://schema.org/"), + // sd "http://www.w3.org/ns/sparql-service-description#" + SD("sd", "http://www.w3.org/ns/sparql-service-description#"), + // sioc "http://rdfs.org/sioc/ns#" + SIOC("sioc", "http://rdfs.org/sioc/ns#"), + // skos "http://www.w3.org/2004/02/skos/core#" + SKOS("skos", "http://www.w3.org/2004/02/skos/core#"), + // skosxl "http://www.w3.org/2008/05/skos-xl#" + SKOSXL("skosxl", "http://www.w3.org/2008/05/skos-xl#"), + // sosa "http://www.w3.org/ns/sosa/" + SOSA("sosa", "http://www.w3.org/ns/sosa/"), + // ssn "http://www.w3.org/ns/ssn/" + SSN("ssn", "http://www.w3.org/ns/ssn/"), + // time "http://www.w3.org/2006/time#" + TIME("time", "http://www.w3.org/2006/time#"), + // v "http://rdf.data-vocabulary.org/#" + V("v", "http://rdf.data-vocabulary.org/#"), + // vcard "http://www.w3.org/2006/vcard/ns#" + VCARD("vcard", "http://www.w3.org/2006/vcard/ns#"), + // void "http://rdfs.org/ns/void#" + VOID("void", "http://rdfs.org/ns/void#"), + // wdr "http://www.w3.org/2007/05/powder#" + WDR("wdr", "http://www.w3.org/2007/05/powder#"), + // wdrs "http://www.w3.org/2007/05/powder-s#" + WDRS("wdrs", "http://www.w3.org/2007/05/powder-s#"), + // xhv "http://www.w3.org/1999/xhtml/vocab#" + XHV("xhv", "http://www.w3.org/1999/xhtml/vocab#"), + // xml "http://www.w3.org/XML/1998/namespace" + XML("xml", "http://www.w3.org/XML/1998/namespace"), + // xsd "http://www.w3.org/2001/XMLSchema#" + XSD(fr.inria.corese.core.next.impl.common.vocabulary.XSD.getVocabularyPreferredPrefix(), fr.inria.corese.core.next.impl.common.vocabulary.XSD.getVocabularyNamespace()), + ; + + private final String prefix; + private final String namespace; + + RDFaInitialPrefixes(String prefix, String namespaceString) { + this.namespace = namespaceString; + this.prefix = prefix; + } + + @Override + public String getPrefix() { + return this.prefix; + } + + @Override + public String getNamespace() { + return this.namespace; + } +} diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/model/RDFaProcessingContext.java b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/model/RDFaProcessingContext.java new file mode 100644 index 000000000..9fe7801bb --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/impl/io/parser/rdfa/model/RDFaProcessingContext.java @@ -0,0 +1,331 @@ +package fr.inria.corese.core.next.impl.io.parser.rdfa.model; + +import fr.inria.corese.core.next.api.IRI; +import fr.inria.corese.core.next.api.Resource; +import fr.inria.corese.core.next.api.Value; +import org.xml.sax.Attributes; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Corresponds to the local values for the processing of an element and the current context at the moment of its evaluation + */ +public class RDFaProcessingContext { + + // Local context + private String elementName = null; + private boolean skipElement = false; + private Resource newSubject = null; + private Resource currentObjectResource = null; + private Resource typedResource = null; + private Set incompleteStatements = null; + private Map> listMappings = new HashMap<>(); + private String currentLanguage = null; + private Value currentPropertyValue = null; + private String defaultVocabulary = null; + private Map elementAttributes = new HashMap<>(); + + /** + * Buffer for accumulating character data between start and end tags. + */ + private StringBuilder characters = new StringBuilder(); + + private boolean isRootElement = true; + + private RDFaEvaluationContext evaluationContext = null; + + /** + * Constructor to be used in step 1 of RDFa processing + * @param context + */ + public RDFaProcessingContext(RDFaEvaluationContext context) { + this.skipElement = false; + this.newSubject = null; + this.currentObjectResource = null; + this.typedResource = null; + this.incompleteStatements = context.getIncompleteStatements(); + this.listMappings = context.getListMappings(); + this.currentLanguage = context.getLanguage(); + this.defaultVocabulary = context.getDefaultVocabulary(); + this.evaluationContext = context; + } + + /** + * + * @return The skip element flag, which indicates whether the current element can safely be ignored since it has no relevant RDFa attributes. Note that descendant elements will still be processed. + */ + public boolean isSkipElement() { + return skipElement; + } + + /** + * + * @param skipElement The skip element flag, which indicates whether the current element can safely be ignored since it has no relevant RDFa attributes. Note that descendant elements will still be processed. + */ + public void setSkipElement(boolean skipElement) { + this.skipElement = skipElement; + } + + /** + * + * @return A new subject value, which once calculated will set the parent subject in an evaluation context, as well as being used to complete any incomplete triples, as described in the next section. + */ + public Resource getNewSubject() { + return newSubject; + } + + /** + * + * @param newSubject A new subject value, which once calculated will set the parent subject in an evaluation context, as well as being used to complete any incomplete triples, as described in the next section. + */ + public void setNewSubject(Resource newSubject) { + this.newSubject = newSubject; + } + + /** + * + * @return A value for the current object resource, the resource to use when creating triples that have a resource object. + */ + public Resource getCurrentObjectResource() { + return currentObjectResource; + } + + /** + * + * @param currentObjectResource A value for the current object resource, the resource to use when creating triples that have a resource object. + */ + public void setCurrentObjectResource(Resource currentObjectResource) { + this.currentObjectResource = currentObjectResource; + } + + /** + * + * @return A value for the typed resource, the source for creating rdf:type relationships to types specified in @typeof. + */ + public Resource getTypedResource() { + return typedResource; + } + + /** + * + * @param typedResource A value for the typed resource, the source for creating rdf:type relationships to types specified in @typeof. + */ + public void setTypedResource(Resource typedResource) { + this.typedResource = typedResource; + } + + /** + * + * @return A list of incomplete triples. A triple can be incomplete when no object resource is provided alongside a predicate that requires a resource (i.e., @rel or @rev). The triples can be completed when a resource becomes available, which will be when the next subject is specified (part of the process called chaining). + */ + public Set getIncompleteStatements() { + return incompleteStatements; + } + + /** + * + * @param incompleteStatements A list of incomplete triples. A triple can be incomplete when no object resource is provided alongside a predicate that requires a resource (i.e., @rel or @rev). The triples can be completed when a resource becomes available, which will be when the next subject is specified (part of the process called chaining). + */ + public void setIncompleteStatements(Set incompleteStatements) { + this.incompleteStatements = incompleteStatements; + } + + /** + * + * @param statement An incomplete triples. A triple can be incomplete when no object resource is provided alongside a predicate that requires a resource (i.e., @rel or @rev). The triples can be completed when a resource becomes available, which will be when the next subject is specified (part of the process called chaining). + */ + public void addIncompleteStatement(RDFaIncompleteStatement statement) { + this.incompleteStatements.add(statement); + } + + /** + * + * @return A list mapping that associates IRIs with lists. + */ + public Map> getListMappings() { + return listMappings; + } + + /** + * + * @param listMappings A list mapping that associates IRIs with lists. + */ + public void setListMappings(Map> listMappings) { + this.listMappings = listMappings; + } + + /** + * + * @param key The IRI of the list + * @param value The resource associated to this list + */ + public void addListMapping(IRI key, Value value) { + this.listMappings.computeIfAbsent(key,k -> new HashSet<>()); + this.listMappings.get(key).add(value); + } + + /** + * + * @param key The IRI of the list + * @param objects The resources associated to this list + */ + public void addListMappings(IRI key, Set objects) { + this.listMappings.put(key, objects); + } + + /** + * + * @return The language. Note that there is no default language. + */ + public String getCurrentLanguage() { + return currentLanguage; + } + + /** + * + * @param currentLanguage The language. Note that there is no default language. + */ + public void setCurrentLanguage(String currentLanguage) { + this.currentLanguage = currentLanguage; + } + + /** + * + * @return A value for the current property value, the literal to use when creating triples that have a literal object, or IRI-s in the absence of @rel or @rev. + */ + public Value getCurrentPropertyValue() { + return currentPropertyValue; + } + + /** + * + * @param currentPropertyValue A value for the current property value, the literal to use when creating triples that have a literal object, or IRI-s in the absence of @rel or @rev. + */ + public void setCurrentPropertyValue(Value currentPropertyValue) { + this.currentPropertyValue = currentPropertyValue; + } + + /** + * + * @return The default vocabulary, a value to use as the prefix IRI when a term unknown to the RDFa Processor is used. + */ + public String getDefaultVocabulary() { + return defaultVocabulary; + } + + /** + * + * @param defaultVocabulary The default vocabulary, a value to use as the prefix IRI when a term unknown to the RDFa Processor is used. + */ + public void setDefaultVocabulary(String defaultVocabulary) { + this.defaultVocabulary = defaultVocabulary; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(this.elementName).append(" "); + sb.append("newSubject: ").append(this.newSubject).append(" "); + sb.append("currentObjectResource: ").append(this.currentObjectResource).append(" "); + sb.append("typedResource: ").append(this.typedResource).append(" "); + sb.append("currentLanguage: ").append(this.currentLanguage).append(" "); + sb.append("currentPropertyValue: ").append(this.currentPropertyValue).append(" "); + sb.append("defaultVocabulary: ").append(this.defaultVocabulary).append(" "); + sb.append("characters: ").append(this.getCharacters().trim()).append(" "); + sb.append("Evaluation context: ").append(this.getEvaluationContext()).append(" "); + sb.append("Attributes: ").append(this.elementAttributes.keySet()).append(" "); + + return sb.toString(); + } + + /** + * + * @return The string created by concatenating the text content of each of the descendant elements of the current element in document order. + */ + public String getCharacters() { + return characters.toString(); + } + + /** + * Clear the current character buffer + */ + public void clearCharacters() { + this.characters = new StringBuilder(); + } + + /** + * Adds characters to the character buffer + */ + public void addCharacters(char[] ch, int start, int length) { + this.characters.append(ch, start, length); + } + + /** + * + * @return The map of the XML attribute of the current element + */ + public Map getElementAttributes() { + return elementAttributes; + } + + /** + * + * @param elementAttributes The map of the XML attribute of the current element + */ + public void setElementAttributes(Attributes elementAttributes) { + for(int i = 0; i < elementAttributes.getLength(); i++) { + this.elementAttributes.put(elementAttributes.getQName(i), elementAttributes.getValue(i)); + } + } + + /** + * + * @return The flag that indicates that the current element is at the root of the document + */ + public boolean isRootElement() { + return isRootElement; + } + + /** + * + * @param rootElement The flag that indicates that the current element is at the root of the document + */ + public void setRootElement(boolean rootElement) { + isRootElement = rootElement; + } + + /** + * + * @return The evaluation context that is used to evaluate the current element + */ + public RDFaEvaluationContext getEvaluationContext() { + return evaluationContext; + } + + /** + * + * @param evaluationContext The evaluation context that is used to evaluate the current element + */ + public void setEvaluationContext(RDFaEvaluationContext evaluationContext) { + this.evaluationContext = evaluationContext; + } + + /** + * + * @return The name ot the current element + */ + public String getElementName() { + return elementName; + } + + /** + * + * @param elementName The name ot the current element + */ + public void setElementName(String elementName) { + this.elementName = elementName; + } +} diff --git a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/SerializerFactory.java b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/SerializerFactory.java index 8693e1d5e..4468b8a74 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/io/serialization/SerializerFactory.java +++ b/src/main/java/fr/inria/corese/core/next/impl/io/serialization/SerializerFactory.java @@ -92,6 +92,7 @@ public SerializerFactory() { RDFFormat.RDFC_1_0.getName() ); }); + tempDefaultRegistry.put(RDFFormat.RDFC_1_0, model -> { RDFC10SerializerOptions defaultConfig = RDFC10SerializerOptions.defaultConfig(); RDFC10Canonicalizer rdfc10Canonicalizer = new RDFC10Canonicalizer( 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 6e247f9ca..a25b83e4f 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 @@ -129,7 +129,7 @@ protected void writeHeader(Writer writer) throws IOException { /** * Collects all namespaces used in the model and attempts to assign prefixes to them - * if auto-declaration is enabled and they are not already mapped. + * if auto-declaration is enabled, and they are not already mapped. */ protected Set collectUsedNamespaces() { Set namespaces = model.stream() diff --git a/src/main/java/fr/inria/corese/core/next/impl/temp/ModelNamespace.java b/src/main/java/fr/inria/corese/core/next/impl/temp/ModelNamespace.java index a5178316c..518355474 100644 --- a/src/main/java/fr/inria/corese/core/next/impl/temp/ModelNamespace.java +++ b/src/main/java/fr/inria/corese/core/next/impl/temp/ModelNamespace.java @@ -29,7 +29,7 @@ public String getPrefix() { } @Override - public String getName() { + public String getNamespace() { return namespaceURI; } } \ No newline at end of file diff --git a/src/test/java/fr/inria/corese/core/next/api/base/AbstractModelTest.java b/src/test/java/fr/inria/corese/core/next/api/base/AbstractModelTest.java index 92552b1f1..50745cbdf 100644 --- a/src/test/java/fr/inria/corese/core/next/api/base/AbstractModelTest.java +++ b/src/test/java/fr/inria/corese/core/next/api/base/AbstractModelTest.java @@ -75,16 +75,16 @@ void testSetNamespaceWithStringArgs() { Optional ns = model.getNamespace("ex"); assertTrue(ns.isPresent()); assertEquals("ex", ns.get().getPrefix()); - assertEquals("http://example.org/ns/", ns.get().getName()); + assertEquals("http://example.org/ns/", ns.get().getNamespace()); Namespace existingNs = model.setNamespace("ex", "http://example.org/ns/"); - assertEquals("http://example.org/ns/", existingNs.getName()); + assertEquals("http://example.org/ns/", existingNs.getNamespace()); assertEquals(1, model.getNamespaces().size()); model.setNamespace("ex", "http://example.com/newns/"); Optional updatedNs = model.getNamespace("ex"); assertTrue(updatedNs.isPresent()); - assertEquals("http://example.com/newns/", updatedNs.get().getName()); + assertEquals("http://example.com/newns/", updatedNs.get().getNamespace()); assertEquals(1, model.getNamespaces().size()); } @@ -99,7 +99,7 @@ void testSetNamespaceWithNamespaceObject() { Optional fetchedNs = model.getNamespace("geosparql"); assertTrue(fetchedNs.isPresent()); - assertEquals("http://example.org/ont/geosparql#", fetchedNs.get().getName()); + assertEquals("http://example.org/ont/geosparql#", fetchedNs.get().getNamespace()); assertEquals(1, model.getNamespaces().size()); } @@ -115,8 +115,8 @@ void testGetNamespaces() { Set namespaces = model.getNamespaces(); assertEquals(2, namespaces.size()); - assertTrue(namespaces.stream().anyMatch(n -> n.getPrefix().equals("ex1") && n.getName().equals("http://example.org/ns1/"))); - assertTrue(namespaces.stream().anyMatch(n -> n.getPrefix().equals("ex2") && n.getName().equals("http://example.org/ns2/"))); + assertTrue(namespaces.stream().anyMatch(n -> n.getPrefix().equals("ex1") && n.getNamespace().equals("http://example.org/ns1/"))); + assertTrue(namespaces.stream().anyMatch(n -> n.getPrefix().equals("ex2") && n.getNamespace().equals("http://example.org/ns2/"))); } /** @@ -134,7 +134,7 @@ void testRemoveNamespace() { // Comparison assertEquals(testNs.getPrefix(), removedNs.get().getPrefix()); - assertEquals(testNs.getName(), removedNs.get().getName()); + assertEquals(testNs.getNamespace(), removedNs.get().getNamespace()); assertEquals(0, model.getNamespaces().size()); assertFalse(model.getNamespace("ex").isPresent()); @@ -689,19 +689,8 @@ public String getPrefix() { } @Override - public String getName() { + public String getNamespace() { return name; } - - @Override - public int compareTo(Namespace other) { - - int prefixComparison = this.getPrefix().compareTo(other.getPrefix()); - if (prefixComparison != 0) { - return prefixComparison; - } - - return this.getName().compareTo(other.getName()); - } } } \ No newline at end of file diff --git a/src/test/java/fr/inria/corese/core/next/api/base/io/RDFFormatTest.java b/src/test/java/fr/inria/corese/core/next/api/base/io/RDFFormatTest.java index 4d4c1bfe3..949c0408c 100644 --- a/src/test/java/fr/inria/corese/core/next/api/base/io/RDFFormatTest.java +++ b/src/test/java/fr/inria/corese/core/next/api/base/io/RDFFormatTest.java @@ -306,7 +306,7 @@ void allFormats() { assertTrue(allFormats.contains(RDFFormat.JSONLD)); assertTrue(allFormats.contains(RDFFormat.RDFXML)); assertTrue(allFormats.contains(RDFFormat.TRIG)); - assertTrue(allFormats.contains(RDFFormat.RDFa)); + assertTrue(allFormats.contains(RDFFormat.RDFA)); assertTrue(allFormats.contains(RDFFormat.RDFC_1_0)); assertThrows(UnsupportedOperationException.class, () -> allFormats.add(RDFFormat.TURTLE), diff --git a/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfa/RDFaParserTest.java b/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfa/RDFaParserTest.java index 2458067d9..68ffd7b56 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfa/RDFaParserTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/io/parser/rdfa/RDFaParserTest.java @@ -3,6 +3,8 @@ import fr.inria.corese.core.next.api.*; import fr.inria.corese.core.next.api.base.io.RDFFormat; import fr.inria.corese.core.next.api.io.parser.RDFParser; +import fr.inria.corese.core.next.api.io.serializer.RDFSerializer; +import fr.inria.corese.core.next.impl.io.serialization.SerializerFactory; import fr.inria.corese.core.next.impl.common.vocabulary.RDF; import fr.inria.corese.core.next.impl.common.vocabulary.XSD; import fr.inria.corese.core.next.impl.io.parser.ParserFactory; @@ -11,14 +13,131 @@ import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; +import java.io.StringWriter; import java.util.Iterator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.*; + +class RDFaParserTest { + + private static final Logger logger = LoggerFactory.getLogger(RDFaParserTest.class); + + private ParserFactory parserFactory = new ParserFactory(); + private ValueFactory valueFactory = new CoreseAdaptedValueFactory(); + private final String defaultTurtlePrefixes = """ + @prefix bibo: . + @prefix cc: . + @prefix dbp: . + @prefix dbp-owl: . + @prefix dbr: . + @prefix dc: . + @prefix ex: . + @prefix foaf: . + @prefix owl: . + @prefix rdf: . + @prefix rdfa: . + @prefix rdfs: . + @prefix xhv: . + @prefix xsd: . + """; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; + @Test + void getRDFFormat() { + Model model = new CoreseModel(); + ValueFactory factory = new CoreseAdaptedValueFactory(); + RDFParser parser = new RDFaParser(model, factory); + assertEquals(RDFFormat.RDFA, parser.getRDFFormat()); + } + + @Test + void parseCurrentSubjectCreatorHead() { + String currentSubjectXHTML = """ + + + Jo's Friends and Family Blog + + + + + ... + + + """; + String currentSubjectNTriples = defaultTurtlePrefixes + """ + foaf:primaryTopic . + dc:creator "Jo" . + """; + + Model parsedModel = new CoreseModel(); + Model resultModel = new CoreseModel(); + ValueFactory factory = new CoreseAdaptedValueFactory(); + RDFParser testedParser = new RDFaParser(parsedModel, factory); + RDFParser resultParser = parserFactory.createRDFParser(RDFFormat.TURTLE, resultModel, valueFactory); + + assertEquals(RDFFormat.RDFA, testedParser.getRDFFormat()); + + resultParser.parse(new ByteArrayInputStream(currentSubjectNTriples.getBytes()), "http://example.org/"); + testedParser.parse(new ByteArrayInputStream(currentSubjectXHTML.getBytes()), "http://example.org/"); + + logModelContent(parsedModel); + assertEquals(resultModel.size(), parsedModel.size()); + Iterator itStatementRef = resultModel.iterator(); + Iterator itStatementTest = parsedModel.iterator(); + while(itStatementRef.hasNext() && itStatementTest.hasNext()) { + Statement statementRef = itStatementRef.next(); + Statement statementTest = itStatementTest.next(); + assertEquals(statementRef.getSubject(), statementTest.getSubject()); + assertEquals(statementRef.getPredicate(), statementTest.getPredicate()); + assertEquals(statementRef.getObject(), statementTest.getObject()); + assertEquals(statementRef.getContext(), statementTest.getContext()); + } + } + + @Test + void parseCurrentSubjectCreatorMiddle() { + String currentSubjectXHTML = """ + + + Jo's Blog + + +

Jo's blog

+

+ Welcome to my blog. +

+ + + """; + String currentSubjectNTriples = defaultTurtlePrefixes + """ + <> dc:creator "Jo" . + """; + + Model parsedModel = new CoreseModel(); + Model resultModel = new CoreseModel(); + ValueFactory factory = new CoreseAdaptedValueFactory(); + RDFParser testedParser = new RDFaParser(parsedModel, factory); + RDFParser resultParser = parserFactory.createRDFParser(RDFFormat.TURTLE, resultModel, valueFactory); -public class RDFaParserTest { + assertEquals(RDFFormat.RDFA, testedParser.getRDFFormat()); - private static final ValueFactory factory = new CoreseAdaptedValueFactory(); + resultParser.parse(new ByteArrayInputStream(currentSubjectNTriples.getBytes()), "http://example.org/"); + testedParser.parse(new ByteArrayInputStream(currentSubjectXHTML.getBytes()), "http://example.org/"); + + logModelContent(parsedModel); + assertEquals(resultModel.size(), parsedModel.size()); + Iterator itStatementRef = resultModel.iterator(); + Iterator itStatementTest = parsedModel.iterator(); + while(itStatementRef.hasNext() && itStatementTest.hasNext()) { + Statement statementRef = itStatementRef.next(); + Statement statementTest = itStatementTest.next(); + assertEquals(statementRef.getSubject(), statementTest.getSubject()); + assertEquals(statementRef.getPredicate(), statementTest.getPredicate()); + assertEquals(statementRef.getObject(), statementTest.getObject()); + assertEquals(statementRef.getContext(), statementTest.getContext()); + } + } @Test public void basicBaseTest() { @@ -27,29 +146,31 @@ public void basicBaseTest() { - - - Test 0001 - - -

This photo was taken by Mark Birbeck.

- + + + Test 0001 + + +

This photo was taken by Mark Birbeck.

+ """; Model testModel = new CoreseModel(); Model referenceModel = new CoreseModel(); - IRI subject = factory.createIRI("http://www.w3.org/2006/07/SWD/RDFa/testsuite/xhtml1-testcases/photo1.jpg"); - IRI predicate = factory.createIRI("http://purl.org/dc/elements/1.1/creator"); - Literal object = factory.createLiteral("Mark Birbeck"); + IRI subject = valueFactory.createIRI("http://www.w3.org/2006/07/SWD/RDFa/testsuite/xhtml1-testcases/photo1.jpg"); + IRI predicate = valueFactory.createIRI("http://purl.org/dc/elements/1.1/creator"); + Literal object = valueFactory.createLiteral("Mark Birbeck"); referenceModel.add(subject, predicate, object); - RDFParser parser = new ParserFactory().createRDFParser(RDFFormat.RDFa, testModel, factory); + RDFParser parser = new ParserFactory().createRDFParser(RDFFormat.RDFA, testModel, valueFactory); - parser.parse(new ByteArrayInputStream(testDataString.getBytes())); + parser.parse(new ByteArrayInputStream(testDataString.getBytes()), "http://www.w3.org/2006/07/SWD/RDFa/testsuite/xhtml1-testcases/"); - assertEquals(RDFFormat.RDFa, parser.getRDFFormat()); + assertEquals(RDFFormat.RDFA, parser.getRDFFormat()); + logModelContent(referenceModel); + logModelContent(testModel); assertEquals(referenceModel.size(), testModel.size()); Iterator itStatementRef = referenceModel.iterator(); Iterator itStatementTest = testModel.iterator(); @@ -79,12 +200,14 @@ public void aboutTest() { Model testModel = new CoreseModel(); - RDFParser parser = new ParserFactory().createRDFParser(RDFFormat.RDFa, testModel, factory); + RDFParser parser = new ParserFactory().createRDFParser(RDFFormat.RDFA, testModel, valueFactory); parser.parse(new ByteArrayInputStream(testDataString.getBytes()), "http://not.the.right.base.uri"); - IRI subject = factory.createIRI("http://w3id.org/people/pierre-maillot"); - IRI object = factory.createIRI("http://xmlns.com/foaf/0.1/Person"); + IRI subject = valueFactory.createIRI("http://w3id.org/people/pierre-maillot"); + IRI object = valueFactory.createIRI("http://xmlns.com/foaf/0.1/Person"); + + logModelContent(testModel); assertEquals(1, testModel.size()); assertTrue(testModel.contains(subject, RDF.type.getIRI(), object)); @@ -95,12 +218,13 @@ public void basicIRItoIRITest() { String testDataString = """ - + -
-
-
+
+
+
+
"""; @@ -108,18 +232,21 @@ public void basicIRItoIRITest() { Model testModel = new CoreseModel(); Model referenceModel = new CoreseModel(); - RDFParser parser = new ParserFactory().createRDFParser(RDFFormat.RDFa, testModel, factory); + RDFParser parser = new ParserFactory().createRDFParser(RDFFormat.RDFA, testModel, valueFactory); parser.parse(new ByteArrayInputStream(testDataString.getBytes()), "http://not.the.right.base.uri"); - IRI albertEinstein = factory.createIRI("http://dbpedia.org/resource/Albert_Einstein"); - IRI birthPlace = factory.createIRI("http://dbpedia.org/property/birthPlace"); - IRI germany = factory.createIRI("http://dbpedia.org/resource/Germany"); + IRI albertEinstein = valueFactory.createIRI("http://dbpedia.org/resource/Albert_Einstein"); + IRI birthPlace = valueFactory.createIRI("http://dbpedia.org/property/birthPlace"); + IRI germany = valueFactory.createIRI("http://dbpedia.org/resource/Germany"); - Statement aeBirthPlaceStatement = factory.createStatement(albertEinstein, birthPlace, germany); + Statement aeBirthPlaceStatement = valueFactory.createStatement(albertEinstein, birthPlace, germany); referenceModel.add(aeBirthPlaceStatement); + logModelContent(referenceModel); + logModelContent(testModel); + assertEquals(1, testModel.size()); assertEquals(referenceModel, testModel); assertTrue(referenceModel.containsAll(testModel)); @@ -143,15 +270,15 @@ public void basicIRItoStringTest() { Model testModel = new CoreseModel(); Model referenceModel = new CoreseModel(); - RDFParser parser = new ParserFactory().createRDFParser(RDFFormat.RDFa, testModel, factory); + RDFParser parser = new ParserFactory().createRDFParser(RDFFormat.RDFA, testModel, valueFactory); parser.parse(new ByteArrayInputStream(testDataString.getBytes()), "http://not.the.right.base.uri"); - IRI albertEinstein = factory.createIRI("http://dbpedia.org/resource/Albert_Einstein"); - IRI foafName = factory.createIRI("http://xmlns.com/foaf/0.1/name"); - Literal aeName = factory.createLiteral("Albert Einstein"); + IRI albertEinstein = valueFactory.createIRI("http://dbpedia.org/resource/Albert_Einstein"); + IRI foafName = valueFactory.createIRI("http://xmlns.com/foaf/0.1/name"); + Literal aeName = valueFactory.createLiteral("Albert Einstein"); - Statement aeNameStatement = factory.createStatement(albertEinstein, foafName, aeName); + Statement aeNameStatement = valueFactory.createStatement(albertEinstein, foafName, aeName); referenceModel.add(aeNameStatement); @@ -169,9 +296,9 @@ public void basicIRItoTypedLiteralTest() { -
- 1879-03-14 -
+
+ 1879-03-14 +
"""; @@ -179,15 +306,15 @@ public void basicIRItoTypedLiteralTest() { Model testModel = new CoreseModel(); Model referenceModel = new CoreseModel(); - RDFParser parser = new ParserFactory().createRDFParser(RDFFormat.RDFa, testModel, factory); + RDFParser parser = new ParserFactory().createRDFParser(RDFFormat.RDFA, testModel, valueFactory); parser.parse(new ByteArrayInputStream(testDataString.getBytes()), "http://not.the.right.base.uri"); - IRI albertEinstein = factory.createIRI("http://dbpedia.org/resource/Albert_Einstein"); - IRI dateOfBirth = factory.createIRI("http://dbpedia.org/property/dateOfBirth"); - Literal aeDateOfBirth = factory.createLiteral("1879-03-14", XSD.xsdDate.getIRI()); + IRI albertEinstein = valueFactory.createIRI("http://dbpedia.org/resource/Albert_Einstein"); + IRI dateOfBirth = valueFactory.createIRI("http://dbpedia.org/property/dateOfBirth"); + Literal aeDateOfBirth = valueFactory.createLiteral("1879-03-14", XSD.xsdDate.getIRI()); - Statement aeDateOfBirthStatement = factory.createStatement(albertEinstein, dateOfBirth, aeDateOfBirth); + Statement aeDateOfBirthStatement = valueFactory.createStatement(albertEinstein, dateOfBirth, aeDateOfBirth); referenceModel.add(aeDateOfBirthStatement); @@ -200,6 +327,10 @@ public void basicIRItoTypedLiteralTest() { Statement statementTest = itStatementTest.next(); assertEquals(statementRef.getSubject(), statementTest.getSubject()); assertEquals(statementRef.getPredicate(), statementTest.getPredicate()); + assertEquals(statementRef.getObject().isLiteral(), statementTest.getObject().isLiteral()); + if(statementRef.getObject().isLiteral()) { + assertEquals(((Literal) statementRef.getObject()).getDatatype(), ((Literal) statementTest.getObject()).getDatatype()); + } assertEquals(statementRef.getObject(), statementTest.getObject()); assertEquals(statementRef.getContext(), statementTest.getContext()); } @@ -226,18 +357,18 @@ public void basicChainTest() { Model testModel = new CoreseModel(); Model referenceModel = new CoreseModel(); - RDFParser parser = new ParserFactory().createRDFParser(RDFFormat.RDFa, testModel, factory); + RDFParser parser = new ParserFactory().createRDFParser(RDFFormat.RDFA, testModel, valueFactory); parser.parse(new ByteArrayInputStream(testDataString.getBytes()), "http://not.the.right.base.uri"); - IRI albertEinstein = factory.createIRI("http://dbpedia.org/resource/Albert_Einstein"); - IRI birthPlace = factory.createIRI("http://dbpedia.org/property/birthPlace"); - IRI germany = factory.createIRI("http://dbpedia.org/resource/Germany"); - IRI conventionalLongName = factory.createIRI("http://dbpedia.org/property/conventionalLongName"); - Literal gerLongName = factory.createLiteral("Federal Republic of Germany"); + IRI albertEinstein = valueFactory.createIRI("http://dbpedia.org/resource/Albert_Einstein"); + IRI birthPlace = valueFactory.createIRI("http://dbpedia.org/property/birthPlace"); + IRI germany = valueFactory.createIRI("http://dbpedia.org/resource/Germany"); + IRI conventionalLongName = valueFactory.createIRI("http://dbpedia.org/property/conventionalLongName"); + Literal gerLongName = valueFactory.createLiteral("Federal Republic of Germany"); - Statement aeBirthPlaceStatement = factory.createStatement(albertEinstein, birthPlace, germany); - Statement germanyNameStatement = factory.createStatement(germany, conventionalLongName, gerLongName); + Statement aeBirthPlaceStatement = valueFactory.createStatement(albertEinstein, birthPlace, germany); + Statement germanyNameStatement = valueFactory.createStatement(germany, conventionalLongName, gerLongName); referenceModel.add(aeBirthPlaceStatement); referenceModel.add(germanyNameStatement); @@ -247,4 +378,108 @@ public void basicChainTest() { assertTrue(referenceModel.containsAll(testModel)); } -} + + @Test + public void inheritSubjectTest() { + String testDataString = """ + + + + Test 0020 + + +
+ this photo was taken by + Mark Birbeck + +
+ + + """; + + Model testModel = new CoreseModel(); + Model referenceModel = new CoreseModel(); + + RDFParser parser = new ParserFactory().createRDFParser(RDFFormat.RDFA, testModel, valueFactory); + + parser.parse(new ByteArrayInputStream(testDataString.getBytes()), "http://inria.fr/"); + + IRI photo1 = valueFactory.createIRI("http://inria.fr/photo1.jpg"); + IRI creator = valueFactory.createIRI("http://purl.org/dc/elements/1.1/creator"); + Literal name = valueFactory.createLiteral("Mark Birbeck"); + + Statement aeDateOfBirthStatement = valueFactory.createStatement(photo1, creator, name); + + referenceModel.add(aeDateOfBirthStatement); + + logModelContent(referenceModel); + logModelContent(testModel); + + assertEquals(1, testModel.size()); + assertEquals(referenceModel.size(), testModel.size()); + Iterator itStatementRef = referenceModel.iterator(); + Iterator itStatementTest = testModel.iterator(); + while(itStatementRef.hasNext() && itStatementTest.hasNext()) { + Statement statementRef = itStatementRef.next(); + Statement statementTest = itStatementTest.next(); + assertEquals(statementRef.getSubject(), statementTest.getSubject()); + assertEquals(statementRef.getPredicate(), statementTest.getPredicate()); + assertEquals(statementRef.getObject(), statementTest.getObject()); + assertEquals(statementRef.getContext(), statementTest.getContext()); + } + assertTrue(referenceModel.containsAll(testModel)); + } + + @Test + public void multiplePrefixDeclaration() { + String testDataString = """ + + + + Test 0020 + + +
+ this photo was taken by + Mark Birbeck + and John Doe + +
+ + + """; + + Model testModel = new CoreseModel(); + Model referenceModel = new CoreseModel(); + + RDFParser parser = new ParserFactory().createRDFParser(RDFFormat.RDFA, testModel, valueFactory); + + parser.parse(new ByteArrayInputStream(testDataString.getBytes()), "http://inria.fr/"); + + IRI photo1 = valueFactory.createIRI("http://inria.fr/photo1.jpg"); + IRI creator1 = valueFactory.createIRI("http://purl.org/dc/elements/1.1/creator"); + Literal name1 = valueFactory.createLiteral("Mark Birbeck"); + IRI creator2 = valueFactory.createIRI("https://schema.org/creator"); + Literal name2 = valueFactory.createLiteral("John Doe"); + + Statement stat1 = valueFactory.createStatement(photo1, creator1, name1); + Statement stat2 = valueFactory.createStatement(photo1, creator2, name2); + + referenceModel.add(stat1); + referenceModel.add(stat2); + + logModelContent(referenceModel); + logModelContent(testModel); + + assertEquals(2, testModel.size()); + assertEquals(referenceModel, testModel); + assertTrue(referenceModel.containsAll(testModel)); + } + + private static void logModelContent(Model model) { + StringWriter outWriter = new StringWriter(); + RDFSerializer serializer = (new SerializerFactory()).createSerializer(RDFFormat.TURTLE, model); + serializer.write(outWriter); + logger.info("{}", outWriter.toString()); + } +} \ No newline at end of file diff --git a/src/test/java/fr/inria/corese/core/next/impl/temp/CoreseModelTest.java b/src/test/java/fr/inria/corese/core/next/impl/temp/CoreseModelTest.java index ebcded5d4..04d2d381f 100644 --- a/src/test/java/fr/inria/corese/core/next/impl/temp/CoreseModelTest.java +++ b/src/test/java/fr/inria/corese/core/next/impl/temp/CoreseModelTest.java @@ -163,7 +163,7 @@ void testFullConstructor() { Set testNamespaces = new HashSet<>(); Namespace ns1 = mock(Namespace.class); when(ns1.getPrefix()).thenReturn("ex"); - when(ns1.getName()).thenReturn("http://example.org/"); + when(ns1.getNamespace()).thenReturn("http://example.org/"); testNamespaces.add(ns1); CoreseModel newModel = new CoreseModel(mockCoreseGraph, testNamespaces); @@ -183,12 +183,12 @@ void testConstructor_FromNamespaces() { Set initialNamespaces = new HashSet<>(); Namespace ns1 = mock(Namespace.class); when(ns1.getPrefix()).thenReturn("prefix1"); - when(ns1.getName()).thenReturn("http://example.org/ns1#"); + when(ns1.getNamespace()).thenReturn("http://example.org/ns1#"); initialNamespaces.add(ns1); Namespace ns2 = mock(Namespace.class); when(ns2.getPrefix()).thenReturn("prefix2"); - when(ns2.getName()).thenReturn("http://example.org/ns2#"); + when(ns2.getNamespace()).thenReturn("http://example.org/ns2#"); initialNamespaces.add(ns2); CoreseModel newModel = new CoreseModel(initialNamespaces);