From 3740ceff153eb3dccbc5f67a6d7dff03f9302a57 Mon Sep 17 00:00:00 2001 From: ignazio Date: Mon, 15 Jan 2024 23:28:54 +0000 Subject: [PATCH] Allow arbitrary annotation properties as qualifier tags in OBO #1099 Allowing arbitrary defined annotation properties as qualifier tags. Use oboInOWL as the default namespace when looking up tag IRIs. This helps with backwards compatibility for undeclared annotation properties. Enforce oio namespace for created_by and creation_date. --- .../owlapi/vocab/Obo2OWLConstants.java | 3 + .../BOMSafeInputStreamAndParseTestCase.java | 10 --- .../oboformat/LoadAnonymousTestCase.java | 4 +- .../oboformat/RoundTripTestBasics.java | 10 ++- .../oboformat/RoundTripTestCase.java | 2 +- .../obolibrarytest/oboformat/TagIRIsTest.java | 69 +++++++++++++++++++ contract/src/test/resources/obo/tag_iris.obo | 56 +++++++++++++++ .../obolibrary/obo2owl/OWLAPIObo2Owl.java | 47 +++++++++++-- .../obolibrary/obo2owl/OWLAPIOwl2Obo.java | 13 +++- 9 files changed, 194 insertions(+), 20 deletions(-) create mode 100644 contract/src/test/java/org/semanticweb/owlapi/obolibrarytest/oboformat/TagIRIsTest.java create mode 100644 contract/src/test/resources/obo/tag_iris.obo diff --git a/api/src/main/java/org/semanticweb/owlapi/vocab/Obo2OWLConstants.java b/api/src/main/java/org/semanticweb/owlapi/vocab/Obo2OWLConstants.java index 91f325be20..17b0cb3a64 100644 --- a/api/src/main/java/org/semanticweb/owlapi/vocab/Obo2OWLConstants.java +++ b/api/src/main/java/org/semanticweb/owlapi/vocab/Obo2OWLConstants.java @@ -68,6 +68,9 @@ public enum Obo2OWLVocabulary implements HasIRI { /**IRI_IAO_0100001. */ IRI_IAO_0100001(DEFAULT_IRI_PREFIX, "IAO_0100001", "term replaced by", OboFormatTag.TAG_REPLACED_BY.getTag()), /**IRI_OIO_shorthand. */ IRI_OIO_shorthand(OIOVOCAB_IRI_PREFIX, SHORTHAND, SHORTHAND, SHORTHAND), /**IRI_OIO_consider. */ IRI_OIO_consider(OIOVOCAB_IRI_PREFIX, "consider", "consider", OboFormatTag.TAG_CONSIDER.getTag()), + /**IRI_OIO_id*/ IRI_OIO_id(OIOVOCAB_IRI_PREFIX, "id", "id", OboFormatTag.TAG_ID.getTag()), + /**IRI_OIO_created_by*/ IRI_OIO_created_by(OIOVOCAB_IRI_PREFIX, "created_by", "created by", OboFormatTag.TAG_CREATED_BY.getTag()), + /**IRI_OIO_creation_date*/ IRI_OIO_creation_date(OIOVOCAB_IRI_PREFIX, "creation_date", "creation date", OboFormatTag.TAG_CREATION_DATE.getTag()), /**IRI_OIO_hasOBOFormatVersion. */ IRI_OIO_hasOBOFormatVersion(OIOVOCAB_IRI_PREFIX, "hasOBOFormatVersion", "has_obo_format_version", OboFormatTag.TAG_FORMAT_VERSION.getTag()), /**IRI_OIO_treatXrefsAsIsA. */ IRI_OIO_treatXrefsAsIsA(OIOVOCAB_IRI_PREFIX, "treat-xrefs-as-is_a", "treat-xrefs-as-is_a", OboFormatTag.TAG_TREAT_XREFS_AS_IS_A.getTag()), /**IRI_OIO_treatXrefsAsHasSubClass. */ IRI_OIO_treatXrefsAsHasSubClass(OIOVOCAB_IRI_PREFIX, "treat-xrefs-as-has-subclass", "treat-xrefs-as-has-subclass", OboFormatTag.TAG_TREAT_XREFS_AS_HAS_SUBCLASS.getTag()), diff --git a/contract/src/test/java/org/semanticweb/owlapi/apitest/syntax/rdf/BOMSafeInputStreamAndParseTestCase.java b/contract/src/test/java/org/semanticweb/owlapi/apitest/syntax/rdf/BOMSafeInputStreamAndParseTestCase.java index 1e89870174..e6585413c7 100644 --- a/contract/src/test/java/org/semanticweb/owlapi/apitest/syntax/rdf/BOMSafeInputStreamAndParseTestCase.java +++ b/contract/src/test/java/org/semanticweb/owlapi/apitest/syntax/rdf/BOMSafeInputStreamAndParseTestCase.java @@ -71,14 +71,4 @@ void testBOMError32big(int[] b, String input) throws IOException { loadFrom(in); } } - - @ParameterizedTest - @MethodSource("data") - void testBOMError32bigReader(int[] b, String input) throws Exception { - try (InputStream in = in(b, input); - InputStreamReader r = new InputStreamReader(in); - ReaderDocumentSource source = new ReaderDocumentSource(r)) { - m.loadOntologyFromOntologyDocument(source); - } - } } diff --git a/contract/src/test/java/org/semanticweb/owlapi/obolibrarytest/oboformat/LoadAnonymousTestCase.java b/contract/src/test/java/org/semanticweb/owlapi/obolibrarytest/oboformat/LoadAnonymousTestCase.java index 5238eee114..ab1080277f 100644 --- a/contract/src/test/java/org/semanticweb/owlapi/obolibrarytest/oboformat/LoadAnonymousTestCase.java +++ b/contract/src/test/java/org/semanticweb/owlapi/obolibrarytest/oboformat/LoadAnonymousTestCase.java @@ -103,7 +103,9 @@ void shouldLoad() { literal("A unit of measurement is a standardized quantity of a physical quality.")), AnnotationAssertion(Annotation(ANNPROPS.hasDbXref, LITERALS.literal), ANNPROPS.iao0000115, CLASSES.uo1.getIRI(), - literal("A unit which is a standard measure of the distance between two points."))); + literal("A unit which is a standard measure of the distance between two points.")), + AnnotationAssertion(RDFSLabel(), ANNPROPS.createdBy.getIRI(), literal("created by")), + AnnotationAssertion(RDFSLabel(), ANNPROPS.id.getIRI(), literal("id"))); assertEquals(expected, asUnorderedSet(ontology.axioms())); } } diff --git a/contract/src/test/java/org/semanticweb/owlapi/obolibrarytest/oboformat/RoundTripTestBasics.java b/contract/src/test/java/org/semanticweb/owlapi/obolibrarytest/oboformat/RoundTripTestBasics.java index 27f043ad75..5b0e5d5cf9 100644 --- a/contract/src/test/java/org/semanticweb/owlapi/obolibrarytest/oboformat/RoundTripTestBasics.java +++ b/contract/src/test/java/org/semanticweb/owlapi/obolibrarytest/oboformat/RoundTripTestBasics.java @@ -8,6 +8,7 @@ import javax.annotation.Nullable; import org.semanticweb.owlapi.documents.StringDocumentTarget; +import org.semanticweb.owlapi.formats.OBODocumentFormat; import org.semanticweb.owlapi.model.OWLAxiom; import org.semanticweb.owlapi.model.OWLOntology; import org.semanticweb.owlapi.obolibrary.obo2owl.OWLAPIOwl2Obo; @@ -43,14 +44,19 @@ public List roundTripOBODoc(OBODoc obodoc, boolean isExpectRoundtrip) OWLOntology oo = convert(obodoc); StringDocumentTarget oo1 = saveOntology(oo); OBODoc obodoc2 = convert(oo); - StringDocumentTarget oo2 = saveOntology(convert(obodoc2)); + OWLOntology o2 = convert(obodoc2); + StringDocumentTarget oo2 = saveOntology(o2); String s1 = oo1.toString(); String s2 = oo2.toString(); assertEquals(s1, s2); obodoc2.check(); List diffs = OBODocDiffer.getDiffs(obodoc, obodoc2); if (isExpectRoundtrip) { - assertEquals(0, diffs.size(), "Expected no diffs but " + diffs); + if (diffs.size() > 0) { + StringDocumentTarget out1 = saveOntology(oo, new OBODocumentFormat()); + StringDocumentTarget out2 = saveOntology(o2, new OBODocumentFormat()); + assertEquals(out1.toString(), out2.toString()); + } } return diffs; } diff --git a/contract/src/test/java/org/semanticweb/owlapi/obolibrarytest/oboformat/RoundTripTestCase.java b/contract/src/test/java/org/semanticweb/owlapi/obolibrarytest/oboformat/RoundTripTestCase.java index 6d35afa307..71879849b5 100644 --- a/contract/src/test/java/org/semanticweb/owlapi/obolibrarytest/oboformat/RoundTripTestCase.java +++ b/contract/src/test/java/org/semanticweb/owlapi/obolibrarytest/oboformat/RoundTripTestCase.java @@ -226,7 +226,7 @@ void testRoundTripOWLRO() { Collection values = clause.getQualifierValues(); assertEquals(1, values.size()); QualifierValue value = values.iterator().next(); - assertEquals("http://purl.obolibrary.org/obo/IAO_0000116", value.getQualifier()); + assertEquals("IAO:0000116", value.getQualifier()); assertEquals("From Allen terminology", value.getValue()); found = true; } diff --git a/contract/src/test/java/org/semanticweb/owlapi/obolibrarytest/oboformat/TagIRIsTest.java b/contract/src/test/java/org/semanticweb/owlapi/obolibrarytest/oboformat/TagIRIsTest.java new file mode 100644 index 0000000000..f22c33a8f3 --- /dev/null +++ b/contract/src/test/java/org/semanticweb/owlapi/obolibrarytest/oboformat/TagIRIsTest.java @@ -0,0 +1,69 @@ +package org.semanticweb.owlapi.obolibrarytest.oboformat; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.semanticweb.owlapi.model.IRI; +import org.semanticweb.owlapi.model.OWLAnnotation; +import org.semanticweb.owlapi.model.OWLAnnotationProperty; +import org.semanticweb.owlapi.model.OWLAxiom; +import org.semanticweb.owlapi.model.OWLClass; +import org.semanticweb.owlapi.model.OWLLiteral; +import org.semanticweb.owlapi.model.OWLOntology; +import org.semanticweb.owlapi.utilities.OWLAPIStreamUtils; +import org.semanticweb.owlapi.vocab.OWL2Datatype; + +class TagIRIsTest extends OboFormatTestBasics { + + @Test + void testTagIRIMapping() { + OWLAnnotationProperty definition = + df.getOWLAnnotationProperty(df.getIRI("http://purl.obolibrary.org/obo/IAO_0000115")); + OWLAnnotationProperty oioCreatedBy = df.getOWLAnnotationProperty( + df.getIRI("http://www.geneontology.org/formats/oboInOwl#created_by")); + OWLAnnotationProperty oioInventedBy = df.getOWLAnnotationProperty( + df.getIRI("http://www.geneontology.org/formats/oboInOwl#invented_by")); + OWLAnnotationProperty source = + df.getOWLAnnotationProperty(df.getIRI("http://purl.obolibrary.org/obo/MYONT_20")); + OWLOntology ont = load("obo/tag_iris.obo", m); + Set axioms = OWLAPIStreamUtils.asSet(ont.axioms()); + OWLClass term1 = df.getOWLClass(df.getIRI("http://purl.obolibrary.org/obo/MYONT_1")); + OWLClass term2 = df.getOWLClass(df.getIRI("http://purl.obolibrary.org/obo/MYONT_2")); + OWLClass term3 = df.getOWLClass(df.getIRI("http://purl.obolibrary.org/obo/MYONT_3")); + OWLClass term4 = df.getOWLClass(df.getIRI("http://purl.obolibrary.org/obo/MYONT_4")); + Set annotations = Stream + .of(df.getOWLAnnotation(df.getRDFSComment(), string("Here is a sub-annotation.")), + df.getOWLAnnotation(df.getRDFSSeeAlso(), string("A nested see also value."))) + .collect(Collectors.toSet()); + assertTrue(axioms.contains(df.getOWLAnnotationAssertionAxiom(definition, term1.getIRI(), + string("Definition of term one."), annotations))); + assertTrue(axioms.contains(df.getOWLAnnotationAssertionAxiom(df.getRDFSSeeAlso(), + term1.getIRI(), string("See also value.")))); + assertTrue(axioms.contains(df.getOWLAnnotationAssertionAxiom(definition, term2.getIRI(), + string("Definition of term two."), Collections + .singleton(df.getOWLAnnotation(source, string("A nested annotation value.")))))); + assertTrue(axioms.contains(df.getOWLAnnotationAssertionAxiom(definition, term3.getIRI(), + string("Definition of term three."), Collections + .singleton(df.getOWLAnnotation(source, string("A definition source value.")))))); + assertTrue( + axioms.contains( + df.getOWLAnnotationAssertionAxiom(oioCreatedBy, term3.getIRI(), string("goc:bro"))), + "created_by is built in and should not be overridden by a typedef"); + assertTrue( + axioms.contains(df.getOWLAnnotationAssertionAxiom(definition, term4.getIRI(), + string("Definition of term four."), + Collections + .singleton(df.getOWLAnnotation(oioInventedBy, string("An inventor value."))))), + "An undeclared tag should have oio namespace"); + + } + + protected OWLLiteral string(String l) { + return df.getOWLLiteral(l, OWL2Datatype.XSD_STRING); + } +} diff --git a/contract/src/test/resources/obo/tag_iris.obo b/contract/src/test/resources/obo/tag_iris.obo new file mode 100644 index 0000000000..9e463e4faa --- /dev/null +++ b/contract/src/test/resources/obo/tag_iris.obo @@ -0,0 +1,56 @@ +format-version: 1.2 +ontology: myont + +[Term] +id: MYONT:1 +name: term one +def: "Definition of term one." [] {comment="Here is a sub-annotation.", seeAlso="A nested see also value."} +property_value: seeAlso "See also value." xsd:string + +[Term] +id: MYONT:2 +name: term two +def: "Definition of term two." [] {MYONT:20="A nested annotation value."} +property_value: MYONT:21 "A top level annotation value." xsd:string + +[Term] +id: MYONT:3 +name: term three +def: "Definition of term three." [] {source="A definition source value."} +intersection_of: MYONT:2 ! term two +intersection_of: results_in_transport_across GO:0005739 ! mitochondrion +created_by: goc:bro + +[Term] +id: MYONT:4 +name: term four +def: "Definition of term four." [] {invented_by="An inventor value."} + +[Typedef] +id: source +name: source +xref: MYONT:20 +is_metadata_tag: true + +[Typedef] +id: MYONT:21 +name: source2 +is_metadata_tag: true + +[Typedef] +id: seeAlso +name: see also +xref: http://www.w3.org/2000/01/rdf-schema#seeAlso +is_metadata_tag: true + +[Typedef] +id: results_in_transport_across +name: results in transport across +namespace: external +xref: RO:0002342 + +[Typedef] +id: created_by +name: created by +namespace: external +xref: http://purl.org/dc/terms/creator diff --git a/oboformat/src/main/java/org/semanticweb/owlapi/obolibrary/obo2owl/OWLAPIObo2Owl.java b/oboformat/src/main/java/org/semanticweb/owlapi/obolibrary/obo2owl/OWLAPIObo2Owl.java index 5f195a32a1..ef9fd0e030 100644 --- a/oboformat/src/main/java/org/semanticweb/owlapi/obolibrary/obo2owl/OWLAPIObo2Owl.java +++ b/oboformat/src/main/java/org/semanticweb/owlapi/obolibrary/obo2owl/OWLAPIObo2Owl.java @@ -3,6 +3,7 @@ import static org.semanticweb.owlapi.utilities.OWLAPIPreconditions.checkNotNull; import static org.semanticweb.owlapi.utilities.OWLAPIPreconditions.verifyNotNull; import static org.semanticweb.owlapi.vocab.Obo2OWLConstants.DEFAULT_IRI_PREFIX; +import static org.semanticweb.owlapi.vocab.Obo2OWLConstants.OIOVOCAB_IRI_PREFIX; import java.io.File; import java.io.FileOutputStream; @@ -1375,6 +1376,14 @@ protected OWLIndividual trIndividual(String instId) { return df.getOWLNamedIndividual(iri); } + private IRI trTagToIRIIncludingTypedefs(String tag) { + IRI iri = ANNOTATIONPROPERTYMAP.get(tag); + if (iri == null) { + iri = oboIdToIRI(tag, true); + } + return iri; + } + /** * Translate tag to annotation prop. * @@ -1382,7 +1391,7 @@ protected OWLIndividual trIndividual(String instId) { * @return the OWL annotation property */ protected OWLAnnotationProperty trTagToAnnotationProp(String tag) { - IRI iri = trTagToIRI(tag, df); + IRI iri = trTagToIRIIncludingTypedefs(tag); OWLAnnotationProperty ap = df.getOWLAnnotationProperty(iri); if (!apToDeclare.contains(ap)) { apToDeclare.add(ap); @@ -1463,9 +1472,19 @@ protected OWLAnnotationValue trLiteral(Object value) { * @return the iri */ public IRI oboIdToIRI(String id) { + return oboIdToIRI(id, false); + } + + /** + * Obo id to iri. + * + * @param id the id + * @return the iri + */ + public IRI oboIdToIRI(String id, boolean oboInOwlDefault) { IRI iri = idToIRICache.get(id); if (iri == null) { - iri = loadOboToIRI(id); + iri = loadOboToIRI(id, oboInOwlDefault); idToIRICache.put(id, iri); } return iri; @@ -1478,6 +1497,16 @@ public IRI oboIdToIRI(String id) { * @return the iri */ public IRI loadOboToIRI(String id) { + return loadOboToIRI(id, false); + } + + /** + * Obo id to iri. + * + * @param id the id + * @return the iri + */ + public IRI loadOboToIRI(String id, boolean oboInOwlDefault) { if (id.contains(" ")) { LOG.error("id contains space: \"{}\"", id); throw new OWLParserException("spaces not allowed: '" + id + '\''); @@ -1514,10 +1543,10 @@ public IRI loadOboToIRI(String id) { return oboIdToIRI(xid); } } - return otherProtocols(id); + return otherProtocols(id, oboInOwlDefault); } - protected IRI otherProtocols(String id) { + protected IRI otherProtocols(String id, boolean oboInOwlDefault) { String[] idParts = id.split(":", 2); String db; String localId; @@ -1537,7 +1566,15 @@ protected IRI otherProtocols(String id) { db = getDefaultIDSpace() + '#'; localId = idParts[0]; } - String uriPrefix = uriPrefix(db); + String uriPrefix; + if (oboInOwlDefault) { + uriPrefix = OIOVOCAB_IRI_PREFIX; + } else { + uriPrefix = DEFAULT_IRI_PREFIX + db; + if (idSpaceMap.containsKey(db)) { + uriPrefix = idSpaceMap.get(db); + } + } String safeId = safeID(localId); try { return df.getIRI(uriPrefix + safeId); diff --git a/oboformat/src/main/java/org/semanticweb/owlapi/obolibrary/obo2owl/OWLAPIOwl2Obo.java b/oboformat/src/main/java/org/semanticweb/owlapi/obolibrary/obo2owl/OWLAPIOwl2Obo.java index cd62ed6c17..2e38280181 100644 --- a/oboformat/src/main/java/org/semanticweb/owlapi/obolibrary/obo2owl/OWLAPIOwl2Obo.java +++ b/oboformat/src/main/java/org/semanticweb/owlapi/obolibrary/obo2owl/OWLAPIOwl2Obo.java @@ -390,7 +390,16 @@ public static String getIdentifierFromObject(OWLObject obj, OWLOntology ont) * @return obo identifier */ public static String getIdentifier(IRI iriId) { - String iri = iriId.toString(); + return getIdentifier(iriId.toString()); + } + + /** + * See table 5.9.2. Translation of identifiers + * + * @param iri the iri + * @return obo identifier + */ + public static String getIdentifier(String iri) { // canonical IRIs String id = getId(iri); String[] s = BLANK_NODE_MARKER.split(id); @@ -485,6 +494,8 @@ protected static String tagFromPrefix(String iri, @Nullable String tag) { } if (iri.startsWith(Obo2OWLConstants.OIOVOCAB_IRI_PREFIX)) { return iri.substring(Obo2OWLConstants.OIOVOCAB_IRI_PREFIX.length()); + } else { + return getIdentifier(iri); } } return tag;