diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ad04bdae..c472789b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Fix how Template adds entities to the QuotedEntityChecker [#1104] + + ## [1.9.5] - 2023-09-20 ### Added @@ -379,6 +384,7 @@ First official release of ROBOT! [#1148]: https://github.com/ontodev/robot/pull/1148 [#1135]: https://github.com/ontodev/robot/pull/1135 [#1119]: https://github.com/ontodev/robot/pull/1119 +[#1104]: https://github.com/ontodev/robot/pull/1104 [#1100]: https://github.com/ontodev/robot/pull/1100 [#1091]: https://github.com/ontodev/robot/issues/1091 [#1089]: https://github.com/ontodev/robot/issues/1089 diff --git a/docs/extract.md b/docs/extract.md index da832212d..0ef88d43e 100644 --- a/docs/extract.md +++ b/docs/extract.md @@ -79,12 +79,14 @@ For more details see the [MIREOT paper](http://dx.doi.org/10.3233/AO-2011-0087). The subset method extracts a sub-ontology that contains only the seed terms (that you specify with `--term` and `--term-file` options) and the relations between them. This method uses the [relation-graph](https://github.com/balhoff/relation-graph) to materialize the existential relations among the seed terms. Procedurally, the subset method materializes the input ontology and adds the inferred axioms to the input ontology. Then filters the ontology with the given seed terms. Finally, it reduces the filtered ontology to remove redundant subClassOf axioms. - robot extract --method subset \ - --input subset.obo \ - --term "obo:ONT_1" \ - --term "obo:ONT_5" \ - --term "BFO:0000050" \ - --output results/subset_result.owl +``` +robot extract --method subset \ + --input subset.obo \ + --term "obo:ONT_1" \ + --term "obo:ONT_5" \ + --term "BFO:0000050" \ + --output results/subset_result.owl +``` ROBOT expects any `--term` or IRI in the `--term-file` to exist in the input ontology. If none of the input terms exist, the command will fail with an [empty terms error](errors#empty-terms-error). This can be overridden by including `--force true`. diff --git a/robot-core/src/main/java/org/obolibrary/robot/QuotedEntityChecker.java b/robot-core/src/main/java/org/obolibrary/robot/QuotedEntityChecker.java index dc81dc6f1..e5840fffe 100644 --- a/robot-core/src/main/java/org/obolibrary/robot/QuotedEntityChecker.java +++ b/robot-core/src/main/java/org/obolibrary/robot/QuotedEntityChecker.java @@ -249,6 +249,9 @@ public void add(OWLEntity entity, String name) { if (entity == null) { return; } + if (name == null) { + return; + } Map map = pickMap(entity); if (map == null) { diff --git a/robot-core/src/main/java/org/obolibrary/robot/Template.java b/robot-core/src/main/java/org/obolibrary/robot/Template.java index f98b331fa..d95ec8135 100644 --- a/robot-core/src/main/java/org/obolibrary/robot/Template.java +++ b/robot-core/src/main/java/org/obolibrary/robot/Template.java @@ -173,7 +173,7 @@ public Template(@Nonnull String name, @Nonnull List> rows) throws E // Add the contents of the tableRows addTable(rows); - addLabels(); + addEntities(); createParser(); } @@ -203,7 +203,7 @@ public Template(@Nonnull String name, @Nonnull List> rows, IOHelper // Add the contents of the tableRows addTable(rows); - addLabels(); + addEntities(); createParser(); } @@ -237,7 +237,7 @@ public Template(@Nonnull String name, @Nonnull List> rows, OWLOntol // Add the contents of the tableRows addTable(rows); - addLabels(); + addEntities(); createParser(); } @@ -276,7 +276,7 @@ public Template( // Add the contents of the tableRows addTable(rows); - addLabels(); + addEntities(); createParser(); } @@ -320,7 +320,7 @@ public Template( // Add the contents of the tableRows addTable(rows); - addLabels(); + addEntities(); createParser(); parser.setOWLEntityChecker(this.checker); } @@ -557,102 +557,103 @@ private void addTable(List> rows) throws Exception { } } - /** Add the labels from the rows of the template to the QuotedEntityChecker. */ - private void addLabels() { - // If there's no label column, we can't add labels - if (labelColumn == -1) { + /** Add the entities from the rows of the template to the QuotedEntityChecker. */ + private void addEntities() { + for (List row : tableRows) { + addEntity(row); + } + } + + /** Add the entity from this row of the template to the QuotedEntityChecker. */ + private void addEntity(List row) { + String id = null; + try { + id = row.get(idColumn); + } catch (IndexOutOfBoundsException e) { + // ignore + } + + if (id == null) { return; } - for (List row : tableRows) { - String id = null; - if (idColumn != -1) { - try { - id = row.get(idColumn); - } catch (IndexOutOfBoundsException e) { - // ignore - } - } - String label = null; + String label = null; + try { + label = row.get(labelColumn); + } catch (IndexOutOfBoundsException e) { + // ignore + } + + String type = null; + if (typeColumn != -1) { try { - label = row.get(labelColumn); + type = row.get(typeColumn); } catch (IndexOutOfBoundsException e) { // ignore } + } + if (type == null || type.trim().isEmpty()) { + type = "class"; + } - if (idColumn != -1 && id == null) { - continue; - } - - if (id == null || label == null) { - continue; - } - - String type = null; - if (typeColumn != -1) { - try { - type = row.get(typeColumn); - } catch (IndexOutOfBoundsException e) { - // ignore - } - } - if (type == null || type.trim().isEmpty()) { - type = "class"; - } + IRI iri = ioHelper.createIRI(id); + if (iri == null) { + iri = IRI.create(id); + } - IRI iri = ioHelper.createIRI(id); - if (iri == null) { - iri = IRI.create(id); - } + // Try to resolve a CURIE + IRI typeIRI = ioHelper.createIRI(type); - // Try to resolve a CURIE - IRI typeIRI = ioHelper.createIRI(type); + // Set to IRI string or to type string + String typeOrIRI = type; + if (typeIRI != null) { + typeOrIRI = typeIRI.toString(); + } - // Set to IRI string or to type string - String typeOrIRI = type; - if (typeIRI != null) { - typeOrIRI = typeIRI.toString(); - } + // Check against builtin types (ignore case), otherwise treat as individual + OWLEntity entity; + String lowerCaseType = typeOrIRI.toLowerCase(); + switch (lowerCaseType) { + case "": + case "http://www.w3.org/2002/07/owl#class": + case "class": + entity = dataFactory.getOWLEntity(EntityType.CLASS, iri); + break; - // Check against builtin types (ignore case), otherwise treat as individual - OWLEntity entity; - String lowerCaseType = typeOrIRI.toLowerCase(); - switch (lowerCaseType) { - case "": - case "http://www.w3.org/2002/07/owl#class": - case "class": - entity = dataFactory.getOWLEntity(EntityType.CLASS, iri); - break; + case "http://www.w3.org/2002/07/owl#objectproperty": + case "object property": + entity = dataFactory.getOWLEntity(EntityType.OBJECT_PROPERTY, iri); + break; - case "http://www.w3.org/2002/07/owl#objectproperty": - case "object property": - entity = dataFactory.getOWLEntity(EntityType.OBJECT_PROPERTY, iri); - break; + case "http://www.w3.org/2002/07/owl#dataproperty": + case "data property": + entity = dataFactory.getOWLEntity(EntityType.DATA_PROPERTY, iri); + break; - case "http://www.w3.org/2002/07/owl#dataproperty": - case "data property": - entity = dataFactory.getOWLEntity(EntityType.DATA_PROPERTY, iri); - break; + case "http://www.w3.org/2002/07/owl#annotationproperty": + case "annotation property": + entity = dataFactory.getOWLEntity(EntityType.ANNOTATION_PROPERTY, iri); + break; - case "http://www.w3.org/2002/07/owl#annotationproperty": - case "annotation property": - entity = dataFactory.getOWLEntity(EntityType.ANNOTATION_PROPERTY, iri); - break; + case "http://www.w3.org/2002/07/owl#datatype": + case "datatype": + entity = dataFactory.getOWLEntity(EntityType.DATATYPE, iri); + break; - case "http://www.w3.org/2002/07/owl#datatype": - case "datatype": - entity = dataFactory.getOWLEntity(EntityType.DATATYPE, iri); - break; + case "http://www.w3.org/2002/07/owl#individual": + case "individual": + case "http://www.w3.org/2002/07/owl#namedindividual": + case "named individual": + default: + // Assume type is an individual (checked later) + entity = dataFactory.getOWLEntity(EntityType.NAMED_INDIVIDUAL, iri); + break; + } - case "http://www.w3.org/2002/07/owl#individual": - case "individual": - case "http://www.w3.org/2002/07/owl#namedindividual": - case "named individual": - default: - // Assume type is an individual (checked later) - entity = dataFactory.getOWLEntity(EntityType.NAMED_INDIVIDUAL, iri); - break; - } + if (id != null) { + checker.add(entity, id); + } + if (label != null) { checker.add(entity, label); } } @@ -795,6 +796,8 @@ private void processRow(List row) throws Exception { case "http://www.w3.org/2002/07/owl#namedindividual": case "named individual": default: + // This is a bit unsafe imo, for example in the case of where the datatype turns out to be + // http://www.w3.org/2002/07/owl#DatatypeProperty"" addIndividualAxioms(iri, row); break; } diff --git a/robot-core/src/test/java/org/obolibrary/robot/TemplateTest.java b/robot-core/src/test/java/org/obolibrary/robot/TemplateTest.java index 8e39eca03..f283ea790 100644 --- a/robot-core/src/test/java/org/obolibrary/robot/TemplateTest.java +++ b/robot-core/src/test/java/org/obolibrary/robot/TemplateTest.java @@ -49,6 +49,35 @@ public void testLegacyTemplateCSV() throws Exception { assertIdentical("/template.owl", template); } + /** + * Test a strange case where a sequence . + * + * @throws Exception if entities cannot be found + */ + @Test + public void testNoLabelsNoTypes() throws Exception { + String path = "/sequence-template.csv"; + List> rows = TemplateHelper.readCSV(this.getClass().getResourceAsStream(path)); + Template t = new Template(path, rows); + t.generateOutputOntology("http://test.com/template.owl", false, null); + } + + /** + * Test a strange case where a sequence . + * + * @throws Exception if entities cannot be found + */ + @Test + public void testNoLabels() throws Exception { + String path = "/workflow-template.csv"; + List> rows = TemplateHelper.readCSV(this.getClass().getResourceAsStream(path)); + IOHelper ioHelper = new IOHelper(); + ioHelper.addPrefix("ex", "http://example.com/"); + Template t = new Template(path, rows, ioHelper); + OWLOntology template = t.generateOutputOntology("http://test.com/template.owl", false, null); + assertIdentical("/workflow-template.ttl", template); + } + /** * Test multiple templates. * diff --git a/robot-core/src/test/resources/sequence-template.csv b/robot-core/src/test/resources/sequence-template.csv new file mode 100644 index 000000000..d979b6ddb --- /dev/null +++ b/robot-core/src/test/resources/sequence-template.csv @@ -0,0 +1,4 @@ +ID,isa +ID,SC % +CL:4030028,CL:0000561 +CL:0000561,CL:0000099 diff --git a/robot-core/src/test/resources/workflow-template.csv b/robot-core/src/test/resources/workflow-template.csv new file mode 100644 index 000000000..f636bf563 --- /dev/null +++ b/robot-core/src/test/resources/workflow-template.csv @@ -0,0 +1,5 @@ +Identifier,Type,Class Restriction Lower,Class Restriction Upper,Domain,Range +ID,TYPE,SC %,SC %,DOMAIN,RANGE +ex:consists_of,owl:ObjectProperty,,,ex:Workflow,ex:Task +ex:Workflow,class,(ex:consists_of min 1 ex:Task),(ex:consists_of max 7 ex:Task),, +ex:Task,class,,,, diff --git a/robot-core/src/test/resources/workflow-template.ttl b/robot-core/src/test/resources/workflow-template.ttl new file mode 100644 index 000000000..3ab680dba --- /dev/null +++ b/robot-core/src/test/resources/workflow-template.ttl @@ -0,0 +1,42 @@ +@prefix owl: . +@prefix rdf: . +@prefix xml: . +@prefix xsd: . +@prefix rdfs: . +@base . + + rdf:type owl:Ontology . + +################################################################# +# Object Properties +################################################################# + +### http://example.com/consists_of + rdf:type owl:ObjectProperty ; + rdfs:domain ; + rdfs:range . + + +################################################################# +# Classes +################################################################# + +### http://example.com/Task + rdf:type owl:Class . + + +### http://example.com/Workflow + rdf:type owl:Class ; + rdfs:subClassOf [ rdf:type owl:Restriction ; + owl:onProperty ; + owl:minQualifiedCardinality "1"^^xsd:nonNegativeInteger ; + owl:onClass + ] , + [ rdf:type owl:Restriction ; + owl:onProperty ; + owl:maxQualifiedCardinality "7"^^xsd:nonNegativeInteger ; + owl:onClass + ] . + + +### Generated by the OWL API (version 4.5.26) https://github.com/owlcs/owlapi