Repository: ontodev/robot
Pull request #656 (2020-03-23 18:21:42)

Change OBO provider to CURIE provider
- [x] `docs/` have been added/updated
- [x] tests have been added/updated
- [x] `mvn verify` says all tests pass
- [ ] `mvn site` says all JavaDocs correct
- [ ] `CHANGELOG.md` has been updated

Fix provider used for short form IDs in `export` to match the IRI to longest namespace possible. If no match is found in prefixes, return the full IRI and log an error.



Patch:
diff --git a/docs/export.md b/docs/export.md
index 997a1b0f..e616285f 100644
--- a/docs/export.md
+++ b/docs/export.md
@@ -46,7 +46,7 @@ Various `--header` types are supported:
 
 * **Special Headers**:
 	* `IRI`: creates an "IRI" column based on the full unique identifier
-	* `ID`: creates an "ID" column based on the short form of the unique identifier (CURIE)
+	* `ID`: creates an "ID" column based on the short form of the unique identifier (CURIE) - please note that all IRIs must have [defined prefixes](global/prefixes), or the full IRI will be returned.
 	* `LABEL`: creates a "Label" column based on `rdfs:label`
 	* `SYNONYMS`: creates a "SYNONYMS" column based on all synonyms (oboInOwl exact, broad, narrow, related, or IAO alternative term)
 	* `SubClass Of`: creates a "SubClass Of" column based on `rdfs:subClassOf`
@@ -95,7 +95,8 @@ By default, the export includes details on the classes and individuals in an ont
 
 For example, to return the details of *individuals only*:
 
-    robot export --input template.owl \
+    robot --prefix "example: http://example.com/" \
+      export --input template.owl \
       --header "ID|LABEL|Type" \
       --include "individuals" \
       --export results/individuals.csv
