Skip to content

Commit

Permalink
Merge pull request #783 from ontodev/782-fix
Browse files Browse the repository at this point in the history
Fix template handling of full IRIs
  • Loading branch information
jamesaoverton authored Dec 10, 2020
2 parents 9a779ed + 0868693 commit f2b0de7
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 28 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed
- Fix blank node subjects in [`report`] in [#767]
- Fixed IRI handling for [`template`] in [#783]

## [1.7.1] - 2020-10-22

Expand Down Expand Up @@ -219,6 +220,7 @@ First official release of ROBOT!
[`template`]: http://robot.obolibrary.org/template
[`validate`]: http://robot.obolibrary.org/validate

[#783]: https://github.com/ontodev/robot/pull/783
[#767]: https://github.com/ontodev/robot/pull/767
[#758]: https://github.com/ontodev/robot/pull/758
[#739]: https://github.com/ontodev/robot/pull/739
Expand Down
14 changes: 14 additions & 0 deletions docs/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ One of the required files (often `--input`) could not be found. This is often ca

When specifying the `--output` (or `--format` for converting), make sure the file has a valid extension. See [convert](/convert) for a current list of ontology formats.

### Invalid Element Error

This error occurs when ROBOT tries to convert an IRI to an XML element name for writing but encounters an illegal character. Common illegal characters include `/` and `:`. This error usually occurs when creating new ontology terms with [`template`](/template). See [Namespaces in XML](https://www.w3.org/TR/REC-xml-names/) for full details on legal XML element names.

The solution is usually to add a new [prefix](/global#prefixes) so that the illegal character is no longer part of the element name. For example, the prefix `ex` for `http://example.com/` is valid, and `http://example.com/foo/bar` is a valid IRI, but `ex:foo/bar` is not a valid element name. By defining a new prefix `foo` for `http://example.com/foo/` we can now use `foo:bar` as a valid element name for the same IRI `http://example.com/foo/bar`.

### Invalid Ontology File Error

ROBOT was expecting an ontology file, and the file exists, but is not in a recognized format. Adding the `-vvv` option will print a stack trace that shows how the underlying OWLAPI library tried to parse the ontology file. This will include details and line numbers that can help isolate the problem.
Expand Down Expand Up @@ -117,6 +123,14 @@ If a prefix is incorrectly formatted, or if the prefix target does not point to
robot -p "robot: http://purl.obolibrary.org/robot/"
```

### Undefined Prefix Error

This error usually occurs when running [`template`](/template). If you use a CURIE in one of the ROBOT template strings as a property (e.g., `A ex:0000115`) but do not define the prefix of that CURIE, ROBOT will be unable to save the ontology file.

To resolve this, make sure all CURIEs use prefixes that are defined. ROBOT includes a set of [default prefixes](https://github.com/ontodev/robot/blob/master/robot-core/src/main/resources/obo_context.jsonld), but you can also define your own prefixes. To include a custom prefix, see [prefixes](/global#prefixes).

When rendering the output, only properties are validated for [QNames](https://en.wikipedia.org/wiki/QName). OWLAPI will allow undefined prefixes to be used in subjects and objects, but the IRI will be the unexpanded version of the CURIE (i.e., the IRI will just be `ex:0000115`).

### Unknown Arg Error

This error message may appear for one of two common reasons:
Expand Down
88 changes: 73 additions & 15 deletions robot-core/src/main/java/org/obolibrary/robot/IOHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.obolibrary.oboformat.writer.OBOFormatWriter;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.formats.*;
import org.semanticweb.owlapi.io.XMLUtils;
import org.semanticweb.owlapi.model.*;
import org.semanticweb.owlapi.rdf.rdfxml.renderer.IllegalElementNameException;
import org.semanticweb.owlapi.rdf.rdfxml.renderer.XMLWriterPreferences;
Expand Down Expand Up @@ -64,6 +65,10 @@ public class IOHelper {
/** Error message when an invalid extension is provided (file format). Expects the file format. */
static final String invalidFormatError = NS + "INVALID FORMAT ERROR unknown format: %s";

/** Error message when writing output fails due to bad element name. */
private static final String invalidElementError =
NS + "INVALID ELEMENT ERROR \"%s\" contains invalid characters";

/** Error message when the specified file cannot be loaded. Expects the file name. */
private static final String invalidOntologyFileError =
NS + "INVALID ONTOLOGY FILE ERROR Could not load a valid ontology from file: %s";
Expand Down Expand Up @@ -108,6 +113,10 @@ public class IOHelper {
+ "SYNTAX ERROR unable to load '%s' with Jena - "
+ "check that this file is in RDF/XML or TTL syntax and try again.";

/** Error message when an invalid prefix is provided. Expects the combined prefix. */
static final String undefinedPrefixError =
NS + "UNDEFINED PREFIX ERROR \"%s\" has unknown prefix; make sure prefix \"%s\" is defined";

/** Optional base namespaces. */
private Set<String> baseNamespaces = new HashSet<>();

Expand Down Expand Up @@ -775,22 +784,10 @@ public static String cellToA1(int rowNum, int colNum) {
* Given a term string, use the current prefixes to create an IRI.
*
* @param term the term to convert to an IRI
* @return the new IRI
*/
@SuppressWarnings("unchecked")
public IRI createIRI(String term) {
return createIRI(term, false);
}

/**
* Given a term string, use the current prefixes to create an IRI.
*
* @param term the term to convert to an IRI
* @param qName if true, check that the expanded IRI is a valid QName (if not, return null)
* @return the new IRI or null
*/
@SuppressWarnings("unchecked")
public IRI createIRI(String term, boolean qName) {
public IRI createIRI(String term) {
if (term == null) {
return null;
}
Expand All @@ -817,7 +814,20 @@ public IRI createIRI(String term, boolean qName) {
logger.warn(e.getMessage());
return null;
}
return iri;
}

/**
* Given a term string, use the current prefixes to create an IRI.
*
* @deprecated replaced by {@link #createIRI(String)}
* @param term the term to convert to an IRI
* @param qName if true, validate that the IRI expands to a QName
* @return the new IRI or null
*/
@Deprecated
public IRI createIRI(String term, boolean qName) {
IRI iri = createIRI(term);
// Check that this is a valid QName
if (qName && !iri.getRemainder().isPresent()) {
return null;
Expand Down Expand Up @@ -1198,6 +1208,46 @@ public void saveContext(File file) throws IOException {
writer.close();
}

/**
* Determine if a string is a CURIE. Note that a valid CURIE is not always a valid QName. Adapted
* from:
*
* @see org.semanticweb.owlapi.io.XMLUtils#isQName(CharSequence)
* @param s Character sequence to check
* @return true if valid CURIE
*/
public static boolean isValidCURIE(CharSequence s) {
if (s == null || 0 >= s.length()) {
// string is null or empty
return false;
}
boolean inLocal = false;
for (int i = 0; i < s.length(); ) {
int codePoint = Character.codePointAt(s, i);
if (codePoint == ':') {
if (inLocal) {
// Second colon - illegal
return false;
}
inLocal = true;
} else {
if (!inLocal) {
// Check for valid NS characters
if (!XMLUtils.isXMLNameStartCharacter(codePoint)) {
return false;
}
} else {
// Check for valid local characters
if (!XMLUtils.isXMLNameChar(codePoint)) {
return false;
}
}
}
i += Character.charCount(codePoint);
}
return true;
}

/**
* Read comma-separated values from a path to a list of lists of strings.
*
Expand Down Expand Up @@ -1511,15 +1561,23 @@ private void saveOntologyFile(
// use native save functionality
try {
ontology.getOWLOntologyManager().saveOntology(ontology, format, ontologyIRI);
} catch (IllegalElementNameException e) {
throw new IOException("ELEMENT NAME EXCEPTION " + e.getCause().getMessage());
} catch (OWLOntologyStorageException e) {
// Determine if its caused by an OBO Format error
if (format instanceof OBODocumentFormat
&& e.getCause() instanceof FrameStructureException) {
throw new IOException(
String.format(oboStructureError, e.getCause().getMessage()), e.getCause());
}
if (e.getCause() instanceof IllegalElementNameException) {
IllegalElementNameException e2 = (IllegalElementNameException) e.getCause();
String element = e2.getElementName();
if (isValidCURIE(element)) {
String prefix = element.split(":")[0];
throw new IOException(String.format(undefinedPrefixError, e2.getElementName(), prefix));
} else {
throw new IOException(String.format(invalidElementError, element));
}
}
throw new IOException(String.format(ontologyStorageError, ontologyIRI.toString()), e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ private IRI getIRI(Map<String, IRI> map, String name) {
public IRI getIRI(String name, boolean create) {
IRI iri = iris.getOrDefault(name, null);
if (iri == null && ioHelper != null && create) {
iri = ioHelper.createIRI(name, true);
iri = ioHelper.createIRI(name);
}
return iri;
}
Expand Down Expand Up @@ -337,7 +337,7 @@ public OWLAnnotationProperty getOWLAnnotationProperty(@Nonnull String name, bool
return dataFactory.getOWLAnnotationProperty(iri);
}
if (create && ioHelper != null) {
iri = ioHelper.createIRI(name, true);
iri = ioHelper.createIRI(name);
if (iri != null) {
return dataFactory.getOWLAnnotationProperty(iri);
}
Expand All @@ -358,7 +358,7 @@ public OWLClass getOWLClass(@Nonnull String name) {
return dataFactory.getOWLClass(iri);
}
if (ioHelper != null) {
iri = ioHelper.createIRI(name, true);
iri = ioHelper.createIRI(name);
if (iri != null) {
return dataFactory.getOWLClass(iri);
}
Expand All @@ -379,7 +379,7 @@ public OWLDataProperty getOWLDataProperty(@Nonnull String name) {
return dataFactory.getOWLDataProperty(iri);
}
if (ioHelper != null) {
iri = ioHelper.createIRI(name, true);
iri = ioHelper.createIRI(name);
if (iri != null) {
return dataFactory.getOWLDataProperty(iri);
}
Expand Down Expand Up @@ -413,7 +413,7 @@ public OWLDatatype getOWLDatatype(@Nonnull String name, boolean create) {
return dataFactory.getOWLDatatype(iri);
}
if (create && ioHelper != null) {
iri = ioHelper.createIRI(name, true);
iri = ioHelper.createIRI(name);
if (iri != null) {
return dataFactory.getOWLDatatype(iri);
}
Expand All @@ -434,7 +434,7 @@ public OWLNamedIndividual getOWLIndividual(@Nonnull String name) {
return dataFactory.getOWLNamedIndividual(iri);
}
if (ioHelper != null) {
iri = ioHelper.createIRI(name, true);
iri = ioHelper.createIRI(name);
if (iri != null) {
return dataFactory.getOWLNamedIndividual(iri);
}
Expand All @@ -455,7 +455,7 @@ public OWLObjectProperty getOWLObjectProperty(@Nonnull String name) {
return dataFactory.getOWLObjectProperty(iri);
}
if (ioHelper != null) {
iri = ioHelper.createIRI(name, true);
iri = ioHelper.createIRI(name);
if (iri != null) {
return dataFactory.getOWLObjectProperty(iri);
}
Expand Down
10 changes: 5 additions & 5 deletions robot-core/src/main/java/org/obolibrary/robot/Template.java
Original file line number Diff line number Diff line change
Expand Up @@ -595,13 +595,13 @@ private void addLabels() {
type = "class";
}

IRI iri = ioHelper.createIRI(id, true);
IRI iri = ioHelper.createIRI(id);
if (iri == null) {
iri = IRI.create(id);
}

// Try to resolve a CURIE
IRI typeIRI = ioHelper.createIRI(type, true);
IRI typeIRI = ioHelper.createIRI(type);

// Set to IRI string or to type string
String typeOrIRI = type;
Expand Down Expand Up @@ -747,7 +747,7 @@ private void processRow(List<String> row) throws Exception {
}

// Try to resolve a CURIE
IRI typeIRI = ioHelper.createIRI(type, true);
IRI typeIRI = ioHelper.createIRI(type);

// Set to IRI string or to type string
String typeOrIRI = type;
Expand Down Expand Up @@ -1826,7 +1826,7 @@ private void addIndividualAxioms(IRI iri, List<String> row) throws Exception {
type = type.trim();

// Try to resolve a CURIE
IRI typeIRI = ioHelper.createIRI(type, true);
IRI typeIRI = ioHelper.createIRI(type);

// Set to IRI string or to type string
String typeOrIRI = type;
Expand Down Expand Up @@ -2217,7 +2217,7 @@ private IRI getIRI(String id, String label) throws Exception {
throw new Exception("You must specify either an ID or a label");
}
if (id != null) {
return ioHelper.createIRI(id, true);
return ioHelper.createIRI(id);
}
return checker.getIRI(label, true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ public static List<IRI> getIRIs(String tableName, List<List<String>> rows, IOHel
if (id == null || id.trim().isEmpty()) {
continue;
}
IRI iri = ioHelper.createIRI(id, true);
IRI iri = ioHelper.createIRI(id);
if (iri == null) {
continue;
}
Expand Down

0 comments on commit f2b0de7

Please sign in to comment.