From 0f14029b530f088f631d27177abe7ab0e1083b5b Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Sun, 26 Oct 2025 20:54:43 +0000 Subject: [PATCH 1/4] Add --input-format option. Add a common `--input-format` option which accepts the same values as the `--output-format` option, but indicates the expected format of the input file (as loaded by the `CommandLineHelper.updateInputOntology` method). --- .../obolibrary/robot/CommandLineHelper.java | 18 ++- .../java/org/obolibrary/robot/IOHelper.java | 103 ++++++++++++++++-- 2 files changed, 100 insertions(+), 21 deletions(-) diff --git a/robot-command/src/main/java/org/obolibrary/robot/CommandLineHelper.java b/robot-command/src/main/java/org/obolibrary/robot/CommandLineHelper.java index 823528e0e..224a4b8d7 100644 --- a/robot-command/src/main/java/org/obolibrary/robot/CommandLineHelper.java +++ b/robot-command/src/main/java/org/obolibrary/robot/CommandLineHelper.java @@ -470,6 +470,7 @@ public static OWLOntology getInputOntology( throws IllegalArgumentException, IOException { List inputOntologyPaths = getOptionalValues(line, "input"); List inputOntologyIRIs = getOptionalValues(line, "input-iri"); + String inputFormat = getOptionalValue(line, "input-format"); int check = inputOntologyPaths.size() + inputOntologyIRIs.size(); if (check > 1) { @@ -477,17 +478,9 @@ public static OWLOntology getInputOntology( } if (!inputOntologyPaths.isEmpty()) { - if (catalogPath != null) { - return ioHelper.loadOntology(inputOntologyPaths.get(0), catalogPath); - } else { - return ioHelper.loadOntology(inputOntologyPaths.get(0)); - } + return ioHelper.loadOntology(inputOntologyPaths.get(0), catalogPath, inputFormat); } else if (!inputOntologyIRIs.isEmpty()) { - if (catalogPath != null) { - return ioHelper.loadOntology(IRI.create(inputOntologyIRIs.get(0)), catalogPath); - } else { - return ioHelper.loadOntology(IRI.create(inputOntologyIRIs.get(0))); - } + return ioHelper.loadOntology(IRI.create(inputOntologyIRIs.get(0)), catalogPath, inputFormat); } else { // Both input options are empty throw new IllegalArgumentException(missingInputError); @@ -913,6 +906,11 @@ public static Options getCommonOptions() { o.addOption("v", "verbose", false, "increased logging"); o.addOption("vv", "very-verbose", false, "high logging"); o.addOption("vvv", "very-very-verbose", false, "maximum logging, including stack traces"); + o.addOption( + null, + "input-format", + true, + "format of the input ontology: obo, owl, ttl, owx, omn, ofn, json"); o.addOption(null, "catalog", true, "use catalog from provided file"); o.addOption("p", "prefix", true, "add a prefix 'foo: http://bar'"); o.addOption("P", "prefixes", true, "use prefixes from JSON-LD file"); diff --git a/robot-core/src/main/java/org/obolibrary/robot/IOHelper.java b/robot-core/src/main/java/org/obolibrary/robot/IOHelper.java index bfbd2fb1e..44f251149 100644 --- a/robot-core/src/main/java/org/obolibrary/robot/IOHelper.java +++ b/robot-core/src/main/java/org/obolibrary/robot/IOHelper.java @@ -339,6 +339,22 @@ public OWLOntology loadOntology(String ontologyPath, String catalogPath) throws return loadOntology(ontologyFile, catalogFile); } + /** + * Load an ontology from a String path, with optional catalog file and explicit document format. + * + * @param ontologyPath the path to the ontology file + * @param catalogPath the path to the catalog file or null + * @param inputFormat the expected input format of the ontology or null + * @return a new ontology object, with a new OWLManager + * @throws IOException on any problem + */ + public OWLOntology loadOntology(String ontologyPath, String catalogPath, String inputFormat) + throws IOException { + File ontologyFile = new File(ontologyPath); + File catalogFile = catalogPath != null ? new File(catalogPath) : null; + return loadOntology(ontologyFile, catalogFile, inputFormat); + } + /** * Load an ontology from a File, using a catalog file if available. * @@ -380,6 +396,20 @@ public OWLOntology loadOntology(File ontologyFile, boolean useCatalog) throws IO * @throws IOException on any problem */ public OWLOntology loadOntology(File ontologyFile, File catalogFile) throws IOException { + return loadOntology(ontologyFile, catalogFile, null); + } + + /** + * Load an ontology from a File, with optional catalog File and input format. + * + * @param ontologyFile the ontology file to load + * @param catalogFile the catalog file to use + * @param inputFormat the expected format of the ontology or null + * @return a new ontology object, with a new OWLManager + * @throws IOException on any problem + */ + public OWLOntology loadOntology(File ontologyFile, File catalogFile, String inputFormat) + throws IOException { logger.debug("Loading ontology {} with catalog file {}", ontologyFile, catalogFile); Object jsonObject = null; OWLOntologyManager manager = OWLManager.createOWLOntologyManager(); @@ -417,14 +447,15 @@ public OWLOntology loadOntology(File ontologyFile, File catalogFile) throws IOEx // Maybe unzip if (ontologyFile.getPath().endsWith(".gz")) { if (catalogFile == null) { - return loadCompressedOntology(ontologyFile, null); + return loadCompressedOntology(ontologyFile, null, inputFormat); } else { - return loadCompressedOntology(ontologyFile, catalogFile.getAbsolutePath()); + return loadCompressedOntology(ontologyFile, catalogFile.getAbsolutePath(), inputFormat); } } // Otherwise load from file using default method - return loadOntology(manager, new FileDocumentSource(ontologyFile)); + OWLDocumentFormat fmt = inputFormat != null ? getFormat(inputFormat) : null; + return loadOntology(manager, new FileDocumentSource(ontologyFile, fmt)); } catch (JsonLdError | OWLOntologyCreationException e) { throw new IOException(String.format(invalidOntologyFileError, ontologyFile.getName()), e); } @@ -442,7 +473,7 @@ public OWLOntology loadOntology(InputStream ontologyStream) throws IOException { } /** - * Load an ontology from an InputStream with a catalog file. + * Load an ontology from an InputStream with an optional catalog file. * * @param ontologyStream the ontology stream to load * @param catalogPath the catalog file to use or null @@ -451,6 +482,20 @@ public OWLOntology loadOntology(InputStream ontologyStream) throws IOException { */ public OWLOntology loadOntology(InputStream ontologyStream, String catalogPath) throws IOException { + return loadOntology(ontologyStream, catalogPath, null); + } + + /** + * Load an ontology from an InputStream with an optional catalog file and input format. + * + * @param ontologyStream the ontology stream to load + * @param catalogPath the catalog file to use or null + * @param inputFormat the expected format of the ontology or null + * @return a new ontology object, with a new OWLManager + * @throws IOException on any problem + */ + public OWLOntology loadOntology( + InputStream ontologyStream, String catalogPath, String inputFormat) throws IOException { OWLOntology ontology; // Maybe load a catalog file File catalogFile = null; @@ -465,7 +510,18 @@ public OWLOntology loadOntology(InputStream ontologyStream, String catalogPath) if (catalogFile != null) { manager.setIRIMappers(Sets.newHashSet(new CatalogXmlIRIMapper(catalogFile))); } - ontology = loadOntology(manager, new StreamDocumentSource(ontologyStream)); + OWLOntologyDocumentSource source = null; + if (inputFormat != null) { + source = + new StreamDocumentSource( + ontologyStream, + StreamDocumentSource.getNextDocumentIRI("inputstream:ontology"), + getFormat(inputFormat), + null); + } else { + source = new StreamDocumentSource(ontologyStream); + } + ontology = loadOntology(manager, source); } catch (OWLOntologyCreationException e) { throw new IOException(invalidOntologyStreamError, e); } @@ -492,6 +548,20 @@ public OWLOntology loadOntology(IRI ontologyIRI) throws IOException { * @throws IOException on any problem */ public OWLOntology loadOntology(IRI ontologyIRI, String catalogPath) throws IOException { + return loadOntology(ontologyIRI, catalogPath, null); + } + + /** + * Load an ontology from an IRI with an optional catalog file and input format. + * + * @param ontologyIRI the ontology IRI to load + * @param catalogPath the catalog file to use or null + * @param inputFormat the expected format of the ontology or null + * @return a new ontology object, with a new OWLManager + * @throws IOException on any problem + */ + public OWLOntology loadOntology(IRI ontologyIRI, String catalogPath, String inputFormat) + throws IOException { OWLOntology ontology; // Maybe load a catalog file File catalogFile = null; @@ -509,11 +579,18 @@ public OWLOntology loadOntology(IRI ontologyIRI, String catalogPath) throws IOEx } // Maybe load a zipped ontology if (ontologyIRI.toString().endsWith(".gz")) { - ontology = loadCompressedOntology(new URL(ontologyIRI.toString()), catalogPath); + ontology = + loadCompressedOntology(new URL(ontologyIRI.toString()), catalogPath, inputFormat); } else { // Otherwise load ontology as normal IRI documentIRI = getDocumentIRIFromMappers(manager, ontologyIRI); - ontology = loadOntology(manager, new IRIDocumentSource(documentIRI)); + OWLOntologyDocumentSource source = null; + if (inputFormat != null) { + source = new IRIDocumentSource(documentIRI, getFormat(inputFormat), null); + } else { + source = new IRIDocumentSource(documentIRI); + } + ontology = loadOntology(manager, source); } } catch (OWLOntologyCreationException e) { throw new IOException(e); @@ -1724,13 +1801,15 @@ private static Set getUndeclaredPredicates( * * @param gzipFile compressed File to load ontology from * @param catalogPath the path to the catalog file or null + * @param inputFormat the expected format of the ontology or null * @return a new ontology object with a new OWLManager * @throws IOException on any problem */ - private OWLOntology loadCompressedOntology(File gzipFile, String catalogPath) throws IOException { + private OWLOntology loadCompressedOntology(File gzipFile, String catalogPath, String inputFormat) + throws IOException { FileInputStream fis = new FileInputStream(gzipFile); GZIPInputStream gis = new GZIPInputStream(fis); - return loadOntology(gis, catalogPath); + return loadOntology(gis, catalogPath, inputFormat); } /** @@ -1739,10 +1818,12 @@ private OWLOntology loadCompressedOntology(File gzipFile, String catalogPath) th * * @param url URL to load from * @param catalogPath the path to the catalog file or null + * @param inputFormat the expected format of the ontology or null * @return a new ontology object with a new OWLManager * @throws IOException on any problem */ - private OWLOntology loadCompressedOntology(URL url, String catalogPath) throws IOException { + private OWLOntology loadCompressedOntology(URL url, String catalogPath, String inputFormat) + throws IOException { // Check for redirects url = followRedirects(url); @@ -1754,7 +1835,7 @@ private OWLOntology loadCompressedOntology(URL url, String catalogPath) throws I throw new IOException(String.format(invalidOntologyIRIError, url)); } GZIPInputStream gis = new GZIPInputStream(is); - return loadOntology(gis, catalogPath); + return loadOntology(gis, catalogPath, inputFormat); } /** From 30d0717ec060835379755d16cc8fdee9ca79608c Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Sun, 26 Oct 2025 20:55:17 +0000 Subject: [PATCH 2/4] Allow the use of `--input-option` for multiple inputs. Update the `CommandLineHelper.getInputOntologies()` methods so that they too use the `--input-format` option if it is present in the command line. --- .../obolibrary/robot/CommandLineHelper.java | 15 ++++---- .../java/org/obolibrary/robot/IOHelper.java | 34 +++++++++++++++++-- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/robot-command/src/main/java/org/obolibrary/robot/CommandLineHelper.java b/robot-command/src/main/java/org/obolibrary/robot/CommandLineHelper.java index 224a4b8d7..1197eea0f 100644 --- a/robot-command/src/main/java/org/obolibrary/robot/CommandLineHelper.java +++ b/robot-command/src/main/java/org/obolibrary/robot/CommandLineHelper.java @@ -1071,21 +1071,22 @@ private static File[] getFilesByPattern(String pattern) throws IllegalArgumentEx public static List getInputOntologies(IOHelper ioHelper, CommandLine line) throws IllegalArgumentException, IOException { List inputOntologies = new ArrayList<>(); + String inputFormat = getOptionalValue(line, "input-format"); // Check for input files List inputOntologyPaths = getOptionalValues(line, "input"); for (String inputOntologyPath : inputOntologyPaths) { - inputOntologies.add(ioHelper.loadOntology(inputOntologyPath)); + inputOntologies.add(ioHelper.loadOntology(inputOntologyPath, true, inputFormat)); } // Check for input IRIs List inputOntologyIRIs = getOptionalValues(line, "input-iri"); for (String inputOntologyIRI : inputOntologyIRIs) { - inputOntologies.add(ioHelper.loadOntology(IRI.create(inputOntologyIRI))); + inputOntologies.add(ioHelper.loadOntology(IRI.create(inputOntologyIRI), null, inputFormat)); } // Check for input patterns (wildcard) String pattern = getOptionalValue(line, "inputs"); if (pattern != null) { for (File inputOntologyFile : getFilesByPattern(pattern)) { - inputOntologies.add(ioHelper.loadOntology(inputOntologyFile)); + inputOntologies.add(ioHelper.loadOntology(inputOntologyFile, true, inputFormat)); } } return inputOntologies; @@ -1104,22 +1105,24 @@ public static List getInputOntologies(IOHelper ioHelper, CommandLin public static List getInputOntologies( IOHelper ioHelper, CommandLine line, String catalogPath) throws IOException { List inputOntologies = new ArrayList<>(); + String inputFormat = getOptionalValue(line, "input-format"); // Check for input files List inputOntologyPaths = getOptionalValues(line, "input"); for (String inputOntologyPath : inputOntologyPaths) { - inputOntologies.add(ioHelper.loadOntology(inputOntologyPath, catalogPath)); + inputOntologies.add(ioHelper.loadOntology(inputOntologyPath, catalogPath, inputFormat)); } // Check for input IRIs List inputOntologyIRIs = getOptionalValues(line, "input-iri"); for (String inputOntologyIRI : inputOntologyIRIs) { - inputOntologies.add(ioHelper.loadOntology(IRI.create(inputOntologyIRI), catalogPath)); + inputOntologies.add( + ioHelper.loadOntology(IRI.create(inputOntologyIRI), catalogPath, inputFormat)); } // Check for input patterns (wildcard) String pattern = getOptionalValue(line, "inputs"); if (pattern != null) { File catalogFile = new File(catalogPath); for (File inputOntologyFile : getFilesByPattern(pattern)) { - inputOntologies.add(ioHelper.loadOntology(inputOntologyFile, catalogFile)); + inputOntologies.add(ioHelper.loadOntology(inputOntologyFile, catalogFile, inputFormat)); } } return inputOntologies; diff --git a/robot-core/src/main/java/org/obolibrary/robot/IOHelper.java b/robot-core/src/main/java/org/obolibrary/robot/IOHelper.java index 44f251149..606a908d5 100644 --- a/robot-core/src/main/java/org/obolibrary/robot/IOHelper.java +++ b/robot-core/src/main/java/org/obolibrary/robot/IOHelper.java @@ -317,12 +317,27 @@ public OWLOntology loadOntology(String ontologyPath) throws IOException { * @throws IOException on any problem */ public OWLOntology loadOntology(String ontologyPath, boolean useCatalog) throws IOException { + return loadOntology(ontologyPath, useCatalog, null); + } + + /** + * Load an ontology from a String path, optionally using a catalog file if available and with an + * explicit input format. + * + * @param ontologyPath the path to the ontology file + * @param useCatalog if true, a catalog file will be used if one is found + * @param inputFormat the expected format of the ontology or null + * @return a new ontology object, with a new OWLManager + * @throws IOException on any problem + */ + public OWLOntology loadOntology(String ontologyPath, boolean useCatalog, String inputFormat) + throws IOException { File ontologyFile = new File(ontologyPath); File catalogFile = null; if (useCatalog) { catalogFile = guessCatalogFile(ontologyFile); } - return loadOntology(ontologyFile, catalogFile); + return loadOntology(new File(ontologyPath), catalogFile, inputFormat); } /** @@ -380,11 +395,26 @@ public OWLOntology loadOntology(File ontologyFile) throws IOException { * @throws IOException on any problem */ public OWLOntology loadOntology(File ontologyFile, boolean useCatalog) throws IOException { + return loadOntology(ontologyFile, useCatalog, null); + } + + /** + * Load an ontology from a File, with option to use a catalog file and to expect an explicit + * format. + * + * @param ontologyFile the ontology file to load + * @param useCatalog when true, a catalog file will be used if one is found + * @param inputFormat the expected format of the ontology or null + * @return a new ontology object, with a new OWLManager + * @throws IOException on any problem + */ + public OWLOntology loadOntology(File ontologyFile, boolean useCatalog, String inputFormat) + throws IOException { File catalogFile = null; if (useCatalog) { catalogFile = guessCatalogFile(ontologyFile); } - return loadOntology(ontologyFile, catalogFile); + return loadOntology(ontologyFile, catalogFile, inputFormat); } /** From dad21e9b7fc6e6c3b4b03505dd4ebfca42e94d79 Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Sun, 26 Oct 2025 20:32:00 +0000 Subject: [PATCH 3/4] Add docs and test for `--input-format`. Mention the `--input-format` option in the global documentation. Add a basic test in which we attempt to load a OFN-formatted file that imports a OWL file, first with the correct format, then with an incorrect format. --- CHANGELOG.md | 3 +++ docs/global.md | 16 +++++++++++++ .../org/obolibrary/robot/IOHelperTest.java | 23 +++++++++++++++++++ robot-core/src/test/resources/import_test.ofn | 14 +++++++++++ 4 files changed, 56 insertions(+) create mode 100644 robot-core/src/test/resources/import_test.ofn diff --git a/CHANGELOG.md b/CHANGELOG.md index e8f393e5f..f1e8f29dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Add `--input-format` global option [#1038] + ## [1.9.8] - 2025-05-15 ### Added diff --git a/docs/global.md b/docs/global.md index daa05964c..203cc7bc6 100644 --- a/docs/global.md +++ b/docs/global.md @@ -91,6 +91,22 @@ For example, you may want to [`merge`](/merge) a set of edited import ontologies If a catalog file is specified and cannot be located, the ontology will be loaded without a catalog file. Similarly, if you do not provide a `--catalog` and the `catalog-v001.xml` file does not exist in your working directory, the ontology will be loaded without a catalog file. Finally, if the catalog specifies an import file that does not exist, the command will fail. +## Input format + +When loading an ontology, the OWLAPI will normally attempt to parse it using all the available parsers, until it finds one that can successfully load the ontology. If the format of the ontology is known, it can be explicitly specified to ROBOT using the `--input-format` option, so that only the appropriate parsers for that format will be used. + +The `--input-format` option accepts the following values: + +| Option value | Ontology format | +| ------------ | --------------------------------------------------------------------------------- | +| owl | [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/) | +| obo | [OBO Flat File](https://owlcollab.github.io/oboformat/doc/GO.format.obo-1_4.html) | +| owx | [OWL/XML](https://www.w3.org/TR/owl-xmlsyntax/) | +| ofn | [OWL Functional](https://www.w3.org/TR/owl2-syntax/) | +| omn | [OWL Manchester](https://www.w3.org/TR/owl2-manchester-syntax/) | +| ttl | [Turtle](https://www.w3.org/TR/turtle/) | +| json | [OBO Graphs JSON](https://github.com/geneontology/obographs/) | + ## Logging ROBOT logs a variety of messages that are typically hidden from the user. When something goes wrong, a detailed exception message is thrown. If the exception message does not provide enough details, you can run the command again with the `-vvv` (very-very-verbose) flag to see the stack trace. diff --git a/robot-core/src/test/java/org/obolibrary/robot/IOHelperTest.java b/robot-core/src/test/java/org/obolibrary/robot/IOHelperTest.java index 8ecf95131..640163e3a 100644 --- a/robot-core/src/test/java/org/obolibrary/robot/IOHelperTest.java +++ b/robot-core/src/test/java/org/obolibrary/robot/IOHelperTest.java @@ -391,4 +391,27 @@ public void testNonStrict() throws IOException { ioHelper.loadOntology(inputStream); assert true; } + + /** + * Test loading an ontology with an explicitly specified expected input format. + * + * @throws IOException on error creating IOHelper + */ + @Test + public void testExplicitInputFormat() throws IOException { + IOHelper ioHelper = new IOHelper(); + + // We should be able to load a OFN file that imports a OWL file + ioHelper.loadOntology("src/test/resources/import_test.ofn", true, "ofn"); + assert true; + + // But trying to load that same file while expecting it to be in OWL should fail + boolean error = false; + try { + ioHelper.loadOntology("src/test/resources/import_test.ofn", true, "owl"); + } catch (IOException ioe) { + error = true; + } + assert error; + } } diff --git a/robot-core/src/test/resources/import_test.ofn b/robot-core/src/test/resources/import_test.ofn new file mode 100644 index 000000000..aab4cddef --- /dev/null +++ b/robot-core/src/test/resources/import_test.ofn @@ -0,0 +1,14 @@ +Prefix(:=) +Prefix(dc:=) +Prefix(owl:=) +Prefix(rdf:=) +Prefix(xml:=) +Prefix(xsd:=) +Prefix(foaf:=) +Prefix(rdfs:=) + + +Ontology( +Import() + +) \ No newline at end of file From 2265558873ff631840ca3546a68a2d866aef7847 Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Tue, 28 Oct 2025 22:05:57 +0000 Subject: [PATCH 4/4] Fix duplicated call to File() constructor. --- robot-core/src/main/java/org/obolibrary/robot/IOHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robot-core/src/main/java/org/obolibrary/robot/IOHelper.java b/robot-core/src/main/java/org/obolibrary/robot/IOHelper.java index 606a908d5..218d5891b 100644 --- a/robot-core/src/main/java/org/obolibrary/robot/IOHelper.java +++ b/robot-core/src/main/java/org/obolibrary/robot/IOHelper.java @@ -337,7 +337,7 @@ public OWLOntology loadOntology(String ontologyPath, boolean useCatalog, String if (useCatalog) { catalogFile = guessCatalogFile(ontologyFile); } - return loadOntology(new File(ontologyPath), catalogFile, inputFormat); + return loadOntology(ontologyFile, catalogFile, inputFormat); } /**