diff --git a/api/src/main/java/org/semanticweb/owlapi/formats/OBODocumentFormat.java b/api/src/main/java/org/semanticweb/owlapi/formats/OBODocumentFormat.java index fea0f5e863..66f6475b8a 100644 --- a/api/src/main/java/org/semanticweb/owlapi/formats/OBODocumentFormat.java +++ b/api/src/main/java/org/semanticweb/owlapi/formats/OBODocumentFormat.java @@ -14,14 +14,12 @@ import javax.annotation.Nonnull; -import org.semanticweb.owlapi.model.OWLDocumentFormatImpl; - /** * @author Matthew Horridge, The University Of Manchester, Bio-Health * Informatics Group * @since 2.0.0 */ -public class OBODocumentFormat extends OWLDocumentFormatImpl { +public class OBODocumentFormat extends PrefixDocumentFormatImpl { /** * Key for validation parameter. Currently supports Boolean.TRUE and @@ -30,20 +28,15 @@ public class OBODocumentFormat extends OWLDocumentFormatImpl { public static final String VALIDATION = "obo.validation"; private static final long serialVersionUID = 40000L; + public OBODocumentFormat() { + super(); + this.clear(); + } + @Nonnull @Override public String getKey() { return "OBO Format"; } - @Override - public boolean isPrefixOWLOntologyFormat() { - return false; - } - - @Override - public PrefixDocumentFormat asPrefixOWLOntologyFormat() { - throw new UnsupportedOperationException(getClass().getName() - + " is not a PrefixDocumentFormat"); - } } diff --git a/contract/src/test/java/org/obolibrary/oboformat/PrefixesTest.java b/contract/src/test/java/org/obolibrary/oboformat/PrefixesTest.java new file mode 100644 index 0000000000..8a3cf7ddf7 --- /dev/null +++ b/contract/src/test/java/org/obolibrary/oboformat/PrefixesTest.java @@ -0,0 +1,113 @@ +package org.obolibrary.oboformat; + +import org.junit.jupiter.api.Test; +import org.semanticweb.owlapi.api.test.baseclasses.TestBase; +import org.semanticweb.owlapi.apibinding.OWLManager; +import org.semanticweb.owlapi.formats.OBODocumentFormat; +import org.semanticweb.owlapi.model.*; +import org.semanticweb.owlapi.search.EntitySearcher; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +public class PrefixesTest extends TestBase { + + @Test + void testPrefixesRoundtrip() throws OWLOntologyStorageException, IOException { + OWLDataFactory factory = OWLManager.getOWLDataFactory(); + OWLAnnotationProperty termReplacedBy = factory.getOWLAnnotationProperty(IRI.create("http://purl.obolibrary.org/obo/IAO_0100001")); + OWLAnnotationProperty consider = factory.getOWLAnnotationProperty(IRI.create("http://www.geneontology.org/formats/oboInOwl#consider")); + OWLOntology oboOnt = loadOntology("obo/test_prefixes.obo", OWLManager.createOWLOntologyManager()); + assertTrue(oboOnt.containsClassInSignature(IRI.create("http://purl.obolibrary.org/obo/FOO_1234"))); + assertTrue(oboOnt.containsClassInSignature(IRI.create("http://somewhere.org/MyClass"))); + assertFalse(oboOnt.containsClassInSignature(IRI.create("https://example.org/myns/#ABC_123"))); + assertTrue(oboOnt.containsClassInSignature(IRI.create("https://example.org/myns/ABC_123"))); + Map prefixMap = oboOnt.getOWLOntologyManager().getOntologyFormat(oboOnt).asPrefixOWLOntologyFormat().getPrefixName2PrefixMap(); + assertEquals("http://somewhere.org/", prefixMap.get("sw:")); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + oboOnt.getOWLOntologyManager().saveOntology(oboOnt, stream); + stream.close(); + String roundtripOBO = new String(stream.toByteArray(), StandardCharsets.UTF_8); + assertTrue(roundtripOBO.contains("idspace: sw http://somewhere.org/")); + assertTrue(roundtripOBO.contains("[Term]\nid: FOO:1234\nis_a: sw:MyClass")); + + OWLOntology replacementsOnt = loadOntology("obo/iris_for_obsoletes_replacements.obo", OWLManager.createOWLOntologyManager()); + assertTrue(EntitySearcher.getAnnotationAssertionAxioms(factory.getOWLClass(IRI.create("http://purl.obolibrary.org/obo/GO_0000108")), replacementsOnt).stream() + .filter(ax -> ax.getProperty().equals(termReplacedBy)) + .anyMatch(ax -> ax.getValue().asIRI().get().equals(IRI.create("http://purl.obolibrary.org/obo/GO_0000109"))), + "Values for replaced_by should be IRIs rather than strings"); + assertTrue(EntitySearcher.getAnnotationAssertionAxioms(factory.getOWLClass(IRI.create("http://purl.obolibrary.org/obo/GO_0000114")), replacementsOnt).stream() + .filter(ax -> ax.getProperty().equals(consider)) + .anyMatch(ax -> ax.getValue().asIRI().get().equals(IRI.create("http://purl.obolibrary.org/obo/GO_0000083"))), + "Values for consider should be IRIs rather than strings"); + assertTrue(EntitySearcher.getAnnotationAssertionAxioms(factory.getOWLClass(IRI.create("http://purl.obolibrary.org/obo/GO_0010553")), replacementsOnt).stream() + .filter(ax -> ax.getProperty().equals(termReplacedBy)) + .anyMatch(ax -> ax.getValue().asIRI().get().equals(IRI.create("http://purl.obolibrary.org/obo/GO_0000122"))), + "Values for replaced_by on alt_ids should be IRIs"); + ByteArrayOutputStream stream2 = new ByteArrayOutputStream(); + replacementsOnt.getOWLOntologyManager().saveOntology(replacementsOnt, stream2); + stream2.close(); + String roundtripReplacementsOnt = new String(stream2.toByteArray(), StandardCharsets.UTF_8); + assertFalse(roundtripReplacementsOnt.contains("idspace:")); + assertTrue(roundtripReplacementsOnt.contains("replaced_by: GO:0000109")); + } + + @Test + void testHandlingOfDeclaredOBOPrefix() throws OWLOntologyStorageException, IOException, OWLOntologyCreationException { + OWLOntology oboOnt = loadOntology("obo/test_obo_prefix.obo", OWLManager.createOWLOntologyManager()); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + oboOnt.getOWLOntologyManager().saveOntology(oboOnt, stream); + stream.close(); + String roundtripOBO = new String(stream.toByteArray(), StandardCharsets.UTF_8); + assertFalse(roundtripOBO.contains("obo:")); + assertFalse(roundtripOBO.contains("obo ")); + assertFalse(roundtripOBO.contains("ex:MMyClass"), "The longest available namespace match should be used"); + assertFalse(roundtripOBO.contains("owl-axioms:")); + } + + @Test + void testOBOFormatShouldNotInjectPrefixesInConstructedDocFormat() throws OWLOntologyStorageException, IOException { + OWLOntology oboOnt = loadOntology("obo/test_obo_prefix.obo", OWLManager.createOWLOntologyManager()); + OWLOntologyManager manager = oboOnt.getOWLOntologyManager(); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + OWLDocumentFormat format = new OBODocumentFormat(); + String defaultNamespace = format.asPrefixOWLOntologyFormat().getDefaultPrefix(); + format.asPrefixOWLOntologyFormat().copyPrefixesFrom(manager.getOntologyFormat(oboOnt).asPrefixOWLOntologyFormat()); + format.asPrefixOWLOntologyFormat().setDefaultPrefix(defaultNamespace); + manager.saveOntology(oboOnt, format, stream); + stream.close(); + String roundtripOBO = new String(stream.toByteArray(), StandardCharsets.UTF_8); + assertFalse(roundtripOBO.contains("idspace: rdf")); + } + + @Test + void testOBOFormatShouldNotInjectPrefixesInLoadedDocFormat() throws OWLOntologyStorageException, IOException { + OWLOntology oboOnt = loadOntology("obo/test_obo_prefix.obo", OWLManager.createOWLOntologyManager()); + OWLOntologyManager manager = oboOnt.getOWLOntologyManager(); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + OWLDocumentFormat format = manager.getOntologyFormat(oboOnt); + format.asPrefixOWLOntologyFormat().copyPrefixesFrom(manager.getOntologyFormat(oboOnt).asPrefixOWLOntologyFormat()); + manager.saveOntology(oboOnt, format, stream); + stream.close(); + String roundtripOBO = new String(stream.toByteArray(), StandardCharsets.UTF_8); + assertFalse(roundtripOBO.contains("idspace: rdf")); + } + + @Test + void testOBOFormatShouldPreventOBOPrefixes() throws OWLOntologyStorageException, IOException { + OWLOntology oboOnt = loadOntology("obo/test_obo_prefix.obo", OWLManager.createOWLOntologyManager()); + OWLOntologyManager manager = oboOnt.getOWLOntologyManager(); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + OWLDocumentFormat format = manager.getOntologyFormat(oboOnt); + format.asPrefixOWLOntologyFormat().setPrefix("GO", "http://purl.obolibrary.org/obo/GX_"); + manager.saveOntology(oboOnt, format, stream); + stream.close(); + String roundtripOBO = new String(stream.toByteArray(), StandardCharsets.UTF_8); + assertFalse(roundtripOBO.contains("idspace: GO")); + } + +} diff --git a/contract/src/test/resources/obo/iris_for_obsoletes_replacements.obo b/contract/src/test/resources/obo/iris_for_obsoletes_replacements.obo new file mode 100644 index 0000000000..12945df263 --- /dev/null +++ b/contract/src/test/resources/obo/iris_for_obsoletes_replacements.obo @@ -0,0 +1,102 @@ +format-version: 1.2 +subsetdef: chebi_ph7_3 "Rhea list of ChEBI terms representing the major species at pH 7.3." +subsetdef: gocheck_do_not_annotate "Term not to be used for direct annotation" +subsetdef: gocheck_do_not_manually_annotate "Term not to be used for direct manual annotation" +subsetdef: goslim_agr "AGR slim" +subsetdef: goslim_aspergillus "Aspergillus GO slim" +subsetdef: goslim_candida "Candida GO slim" +subsetdef: goslim_chembl "ChEMBL protein targets summary" +subsetdef: goslim_drosophila "Drosophila GO slim" +subsetdef: goslim_flybase_ribbon "FlyBase Drosophila GO ribbon slim" +subsetdef: goslim_generic "Generic GO slim" +subsetdef: goslim_metagenomics "Metagenomics GO slim" +subsetdef: goslim_mouse "Mouse GO slim" +subsetdef: goslim_pir "PIR GO slim" +subsetdef: goslim_plant "Plant GO slim" +subsetdef: goslim_pombe "Fission yeast GO slim" +subsetdef: goslim_synapse "synapse GO slim" +subsetdef: goslim_yeast "Yeast GO slim" +subsetdef: prokaryote_subset "GO subset for prokaryotes" +synonymtypedef: syngo_official_label "label approved by the SynGO project" +synonymtypedef: systematic_synonym "Systematic synonym" EXACT +default-namespace: gene_ontology +ontology: go +property_value: has_ontology_root_term GO:0003674 +property_value: has_ontology_root_term GO:0005575 +property_value: has_ontology_root_term GO:0008150 +property_value: http://purl.org/dc/elements/1.1/description "The Gene Ontology (GO) provides a framework and set of concepts for describing the functions of gene products from all organisms." xsd:string +property_value: http://purl.org/dc/elements/1.1/title "Gene Ontology" xsd:string +property_value: http://purl.org/dc/terms/license http://creativecommons.org/licenses/by/4.0/ + +[Term] +id: GO:0000108 +name: obsolete repairosome +namespace: cellular_component +def: "OBSOLETE. A stable complex of proteins that carry out the DNA damage recognition and incision reactions characteristic of nucleotide excision repair (NER), such as DNA damage recognition, DNA helix unwinding, and endonucleolytic cleavage at sites flanking damaged DNA; includes TFIIH subunits and additional polypeptides; may form in the absence of DNA damage." [PMID:10681587, PMID:9852079] +comment: This term was made obsolete because 'repairosome' has fallen out of use in the literature, and the large complex described in the definition has not been confirmed to exist. The term has also confused annotators. +synonym: "repairosome" EXACT [] +is_obsolete: true +replaced_by: GO:0000109 + +[Term] +id: GO:0000109 +name: nucleotide-excision repair complex +namespace: cellular_component +def: "Any complex formed of proteins that act in nucleotide-excision repair." [PMID:10915862] +comment: Note that process information is included in the term and definition for the purpose of describing and distinguishing the complex. +subset: goslim_pir +synonym: "UvrB-UvrC complex" NARROW [PMID:12145219] +synonym: "UvrBC complex" NARROW [GOC:bhm, PMID:12145219] +is_a: GO:0032991 ! protein-containing complex +intersection_of: GO:0032991 ! protein-containing complex +intersection_of: capable_of_part_of GO:0006289 ! nucleotide-excision repair +relationship: part_of GO:0005634 ! nucleus + +[Term] +id: GO:0000114 +name: obsolete regulation of transcription involved in G1 phase of mitotic cell cycle +namespace: biological_process +def: "OBSOLETE. Any process that regulates transcription such that the target genes are transcribed as part of the G1 phase of the mitotic cell cycle." [GOC:dph, GOC:mah, GOC:tb] +comment: This term was made obsolete because it is unclear exactly what it means. It could mean either 'regulation of transcription during phase X' or 'regulation of transition between phase X and phase Y'. +synonym: "G1-specific transcription in mitotic cell cycle" RELATED [] +synonym: "regulation of transcription from RNA polymerase II promoter during G1 phase of cell cycle" EXACT [] +synonym: "regulation of transcription involved in G1 phase of mitotic cell cycle" EXACT [] +is_obsolete: true +consider: GO:0000083 +consider: GO:0006357 + +[Term] +id: GO:0000122 +name: negative regulation of transcription by RNA polymerase II +namespace: biological_process +alt_id: GO:0010553 +alt_id: GO:0045816 +def: "Any process that stops, prevents, or reduces the frequency, rate or extent of transcription mediated by RNA polymerase II." [GOC:go_curators, GOC:txnOH] +synonym: "down regulation of global transcription from RNA polymerase II promoter" RELATED [] +synonym: "down regulation of transcription from RNA polymerase II promoter" EXACT [] +synonym: "down-regulation of global transcription from RNA polymerase II promoter" RELATED [] +synonym: "down-regulation of transcription from RNA polymerase II promoter" EXACT [] +synonym: "downregulation of global transcription from RNA polymerase II promoter" RELATED [] +synonym: "downregulation of transcription from RNA polymerase II promoter" EXACT [] +synonym: "inhibition of global transcription from RNA polymerase II promoter" RELATED [] +synonym: "inhibition of transcription from RNA polymerase II promoter" EXACT [] +synonym: "negative regulation of gene-specific transcription from RNA polymerase II promoter" RELATED [] +synonym: "negative regulation of global transcription from Pol II promoter" RELATED [] +synonym: "negative regulation of transcription from Pol II promoter" EXACT [] +synonym: "negative regulation of transcription from RNA polymerase II promoter" EXACT [] +synonym: "negative regulation of transcription from RNA polymerase II promoter, global" RELATED [] +intersection_of: GO:0065007 ! biological regulation +intersection_of: negatively_regulates GO:0006366 ! transcription by RNA polymerase II + +[Typedef] +id: capable_of_part_of +name: capable of part of +namespace: external +xref: RO:0002216 + +[Typedef] +id: negatively_regulates +name: negatively regulates +namespace: external +xref: RO:0002212 +is_a: regulates ! regulates diff --git a/contract/src/test/resources/obo/test_obo_prefix.obo b/contract/src/test/resources/obo/test_obo_prefix.obo new file mode 100644 index 0000000000..ad3fba035a --- /dev/null +++ b/contract/src/test/resources/obo/test_obo_prefix.obo @@ -0,0 +1,25 @@ +format-version: 1.2 +idspace: ex http://example.org/ +idspace: ex1 http://example.org/M +idspace: obo http://purl.obolibrary.org/obo/ +ontology: foo + +[Term] +id: FOO:1234 +name: the 1234 class +def: "A very important concept." +is_a: ex:MyClass + +[Term] +id: ex:MyClass +name: my ex class + +[Term] +id: ex1:MyClass +name: my ex1 class +relationship: capable_of_part_of FOO:1234 + +[Typedef] +id: capable_of_part_of +name: capable of part of +xref: RO:0002216 diff --git a/contract/src/test/resources/obo/test_prefixes.obo b/contract/src/test/resources/obo/test_prefixes.obo new file mode 100644 index 0000000000..afd9cc1878 --- /dev/null +++ b/contract/src/test/resources/obo/test_prefixes.obo @@ -0,0 +1,17 @@ +format-version: 1.2 +idspace: owl http://www.w3.org/2002/07/owl# +idspace: rdf http://www.w3.org/1999/02/22-rdf-syntax-ns# +idspace: rdfs http://www.w3.org/2000/01/rdf-schema# +idspace: sw http://somewhere.org/ +idspace: myns https://example.org/myns/ +idspace: xml http://www.w3.org/XML/1998/namespace +idspace: xsd http://www.w3.org/2001/XMLSchema# +ontology: foo + +[Term] +id: FOO:1234 +is_a: sw:MyClass + +[Term] +id: myns:ABC_123 +is_a: sw:MyClass diff --git a/oboformat/src/main/java/org/obolibrary/obo2owl/OBOFormatPrefixManager.java b/oboformat/src/main/java/org/obolibrary/obo2owl/OBOFormatPrefixManager.java new file mode 100644 index 0000000000..101234e543 --- /dev/null +++ b/oboformat/src/main/java/org/obolibrary/obo2owl/OBOFormatPrefixManager.java @@ -0,0 +1,181 @@ +package org.obolibrary.obo2owl; + +import org.semanticweb.owlapi.model.IRI; +import org.semanticweb.owlapi.model.OWLRuntimeException; +import org.semanticweb.owlapi.model.PrefixManager; +import org.semanticweb.owlapi.util.StringComparator; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; + +public class OBOFormatPrefixManager implements PrefixManager { + + private final String OBO_NS = "http://purl.obolibrary.org/obo/"; + + @Nonnull + private StringComparator comparator = new StringComparator() { + + private final Comparator alphabetical = Comparator.comparing(s -> s); + private final Comparator byLength = Comparator.comparing(s -> -s.length()); + private final Comparator byBoth = byLength.thenComparing(alphabetical); + + @Override + public int compare(String o1, String o2) { + return byBoth.compare(o1, o2); + } + + }; + + @Nonnull + private final TreeMap nsToPrefix; + @Nonnull + private final TreeMap prefixToNS; + + public OBOFormatPrefixManager(@Nullable PrefixManager pm) { + prefixToNS = new TreeMap<>(comparator); + nsToPrefix = new TreeMap<>(comparator); + if (pm != null) { + copyPrefixesFrom(pm); + } + } + + @Nonnull + @Override + public StringComparator getPrefixComparator() { + return this.comparator; + } + + @Override + public void setPrefixComparator(@Nonnull StringComparator comparator) { + this.comparator = comparator; + } + + @Nullable + @Override + public String getDefaultPrefix() { + return null; + } + + @Override + public boolean containsPrefixMapping(@Nonnull String prefixName) { + return prefixToNS.containsKey(prefixName); + } + + @Nullable + @Override + public String getPrefix(@Nonnull String prefixName) { + return prefixToNS.get(prefixName); + } + + @Nonnull + @Override + public Map getPrefixName2PrefixMap() { + return Collections.unmodifiableMap(prefixToNS); + } + + @Nonnull + @Override + public IRI getIRI(@Nonnull String prefixIRI) { + if (prefixIRI.startsWith("<")) { + return IRI.create(prefixIRI.substring(1, prefixIRI.length() - 1)); + } + int sep = prefixIRI.indexOf(':'); + if (sep == -1) { + if (getDefaultPrefix() != null) { + return IRI.create(getDefaultPrefix() + prefixIRI); + } else { + return IRI.create(prefixIRI); + } + } else { + String prefixName = prefixIRI.substring(0, sep + 1); + if (!containsPrefixMapping(prefixName)) { + throw new OWLRuntimeException( + "Prefix not registered for prefix name: " + prefixName); + } + String prefix = getPrefix(prefixName); + String localName = prefixIRI.substring(sep + 1); + return IRI.create(prefix, localName); + } + } + + @Nullable + @Override + public String getPrefixIRI(@Nonnull IRI iri) { + String iriString = iri.toString(); + Optional> mappingOpt = nsToPrefix.entrySet().stream().filter(e -> iriString.startsWith(e.getKey())).findFirst(); + if (mappingOpt.isPresent()) { + Map.Entry mapping = mappingOpt.get(); + String localId = iriString.substring(mapping.getKey().length()); + return mapping.getValue() + ":" + localId; + } else return null; + } + + @Nullable + @Override + public String getPrefixIRIIgnoreQName(@Nonnull IRI iri) { + return getPrefixIRI(iri); + } + + @Nonnull + @Override + public Set getPrefixNames() { + return prefixToNS.keySet(); + } + + @Override + public void setDefaultPrefix(@Nullable String defaultPrefix) { + } + + /** + * @param prefixName name The prefix name (must end with a colon) + * @param prefix The prefix. This prefix manager does not accept prefixes that overlap with + * the default OBO namespace. + */ + @Override + public void setPrefix(@Nonnull String prefixName, @Nonnull String prefix) { + if (!prefixName.isEmpty() && !prefixName.equals(":") && !OBO_NS.startsWith(prefix) && !prefix.startsWith(OBO_NS)) { + String cleanPrefixName = prefixName; + if (prefixName.endsWith(":")) { + cleanPrefixName = prefixName.substring(0, prefixName.length() - 1); + } + prefixToNS.put(cleanPrefixName, prefix); + nsToPrefix.put(prefix, cleanPrefixName); + } + } + + @Override + public void copyPrefixesFrom(@Nonnull PrefixManager from) { + copyPrefixesFrom(from.getPrefixName2PrefixMap()); + } + + @Override + public void copyPrefixesFrom(@Nonnull Map from) { + for (Map.Entry e : from.entrySet()) { + String prefix = e.getKey(); + if (!prefix.isEmpty()) { + setPrefix(e.getKey(), e.getValue()); + } + } + } + + @Override + public void unregisterNamespace(@Nonnull String namespace) { + List toRemove = new ArrayList<>(); + for (Map.Entry e : prefixToNS.entrySet()) { + if (e.getValue().equals(namespace)) { + toRemove.add(e.getKey()); + } + } + for (String s : toRemove) { + prefixToNS.remove(s); + } + nsToPrefix.remove(namespace); + } + + @Override + public void clear() { + prefixToNS.clear(); + nsToPrefix.clear(); + } +} diff --git a/oboformat/src/main/java/org/obolibrary/obo2owl/OWLAPIObo2Owl.java b/oboformat/src/main/java/org/obolibrary/obo2owl/OWLAPIObo2Owl.java index 1194e344ec..cd027d3a5a 100644 --- a/oboformat/src/main/java/org/obolibrary/obo2owl/OWLAPIObo2Owl.java +++ b/oboformat/src/main/java/org/obolibrary/obo2owl/OWLAPIObo2Owl.java @@ -253,6 +253,10 @@ public void setObodoc(OBODoc obodoc) { this.obodoc = obodoc; } + public Map getIdSpaceMap() { + return Collections.unmodifiableMap(this.idSpaceMap); + } + /** * Gets the owl ontology. * @@ -548,7 +552,12 @@ public void trHeaderFrame(@Nonnull Frame headerFrame) { trAnnotations(clause)); } } else if (tag == OboFormatTag.TAG_IDSPACE) { - // do not translate, as they are just directives? TODO ask Chris + for (Clause clause : headerFrame.getClauses(t)) { + Object[] values = clause.getValues().toArray(); + String prefix = values[0].toString(); + String baseurl = values[1].toString(); + idSpaceMap.put(prefix, baseurl); + } } else if (tag == OboFormatTag.TAG_OWL_AXIOMS) { // in theory, there should only be one tag // but we can silently collapse multiple tags @@ -1244,6 +1253,12 @@ protected OWLAxiom trGenericClause(@Nonnull OWLAnnotationSubject sub, @Nonnull S } ax = fac.getOWLAnnotationAssertionAxiom(trTagToAnnotationProp(tag), sub, trLiteral(clause.getValue()), annotations); + } else if (tagConstant == OboFormatTag.TAG_REPLACED_BY + || tagConstant == OboFormatTag.TAG_CONSIDER) { + String curie = (String) clause.getValue(); + IRI iri = oboIdToIRI(curie); + ax = fac.getOWLAnnotationAssertionAxiom(trTagToAnnotationProp(tag), sub, + iri, annotations); } else { // generic // System.out.println("generic clause:"+clause); @@ -1700,12 +1715,15 @@ public IRI oboIdToIRI_load(@Nonnull String id, boolean oboInOwlDefault) { String localId; if (idParts.length > 1) { // Prefixed-ID (canonical or not) localId = idParts[1]; - uriPrefix = idSpaceMap.getOrDefault(idParts[0], DEFAULT_IRI_PREFIX + idParts[0] + '_'); - - // Non-canonical prefixed IDs use a '#' separator - // TODO - recognize all non-canonical prefixed IDs - if (localId.contains("_")) { - uriPrefix += "#"; + if (idSpaceMap.containsKey(idParts[0])) { + uriPrefix = idSpaceMap.get(idParts[0]); + } else { + uriPrefix = DEFAULT_IRI_PREFIX + idParts[0] + '_'; + // Non-canonical prefixed IDs use a '#' separator + // TODO - recognize all non-canonical prefixed IDs + if (localId.contains("_")) { + uriPrefix += "#"; + } } } else { // Unprefixed-ID // Special case for relation xrefs (5.9.3. Special Rules for Relations) @@ -1760,10 +1778,8 @@ protected String translateShorthandIdToExpandedId(@Nonnull String id) { Collection xrefs = tdf.getTagValues(OboFormatTag.TAG_XREF, Xref.class); String matchingExpandedId = null; for (Xref xref : xrefs) { - // System.err.println("ID:"+id+" xref:"+xref); if (xref != null) { String xid = xref.getIdref(); - // System.err.println(" ID:"+id+" xid:"+xid); if (xid.equals(id)) { continue; } @@ -1780,7 +1796,6 @@ protected String translateShorthandIdToExpandedId(@Nonnull String id) { if (matchingExpandedId == null) { return id; } - // System.err.println(" ID:"+id+" matching:"+matchingExpandedId); return matchingExpandedId; } diff --git a/oboformat/src/main/java/org/obolibrary/obo2owl/OWLAPIOwl2Obo.java b/oboformat/src/main/java/org/obolibrary/obo2owl/OWLAPIOwl2Obo.java index 1400e68e14..cf45247344 100644 --- a/oboformat/src/main/java/org/obolibrary/obo2owl/OWLAPIOwl2Obo.java +++ b/oboformat/src/main/java/org/obolibrary/obo2owl/OWLAPIOwl2Obo.java @@ -1,21 +1,12 @@ package org.obolibrary.obo2owl; +import static org.obolibrary.obo2owl.Obo2OWLConstants.DEFAULT_IRI_PREFIX; import static org.semanticweb.owlapi.search.EntitySearcher.getAnnotationObjects; import static org.semanticweb.owlapi.util.OWLAPIPreconditions.verifyNotNull; import java.io.UnsupportedEncodingException; import java.text.ParseException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -32,58 +23,9 @@ import org.obolibrary.oboformat.model.Xref; import org.obolibrary.oboformat.parser.OBOFormatConstants; import org.obolibrary.oboformat.parser.OBOFormatConstants.OboFormatTag; -import org.semanticweb.owlapi.model.AxiomType; -import org.semanticweb.owlapi.model.IRI; -import org.semanticweb.owlapi.model.OWLAnnotation; -import org.semanticweb.owlapi.model.OWLAnnotationAssertionAxiom; -import org.semanticweb.owlapi.model.OWLAnnotationProperty; -import org.semanticweb.owlapi.model.OWLAnnotationValue; -import org.semanticweb.owlapi.model.OWLAsymmetricObjectPropertyAxiom; -import org.semanticweb.owlapi.model.OWLAxiom; -import org.semanticweb.owlapi.model.OWLClass; -import org.semanticweb.owlapi.model.OWLClassAssertionAxiom; -import org.semanticweb.owlapi.model.OWLClassExpression; -import org.semanticweb.owlapi.model.OWLDataFactory; -import org.semanticweb.owlapi.model.OWLDatatype; -import org.semanticweb.owlapi.model.OWLDeclarationAxiom; -import org.semanticweb.owlapi.model.OWLDisjointClassesAxiom; -import org.semanticweb.owlapi.model.OWLDisjointObjectPropertiesAxiom; -import org.semanticweb.owlapi.model.OWLEntity; -import org.semanticweb.owlapi.model.OWLEquivalentClassesAxiom; -import org.semanticweb.owlapi.model.OWLEquivalentObjectPropertiesAxiom; -import org.semanticweb.owlapi.model.OWLFunctionalObjectPropertyAxiom; -import org.semanticweb.owlapi.model.OWLInverseFunctionalObjectPropertyAxiom; -import org.semanticweb.owlapi.model.OWLInverseObjectPropertiesAxiom; -import org.semanticweb.owlapi.model.OWLLiteral; -import org.semanticweb.owlapi.model.OWLNamedIndividual; -import org.semanticweb.owlapi.model.OWLNamedObject; -import org.semanticweb.owlapi.model.OWLNaryPropertyAxiom; -import org.semanticweb.owlapi.model.OWLObject; -import org.semanticweb.owlapi.model.OWLObjectAllValuesFrom; -import org.semanticweb.owlapi.model.OWLObjectCardinalityRestriction; -import org.semanticweb.owlapi.model.OWLObjectComplementOf; -import org.semanticweb.owlapi.model.OWLObjectExactCardinality; -import org.semanticweb.owlapi.model.OWLObjectIntersectionOf; -import org.semanticweb.owlapi.model.OWLObjectMaxCardinality; -import org.semanticweb.owlapi.model.OWLObjectMinCardinality; -import org.semanticweb.owlapi.model.OWLObjectProperty; -import org.semanticweb.owlapi.model.OWLObjectPropertyDomainAxiom; -import org.semanticweb.owlapi.model.OWLObjectPropertyExpression; -import org.semanticweb.owlapi.model.OWLObjectPropertyRangeAxiom; -import org.semanticweb.owlapi.model.OWLObjectSomeValuesFrom; -import org.semanticweb.owlapi.model.OWLObjectUnionOf; -import org.semanticweb.owlapi.model.OWLOntology; -import org.semanticweb.owlapi.model.OWLOntologyManager; -import org.semanticweb.owlapi.model.OWLQuantifiedObjectRestriction; -import org.semanticweb.owlapi.model.OWLReflexiveObjectPropertyAxiom; -import org.semanticweb.owlapi.model.OWLRuntimeException; -import org.semanticweb.owlapi.model.OWLSubAnnotationPropertyOfAxiom; -import org.semanticweb.owlapi.model.OWLSubClassOfAxiom; -import org.semanticweb.owlapi.model.OWLSubObjectPropertyOfAxiom; -import org.semanticweb.owlapi.model.OWLSubPropertyChainOfAxiom; -import org.semanticweb.owlapi.model.OWLSymmetricObjectPropertyAxiom; -import org.semanticweb.owlapi.model.OWLTransitiveObjectPropertyAxiom; +import org.semanticweb.owlapi.model.*; import org.semanticweb.owlapi.rdf.rdfxml.parser.RDFConstants; +import org.semanticweb.owlapi.util.DefaultPrefixManager; import org.semanticweb.owlapi.vocab.Namespaces; import org.semanticweb.owlapi.vocab.OWL2Datatype; import org.semanticweb.owlapi.vocab.OWLRDFVocabulary; @@ -105,9 +47,9 @@ public class OWLAPIOwl2Obo { */ private static final Logger LOG = LoggerFactory.getLogger(OWLAPIOwl2Obo.class); private static final String IRI_CLASS_SYNONYMTYPEDEF = - Obo2OWLConstants.DEFAULT_IRI_PREFIX + "IAO_synonymtypedef"; + DEFAULT_IRI_PREFIX + "IAO_synonymtypedef"; private static final String IRI_CLASS_SUBSETDEF = - Obo2OWLConstants.DEFAULT_IRI_PREFIX + "IAO_subsetdef"; + DEFAULT_IRI_PREFIX + "IAO_subsetdef"; /** * The absoulte url pattern. */ @@ -143,6 +85,12 @@ public class OWLAPIOwl2Obo { * The id space map. */ protected Map idSpaceMap; + + /** + * A PrefixManager which can be used to populate the idSpaceMap + */ + private PrefixManager prefixManager = new DefaultPrefixManager(); + /** * The annotation property map. */ @@ -171,8 +119,17 @@ public class OWLAPIOwl2Obo { protected final void init() { idSpaceMap = new HashMap<>(); - // legacy: - idSpaceMap.put("http://www.obofoundry.org/ro/ro.owl#", "OBO_REL"); + // preserve prefix mappings loaded from a previous serialization + if (this.prefixManager != null) { + this.prefixManager.getPrefixName2PrefixMap().forEach((prefix, namespace) -> { + String cleanPrefix = prefix; + if (prefix.endsWith(":")) { + cleanPrefix = prefix.substring(0, prefix.length() - 1); + } + // OBO format doesn't support a default namespace (empty prefix) + if (!cleanPrefix.isEmpty()) idSpaceMap.put(cleanPrefix, namespace); + }); + } untranslatableAxioms = new HashSet<>(); apToDeclare = new HashSet<>(); } @@ -185,7 +142,7 @@ protected final void init() { public OWLAPIOwl2Obo(@Nonnull OWLOntologyManager translationManager) { manager = translationManager; fac = manager.getOWLDataFactory(); - init(); + //init(); } /** @@ -276,6 +233,10 @@ public void setObodoc(@Nonnull OBODoc obodoc) { this.obodoc = obodoc; } + public void setPrefixManager(@Nonnull PrefixManager manager) { + this.prefixManager = new OBOFormatPrefixManager(manager); + } + /** * Convert. * @@ -400,7 +361,7 @@ protected void preProcess() { if (v instanceof OWLLiteral) { viewRel = ((OWLLiteral) v).getLiteral(); } else { - viewRel = getIdentifier((IRI) v); + viewRel = getIdentifier((IRI) v, this.prefixManager); } break; } @@ -480,7 +441,7 @@ protected boolean trObjectProperty(@Nullable OWLObjectProperty prop, @Nullable S clause = new Clause(tag, value); f.addClause(clause); } - addQualifiers(clause, annotations); + addQualifiers(clause, annotations, this.prefixManager); return true; } @@ -502,7 +463,7 @@ protected boolean trObjectProperty(@Nullable OWLObjectProperty prop, String tag, Clause clause = new Clause(tag); clause.addValue(value); f.addClause(clause); - addQualifiers(clause, annotations); + addQualifiers(clause, annotations, this.prefixManager); return true; } @@ -599,7 +560,7 @@ protected void tr(@Nonnull OWLSubPropertyChainOfAxiom ax) { clause.addValue(rel2); } f.addClause(clause); - addQualifiers(clause, unprocessedAnnotations); + addQualifiers(clause, unprocessedAnnotations, this.prefixManager); } /** @@ -797,7 +758,7 @@ protected void tr(@Nonnull OWLSubObjectPropertyOfAxiom ax) { Frame f = getTypedefFrame((OWLObjectProperty) sub); Clause clause = new Clause(OboFormatTag.TAG_IS_A, supId); f.addClause(clause); - addQualifiers(clause, ax.getAnnotations()); + addQualifiers(clause, ax.getAnnotations(), this.prefixManager); } else { error(ax, true); } @@ -813,17 +774,17 @@ protected void tr(@Nonnull OWLSubAnnotationPropertyOfAxiom ax) { false); return; } - String tagObject = owlObjectToTag(sup); + String tagObject = owlObjectToTag(sup, this.prefixManager); if (OboFormatTag.TAG_SYNONYMTYPEDEF.getTag().equals(tagObject)) { String name = ""; String scope = null; for (OWLAnnotationAssertionAxiom axiom : getOWLOntology() .getAnnotationAssertionAxioms(sub.getIRI())) { - String tg = owlObjectToTag(axiom.getProperty()); + String tg = owlObjectToTag(axiom.getProperty(), this.prefixManager); if (OboFormatTag.TAG_NAME.getTag().equals(tg)) { name = ((OWLLiteral) axiom.getValue()).getLiteral(); } else if (OboFormatTag.TAG_SCOPE.getTag().equals(tg)) { - scope = owlObjectToTag(axiom.getValue()); + scope = owlObjectToTag(axiom.getValue(), this.prefixManager); } } Frame hf = getObodoc().getHeaderFrame(); @@ -833,7 +794,7 @@ protected void tr(@Nonnull OWLSubAnnotationPropertyOfAxiom ax) { if (scope != null) { clause.addValue(scope); } - addQualifiers(clause, ax.getAnnotations()); + addQualifiers(clause, ax.getAnnotations(), this.prefixManager); if (!hf.getClauses().contains(clause)) { hf.addClause(clause); } else { @@ -844,7 +805,7 @@ protected void tr(@Nonnull OWLSubAnnotationPropertyOfAxiom ax) { String comment = ""; for (OWLAnnotationAssertionAxiom axiom : getOWLOntology() .getAnnotationAssertionAxioms(sub.getIRI())) { - String tg = owlObjectToTag(axiom.getProperty()); + String tg = owlObjectToTag(axiom.getProperty(), this.prefixManager); if (OboFormatTag.TAG_COMMENT.getTag().equals(tg)) { comment = ((OWLLiteral) axiom.getValue()).getLiteral(); break; @@ -859,7 +820,7 @@ protected void tr(@Nonnull OWLSubAnnotationPropertyOfAxiom ax) { } else { LOG.error("duplicate clause: {} in header", clause); } - addQualifiers(clause, ax.getAnnotations()); + addQualifiers(clause, ax.getAnnotations(), this.prefixManager); return; } if (sub instanceof OWLObjectProperty && sup instanceof OWLObjectProperty) { @@ -870,7 +831,7 @@ protected void tr(@Nonnull OWLSubAnnotationPropertyOfAxiom ax) { Frame f = getTypedefFrame(sub); Clause clause = new Clause(OboFormatTag.TAG_IS_A, supId); f.addClause(clause); - addQualifiers(clause, ax.getAnnotations()); + addQualifiers(clause, ax.getAnnotations(), this.prefixManager); } else { error(ax, true); } @@ -901,7 +862,7 @@ protected void tr(@Nonnull OWLAnnotationAssertionAxiom ax, @Nonnull Frame frame) @SuppressWarnings("null") protected boolean tr(OWLAnnotationProperty prop, @Nonnull OWLAnnotationValue annVal, @Nonnull Set qualifiers, @Nonnull Frame frame) { - String tagString = owlObjectToTag(prop); + String tagString = owlObjectToTag(prop, this.prefixManager); OboFormatTag tag = null; if (tagString != null) { tag = OBOFormatConstants.getTag(tagString); @@ -913,8 +874,8 @@ && isMetadataTag(prop)) { if (propId != null) { Clause clause = new Clause(OboFormatTag.TAG_RELATIONSHIP); clause.addValue(propId); - clause.addValue(getIdentifier((IRI) annVal)); - addQualifiers(clause, qualifiers); + clause.addValue(getIdentifier((IRI) annVal, this.prefixManager)); + addQualifiers(clause, qualifiers, this.prefixManager); frame.addClause(clause); return true; } @@ -929,8 +890,8 @@ && isMetadataTag(prop)) { if (!valueString.isEmpty()) { if (tag == OboFormatTag.TAG_ID) { if (!frame.getId().equals(value)) { - warn("Conflicting id definitions: 1) " + frame.getId() + " 2)" + value); - return false; + warn("Dropping conflicting id definition: 1) " + frame.getId() + " 2)" + value); + return true; } return true; } @@ -948,7 +909,7 @@ && isMetadataTag(prop)) { Set unprocessedQualifiers = new HashSet<>(qualifiers); if (tag == OboFormatTag.TAG_DEF) { for (OWLAnnotation aan : qualifiers) { - String propId = owlObjectToTag(aan.getProperty()); + String propId = owlObjectToTag(aan.getProperty(), this.prefixManager); if ("xref".equals(propId)) { OWLAnnotationValue v = aan.getValue(); String xrefValue; @@ -987,7 +948,7 @@ && isMetadataTag(prop)) { String synonymType = null; handleSynonym(qualifiers, synonymType, clause, unprocessedQualifiers); } - addQualifiers(clause, unprocessedQualifiers); + addQualifiers(clause, unprocessedQualifiers, this.prefixManager); // before adding the clause check for redundant clauses boolean redundant = false; for (Clause frameClause : frame.getClauses()) { @@ -1031,7 +992,7 @@ protected void handleSynonym(@Nonnull Set qualifiers, @Nullable S String type = null; clause.setXrefs(new ArrayList()); for (OWLAnnotation aan : qualifiers) { - String propId = owlObjectToTag(aan.getProperty()); + String propId = owlObjectToTag(aan.getProperty(), this.prefixManager); if (OboFormatTag.TAG_XREF.getTag().equals(propId)) { OWLAnnotationValue v = aan.getValue(); String xrefValue; @@ -1084,8 +1045,8 @@ protected boolean trGenericPropertyValue(OWLAnnotationProperty prop, OWLAnnotati // no built-in obo tag for this: use the generic property_value tag Clause clause = new Clause(OboFormatTag.TAG_PROPERTY_VALUE.getTag()); String propId = getIdentifier(prop); - addQualifiers(clause, qualifiers); - if (!propId.equals("shorthand")) { + addQualifiers(clause, qualifiers, this.prefixManager); + if (!prop.getIRI().equals(Obo2OWLVocabulary.IRI_OIO_shorthand.iri)) { clause.addValue(propId); if (annVal instanceof OWLLiteral) { OWLLiteral owlLiteral = (OWLLiteral) annVal; @@ -1104,7 +1065,7 @@ protected boolean trGenericPropertyValue(OWLAnnotationProperty prop, OWLAnnotati clause.addValue(dataTypeIri.toString()); } } else if (annVal instanceof IRI) { - clause.addValue(getIdentifier((IRI) annVal)); + clause.addValue(getIdentifier((IRI) annVal, this.prefixManager)); } frame.addClause(clause); } @@ -1126,7 +1087,7 @@ protected Object getValue(@Nonnull OWLAnnotationValue annVal, String tag) { OWLLiteral l = (OWLLiteral) annVal; value = l.isBoolean() ? Boolean.valueOf(l.parseBoolean()) : l.getLiteral(); } else if (annVal instanceof IRI) { - value = getIdentifier((IRI) annVal); + value = getIdentifier((IRI) annVal, this.prefixManager); } if (OboFormatTag.TAG_EXPAND_EXPRESSION_TO.getTag().equals(tag)) { String s = value.toString(); @@ -1151,8 +1112,19 @@ protected Object getValue(@Nonnull OWLAnnotationValue annVal, String tag) { * @param qualifiers the qualifiers */ protected static void addQualifiers(@Nonnull Clause c, @Nonnull Set qualifiers) { + addQualifiers(c, qualifiers, new DefaultPrefixManager()); + } + + /** + * Adds the qualifiers. + * + * @param c the c + * @param qualifiers the qualifiers + * @param pm PrefixManager to pass through for IRI compaction + */ + protected static void addQualifiers(@Nonnull Clause c, @Nonnull Set qualifiers, @Nonnull PrefixManager pm) { for (OWLAnnotation ann : qualifiers) { - String prop = owlObjectToTag(ann.getProperty()); + String prop = owlObjectToTag(ann.getProperty(), pm); if (prop == null) { prop = ann.getProperty().getIRI().toString(); } @@ -1163,7 +1135,7 @@ protected static void addQualifiers(@Nonnull Clause c, @Nonnull Set entry : this.idSpaceMap.entrySet()) { + Clause idSpaceClause = new Clause(OboFormatTag.TAG_IDSPACE.getTag()); + idSpaceClause.setValues(Arrays.asList(new String[] {entry.getKey(), entry.getValue() })); + f.addClause(idSpaceClause); + } } /** @@ -1311,7 +1288,7 @@ protected void tr(@Nonnull OWLEquivalentClassesAxiom ax) { Clause c = new Clause(OboFormatTag.TAG_EQUIVALENT_TO.getTag()); c.setValue(cls2); f.addClause(c); - addQualifiers(c, ax.getAnnotations()); + addQualifiers(c, ax.getAnnotations(), this.prefixManager); } else if (ce2 instanceof OWLObjectUnionOf) { List list2 = ((OWLObjectUnionOf) ce2).getOperandsAsList(); for (OWLClassExpression oce : list2) { @@ -1324,7 +1301,7 @@ protected void tr(@Nonnull OWLEquivalentClassesAxiom ax) { Clause c = new Clause(OboFormatTag.TAG_UNION_OF.getTag()); c.setValue(id); equivalenceAxiomClauses.add(c); - addQualifiers(c, ax.getAnnotations()); + addQualifiers(c, ax.getAnnotations(), this.prefixManager); } } else if (ce2 instanceof OWLObjectIntersectionOf) { List list2 = ((OWLObjectIntersectionOf) ce2).getOperandsAsList(); @@ -1426,7 +1403,7 @@ protected void tr(@Nonnull OWLEquivalentClassesAxiom ax) { assert string != null; c.addQualifierValue(new QualifierValue("all_only", string)); } - addQualifiers(c, ax.getAnnotations()); + addQualifiers(c, ax.getAnnotations(), this.prefixManager); } else if (!f.getClauses(OboFormatTag.TAG_INTERSECTION_OF).isEmpty()) { error( "The axiom is not translated (maximimum one IntersectionOf EquivalenceAxiom)", @@ -1481,7 +1458,7 @@ protected void tr(@Nonnull OWLDisjointClassesAxiom ax) { Clause c = new Clause(OboFormatTag.TAG_DISJOINT_FROM.getTag()); c.setValue(cls2); f.addClause(c); - addQualifiers(c, ax.getAnnotations()); + addQualifiers(c, ax.getAnnotations(), this.prefixManager); } /** @@ -1502,11 +1479,11 @@ protected void tr(@Nonnull OWLDeclarationAxiom axiom) { boolean isClass = entity.isOWLClass(); boolean isObjectProperty = entity.isOWLObjectProperty(); // check whether the entity is an alt_id - Optional altIdOptional = checkForOboAltId(set); + Optional altIdOptional = checkForOboAltId(set, this.prefixManager); if (altIdOptional.isPresent()) { // the entity will not be translated // instead create the appropriate alt_id in the replaced_by frame - String currentId = getIdentifier(entity.getIRI()); + String currentId = getIdentifier(entity.getIRI(), this.prefixManager); addAltId(altIdOptional.get().replacedBy, currentId, isClass, isObjectProperty); // add unrelated annotations to untranslatableAxioms axioms untranslatableAxioms.addAll(altIdOptional.get().unrelated); @@ -1522,7 +1499,7 @@ protected void tr(@Nonnull OWLDeclarationAxiom axiom) { for (OWLAxiom a : set) { OWLAnnotationAssertionAxiom ax = (OWLAnnotationAssertionAxiom) a; OWLAnnotationProperty prop = ax.getProperty(); - String tag = owlObjectToTag(prop); + String tag = owlObjectToTag(prop, this.prefixManager); if (OboFormatTag.TAG_IS_METADATA_TAG.getTag().equals(tag)) { f = getTypedefFrame(entity); break; @@ -1586,7 +1563,7 @@ private static class OboAltIdCheckResult { */ @Nonnull private static Optional checkForOboAltId( - Set annotations) { + Set annotations, @Nonnull PrefixManager pm) { String replacedBy = null; boolean isMerged = false; boolean isDeprecated = false; @@ -1610,7 +1587,7 @@ private static Optional checkForOboAltId( // fallback: also check for an IRI if (value.asIRI().isPresent()) { // translate IRI to OBO style ID - replacedBy = getIdentifier(value.asIRI().get()); + replacedBy = getIdentifier(value.asIRI().get(), pm); } else { unrelatedAxioms.add(axiom); } @@ -1637,7 +1614,7 @@ private static Optional checkForOboAltId( @Nullable public String getIdentifier(OWLObject obj) { try { - return getIdentifierFromObject(obj, getOWLOntology()); + return getIdentifierFromObject(obj, getOWLOntology(), this.prefixManager); } catch (UntranslatableAxiomException e) { error(e.getMessage(), true); } @@ -1725,30 +1702,47 @@ public static String getIdentifierFromObject(@Nonnull OWLObject obj, @Nonnull OW @Nullable public static String getIdentifierFromObject(OWLObject obj, @Nonnull OWLOntology ont) throws UntranslatableAxiomException { + return getIdentifierFromObject(obj, ont, new DefaultPrefixManager()); + } + + /** + * Retrieve the identifier for a given {@link OWLObject}. This methods uses also shorthand hints + * to resolve the identifier. Should the translation process encounter an unexpected axiom an + * + * @param obj the {@link OWLObject} to resolve + * @param ont the target ontology + * @param pm PrefixManager to pass through for IRI compaction + * @return identifier or null + * @throws UntranslatableAxiomException the untranslatable axiom exception + * {@link UntranslatableAxiomException} is thrown. + */ + @Nullable + public static String getIdentifierFromObject(OWLObject obj, @Nonnull OWLOntology ont, @Nonnull PrefixManager pm) + throws UntranslatableAxiomException{ if (obj instanceof OWLObjectProperty || obj instanceof OWLAnnotationProperty) { OWLEntity entity = (OWLEntity) obj; Set axioms = - ont.getAnnotationAssertionAxioms(entity.getIRI()); + ont.getAnnotationAssertionAxioms(entity.getIRI()); for (OWLAnnotationAssertionAxiom ax : axioms) { - String propId = getIdentifierFromObject(ax.getProperty().getIRI(), ont); + IRI propId = ax.getProperty().getIRI(); // see BFOROXrefTest // 5.9.3. Special Rules for Relations - if (propId.equals("shorthand")) { + if (propId.equals(Obo2OWLVocabulary.IRI_OIO_shorthand.iri)) { OWLAnnotationValue value = ax.getValue(); if (value instanceof OWLLiteral) { return ((OWLLiteral) value).getLiteral(); } throw new UntranslatableAxiomException( - "Untranslatable axiom, expected literal value, but was: " + value - + " in axiom: " + ax); + "Untranslatable axiom, expected literal value, but was: " + value + + " in axiom: " + ax); } } } if (obj instanceof OWLEntity) { - return getIdentifier(((OWLEntity) obj).getIRI()); + return getIdentifier(((OWLEntity) obj).getIRI(), pm); } if (obj instanceof IRI) { - return getIdentifier((IRI) obj); + return getIdentifier((IRI) obj, pm); } return null; } @@ -1761,15 +1755,36 @@ public static String getIdentifierFromObject(OWLObject obj, @Nonnull OWLOntology */ @Nullable public static String getIdentifier(@Nullable IRI iriId) { + return getIdentifier(iriId, new DefaultPrefixManager()); + } + + /** + * See table 5.9.2. Translation of identifiers + * + * @param iriId the iri id + * @param pm PrefixManager to pass through for IRI compaction + * @return obo identifier or null + */ + @Nullable + public static String getIdentifier(@Nullable IRI iriId, @Nonnull PrefixManager pm) { if (iriId == null) { return null; } - String iri = iriId.toString(); + // If the iri is OBO-style, don't use the prefix manager. + // There is a lot of special-case legacy behavior that + // should not be broken. + if (!iriId.toString().startsWith(DEFAULT_IRI_PREFIX)) { + String curie = pm.getPrefixIRIIgnoreQName(iriId); + if (curie != null && !curie.startsWith(":")) { + return curie; + } + } // canonical IRIs // if (iri.startsWith("http://purl.obolibrary.org/obo/")) { // String canonicalId = iri.replace("http://purl.obolibrary.org/obo/", // ""); // } + String iri = iriId.toString(); int indexSlash = iri.lastIndexOf('/'); String id = null; if (indexSlash > -1) { @@ -1816,7 +1831,7 @@ public static String getIdentifier(@Nullable IRI iriId) { } } if (s.length > 2 && !id.contains("#") - && s[s.length - 1].replaceAll("[0-9]", "").isEmpty()) { + && s[s.length - 1].replaceAll("[0-9]", "").isEmpty()) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < s.length; i++) { if (i > 0) { @@ -1841,6 +1856,18 @@ public static String getIdentifier(@Nullable IRI iriId) { */ @Nullable public static String owlObjectToTag(OWLObject obj) { + return owlObjectToTag(obj, new DefaultPrefixManager()); + } + + /** + * Owl object to tag. + * + * @param obj the object + * @param pm PrefixManager to pass through for IRI compaction + * @return the string + */ + @Nullable + public static String owlObjectToTag(OWLObject obj, @Nonnull PrefixManager pm) { IRI iriObj = null; if (obj instanceof OWLNamedObject) { iriObj = ((OWLNamedObject) obj).getIRI(); @@ -1854,8 +1881,8 @@ public static String owlObjectToTag(OWLObject obj) { String tag = ANNOTATIONPROPERTYMAP.get(iri); if (tag == null) { // hard coded values for legacy annotation properties: (TEMPORARY) - if (iri.startsWith(Obo2OWLConstants.DEFAULT_IRI_PREFIX + "IAO_")) { - String legacyId = iri.replace(Obo2OWLConstants.DEFAULT_IRI_PREFIX, ""); + if (iri.startsWith(DEFAULT_IRI_PREFIX + "IAO_")) { + String legacyId = iri.replace(DEFAULT_IRI_PREFIX, ""); if (legacyId.equals("IAO_xref")) { return OboFormatTag.TAG_XREF.getTag(); } @@ -1870,7 +1897,7 @@ public static String owlObjectToTag(OWLObject obj) { if (iri.startsWith(prefix)) { tag = iri.substring(prefix.length()); } else { - tag = getIdentifier(iriObj); + tag = getIdentifier(iriObj, pm); } } return tag; @@ -1883,7 +1910,7 @@ public static String owlObjectToTag(OWLObject obj) { * @return the term frame */ protected Frame getTermFrame(@Nonnull OWLClass entity) { - String id = getIdentifier(entity.getIRI()); + String id = getIdentifier(entity.getIRI(), this.prefixManager); return getTermFrame(id); } @@ -2042,7 +2069,7 @@ protected void tr(@Nonnull OWLSubClassOfAxiom ax) { c.setValue(getIdentifier(sup)); c.setQualifierValues(qvs); f.addClause(c); - addQualifiers(c, ax.getAnnotations()); + addQualifiers(c, ax.getAnnotations(), this.prefixManager); } else if (sup instanceof OWLObjectCardinalityRestriction) { // OWLObjectExactCardinality // OWLObjectMinCardinality @@ -2152,7 +2179,7 @@ protected Clause createRelationshipClauseWithRestrictions( c.addValue(getIdentifier(r.getProperty())); c.addValue(fillerId); c.setQualifierValues(qvs); - addQualifiers(c, ax.getAnnotations()); + addQualifiers(c, ax.getAnnotations(), this.prefixManager); return c; } @@ -2180,7 +2207,7 @@ protected Clause createRelationshipClauseWithCardinality( q = MAX_CARDINALITY; } c.addQualifierValue(new QualifierValue(q, Integer.toString(restriction.getCardinality()))); - addQualifiers(c, ax.getAnnotations()); + addQualifiers(c, ax.getAnnotations(), this.prefixManager); return c; } diff --git a/oboformat/src/main/java/org/semanticweb/owlapi/oboformat/OBOFormatOWLAPIParser.java b/oboformat/src/main/java/org/semanticweb/owlapi/oboformat/OBOFormatOWLAPIParser.java index f09f027319..9fd209e67d 100644 --- a/oboformat/src/main/java/org/semanticweb/owlapi/oboformat/OBOFormatOWLAPIParser.java +++ b/oboformat/src/main/java/org/semanticweb/owlapi/oboformat/OBOFormatOWLAPIParser.java @@ -20,6 +20,7 @@ import java.io.Serializable; import java.net.JarURLConnection; import java.net.URL; +import java.util.Map; import java.util.Optional; import javax.annotation.Nonnull; @@ -33,10 +34,8 @@ import org.semanticweb.owlapi.io.AbstractOWLParser; import org.semanticweb.owlapi.io.OWLOntologyDocumentSource; import org.semanticweb.owlapi.io.OWLParserException; -import org.semanticweb.owlapi.model.OWLDocumentFormat; -import org.semanticweb.owlapi.model.OWLDocumentFormatFactory; -import org.semanticweb.owlapi.model.OWLOntology; -import org.semanticweb.owlapi.model.OWLOntologyLoaderConfiguration; +import org.semanticweb.owlapi.model.*; +import org.semanticweb.owlapi.util.DefaultPrefixManager; /** oboformat parser */ public class OBOFormatOWLAPIParser extends AbstractOWLParser implements Serializable { @@ -51,6 +50,7 @@ public OWLDocumentFormat parse(@Nonnull OWLOntologyDocumentSource documentSource // XXX configuration is not used OBOFormatParser p = new OBOFormatParser(); OBODoc obodoc = null; + Map idSpaceMap = null; try { Reader reader = null; InputStream is = null; @@ -93,6 +93,7 @@ public OWLDocumentFormat parse(@Nonnull OWLOntologyDocumentSource documentSource // create a translator object and feed it the OBO Document OWLAPIObo2Owl bridge = new OWLAPIObo2Owl(ontology.getOWLOntologyManager()); bridge.convert(obodoc, ontology); + idSpaceMap = bridge.getIdSpaceMap(); } finally { if (is != null) { is.close(); @@ -104,7 +105,12 @@ public OWLDocumentFormat parse(@Nonnull OWLOntologyDocumentSource documentSource } catch (OBOFormatParserException e) { throw new OWLParserException(e); } - return new OBODocumentFormat(); + OBODocumentFormat format = new OBODocumentFormat(); + PrefixManager pm = new DefaultPrefixManager(); + pm.clear(); + format.setPrefixManager(pm); + if (idSpaceMap != null) format.copyPrefixesFrom(idSpaceMap); + return format; } @Nonnull diff --git a/oboformat/src/main/java/org/semanticweb/owlapi/oboformat/OBOFormatRenderer.java b/oboformat/src/main/java/org/semanticweb/owlapi/oboformat/OBOFormatRenderer.java index 986d5cae7f..2cdc7b8374 100644 --- a/oboformat/src/main/java/org/semanticweb/owlapi/oboformat/OBOFormatRenderer.java +++ b/oboformat/src/main/java/org/semanticweb/owlapi/oboformat/OBOFormatRenderer.java @@ -52,6 +52,9 @@ public static void render(@Nonnull OWLOntology ontology, @Nonnull Writer writer, OWLDocumentFormat format) throws OWLOntologyStorageException { try { OWLAPIOwl2Obo translator = new OWLAPIOwl2Obo(ontology.getOWLOntologyManager()); + if (format.isPrefixOWLOntologyFormat()) { + translator.setPrefixManager(format.asPrefixOWLOntologyFormat()); + } final OBODoc result = translator.convert(ontology); boolean hasImports = ontology.getImports().isEmpty() == false; NameProvider nameProvider;