diff --git a/robot-core/src/main/java/org/obolibrary/robot/ExportOperation.java b/robot-core/src/main/java/org/obolibrary/robot/ExportOperation.java
index e870a090..44a05e85 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/ExportOperation.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/ExportOperation.java
@@ -106,7 +106,7 @@ public class ExportOperation {
     String entityFormat = OptionsHelper.getOption(options, "entity-format", "NAME");
 
     // Create some providers for rendering entities
-    ShortFormProvider oboProvider = new OBOShortFormProvider(ioHelper.getPrefixes());
+    ShortFormProvider oboProvider = new CURIEShortFormProvider(ioHelper.getPrefixes());
     ShortFormProvider iriProvider = new IRIValueShortFormProvider();
     ShortFormProvider quotedProvider =
         new QuotedAnnotationValueShortFormProvider(
diff --git a/robot-core/src/main/java/org/obolibrary/robot/providers/CURIEShortFormProvider.java b/robot-core/src/main/java/org/obolibrary/robot/providers/CURIEShortFormProvider.java
new file mode 100644
index 00000000..590e21e4
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/providers/CURIEShortFormProvider.java
@@ -0,0 +1,61 @@
+package org.obolibrary.robot.providers;
+
+import java.util.*;
+import javax.annotation.Nonnull;
+import org.semanticweb.owlapi.model.OWLEntity;
+import org.semanticweb.owlapi.util.ShortFormProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * ShortFormProvider to return CURIEs. Similar to QNameShortFormProvider.
+ *
+ * @author <a href="mailto:rbca.jackson@gmail.com">Becky Jackson</a>
+ */
+public class CURIEShortFormProvider implements ShortFormProvider {
+  /** Logger. */
+  private static final Logger logger = LoggerFactory.getLogger(CURIEShortFormProvider.class);
+
+  private List<Map.Entry<String, String>> sortedPrefixMap;
+
+  /**
+   * Init a new CURIEShortFormProvider
+   *
+   * @param prefix2Ns Map of prefixes to namespaces
+   */
+  public CURIEShortFormProvider(Map<String, String> prefix2Ns) {
+    // Create a list from elements of HashMap
+    sortedPrefixMap = new LinkedList<>(prefix2Ns.entrySet());
+
+    // Sort the list, placing longest values (ns) first
+    sortedPrefixMap.sort(
+        Collections.reverseOrder(Comparator.comparingInt(o -> o.getValue().length())));
+  }
+
+  /**
+   * Get the short form as a CURIE.
+   *
+   * @param entity OWLEntity to get short form of
+   * @return CURIE
+   */
+  @Override
+  @Nonnull
+  public String getShortForm(@Nonnull OWLEntity entity) {
+    String iri = entity.getIRI().toString();
+    // Find the first (longest) match from sorted prefix/ns entries
+    for (Map.Entry<String, String> prefix2Ns : sortedPrefixMap) {
+      String prefix = prefix2Ns.getKey() + ":";
+      String ns = prefix2Ns.getValue();
+      if (iri.startsWith(ns)) {
+        return iri.replace(ns, prefix);
+      }
+    }
+    // Could not match, just return full IRI
+    logger.error("Unable to find namespace for: " + iri);
+    return iri;
+  }
+
+  /** Dispose of the ShortFormProvider */
+  @Override
+  public void dispose() {}
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/providers/OBOShortFormProvider.java b/robot-core/src/main/java/org/obolibrary/robot/providers/OBOShortFormProvider.java
deleted file mode 100644
index bfa02f19..00000000
--- a/robot-core/src/main/java/org/obolibrary/robot/providers/OBOShortFormProvider.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package org.obolibrary.robot.providers;
-
-import java.util.Arrays;
-import java.util.Map;
-import javax.annotation.Nonnull;
-import org.semanticweb.owlapi.model.OWLEntity;
-import org.semanticweb.owlapi.util.NamespaceUtil;
-import org.semanticweb.owlapi.util.ShortFormProvider;
-
-/**
- * ShortFormProvider to return CURIEs with support for OBO Namespaces. Similar to
- * QNameShortFormProvider.
- *
- * @author <a href="mailto:rbca.jackson@gmail.com">Becky Jackson</a>
- */
-public class OBOShortFormProvider implements ShortFormProvider {
-
-  private final NamespaceUtil namespaceUtil = new NamespaceUtil();
-
-  /**
-   * Init a new OBOShortFormProvider
-   *
-   * @param prefix2Ns Map of prefixes to namespaces
-   */
-  public OBOShortFormProvider(Map<String, String> prefix2Ns) {
-    prefix2Ns.forEach((key, v) -> namespaceUtil.setPrefix(v, key));
-  }
-
-  /**
-   * Get the short form as a CURIE.
-   *
-   * @param entity OWLEntity to get short form of
-   * @return CURIE
-   */
-  @Override
-  @Nonnull
-  public String getShortForm(@Nonnull OWLEntity entity) {
-    // First check if this is an OBO prefix
-    String strIRI = entity.getIRI().toString();
-    if (strIRI.startsWith("http://purl.obolibrary.org/obo/")) {
-      // If so, just split some strings to get the namespace
-      String shortForm = strIRI.replace("http://purl.obolibrary.org/obo/", "");
-      String[] split = shortForm.split("_");
-      String prefix;
-      if (split.length > 2) {
-        prefix = String.join("_", Arrays.copyOf(split, split.length - 1));
-      } else {
-        prefix = split[0];
-      }
-      String local = split[split.length - 1];
-      return prefix + ":" + local;
-    } else {
-      // Otherwise, use the QNameShortFormProvider basic method to get short form
-      String namespace = entity.getIRI().getNamespace();
-      String prefix = namespaceUtil.getPrefix(namespace);
-      return entity.getIRI().prefixedBy(prefix + ':');
-    }
-  }
-
-  /** Dispose of the ShortFormProvider */
-  @Override
-  public void dispose() {}
-}


Test patch:
diff --git a/robot-core/src/test/java/org/obolibrary/robot/CURIEShortFormProviderTest.java b/robot-core/src/test/java/org/obolibrary/robot/CURIEShortFormProviderTest.java
new file mode 100644
index 00000000..0821b2f7
--- /dev/null
+++ b/robot-core/src/test/java/org/obolibrary/robot/CURIEShortFormProviderTest.java
@@ -0,0 +1,34 @@
+package org.obolibrary.robot;
+
+import java.io.IOException;
+import org.junit.Test;
+import org.obolibrary.robot.providers.CURIEShortFormProvider;
+import org.semanticweb.owlapi.apibinding.OWLManager;
+import org.semanticweb.owlapi.model.IRI;
+import org.semanticweb.owlapi.model.OWLClass;
+import org.semanticweb.owlapi.model.OWLDataFactory;
+
+public class CURIEShortFormProviderTest extends CoreTest {
+
+  @Test
+  public void testProvider() throws IOException {
+    IOHelper ioHelper = new IOHelper();
+    CURIEShortFormProvider sfp = new CURIEShortFormProvider(ioHelper.getPrefixes());
+    OWLDataFactory df = OWLManager.getOWLDataFactory();
+
+    // Short form 1 should return GO (longer than OBO namespace)
+    OWLClass c1 = df.getOWLClass(IRI.create("http://purl.obolibrary.org/obo/GO_123"));
+    String sf1 = sfp.getShortForm(c1);
+    assert sf1.equals("GO:123");
+
+    // Short form 2 will default to base OBO since UNKNOWN is not registered
+    OWLClass c2 = df.getOWLClass(IRI.create("http://purl.obolibrary.org/obo/UNKNOWN_123"));
+    String sf2 = sfp.getShortForm(c2);
+    assert sf2.equals("obo:UNKNOWN_123");
+
+    // The example NS is not in prefixes so full IRI will be returned
+    OWLClass c3 = df.getOWLClass(IRI.create("http://example.com/UNKNOWN_456"));
+    String sf3 = sfp.getShortForm(c3);
+    assert sf3.equals("http://example.com/UNKNOWN_456");
+  }
+}
diff --git a/robot-core/src/test/java/org/obolibrary/robot/ExportOperationTest.java b/robot-core/src/test/java/org/obolibrary/robot/ExportOperationTest.java
index a4c42442..b394e458 100644
--- a/robot-core/src/test/java/org/obolibrary/robot/ExportOperationTest.java
+++ b/robot-core/src/test/java/org/obolibrary/robot/ExportOperationTest.java
@@ -26,6 +26,8 @@ public class ExportOperationTest extends CoreTest {
   public void testExportToXLSX() throws Exception {
     OWLOntology ontology = loadOntology("/simple.owl");
     IOHelper ioHelper = new IOHelper();
+    ioHelper.addPrefix(
+        "simple", "https://github.com/ontodev/robot/robot-core/src/test/resources/simple.owl#");
     List<String> columns = Arrays.asList("ID", "SubClass Of [ID]");
     Map<String, String> options = ExportOperation.getDefaultOptions();