Repository: ontodev/robot
Pull request #363 (2018-09-18 13:27:32)

Add labels option to diff
See #361


Patch:
diff --git a/docs/diff.md b/docs/diff.md
index ccfcaa6d..0a050240 100644
--- a/docs/diff.md
+++ b/docs/diff.md
@@ -22,6 +22,8 @@ If `--output` is provided then a report will be written with any differences bet
 
 See <a href="/examples/release-diff.txt" target="_blank">`release-diff.txt`</a> for an example. In the output, 'Ontology 1' corresponds to your `--left` input and 'Ontology 2' corresponds to your `--right` input.
 
+The output is in OWL Manchester syntax, but you can include entity labels with `--labels true`.
+
 You can also compare ontologies by IRI with `--left-iri` and `--right-iri`. You may want to compare a local file to a release, in which case:
 <!-- DO NOT TEST -->
 ```
diff --git a/robot-command/src/main/java/org/obolibrary/robot/DiffCommand.java b/robot-command/src/main/java/org/obolibrary/robot/DiffCommand.java
index 12a13471..c5383484 100644
--- a/robot-command/src/main/java/org/obolibrary/robot/DiffCommand.java
+++ b/robot-command/src/main/java/org/obolibrary/robot/DiffCommand.java
@@ -4,6 +4,8 @@ import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.Options;
 import org.semanticweb.owlapi.model.IRI;
@@ -45,6 +47,7 @@ public class DiffCommand implements Command {
     o.addOption("r", "right", true, "load right ontology from file");
     o.addOption("R", "right-iri", true, "load right ontology from IRI");
     o.addOption("o", "output", true, "save results to file");
+    o.addOption(null, "labels", true, "if true, use labels in place of entity IRIs");
     options = o;
   }
 
@@ -145,7 +148,10 @@ public class DiffCommand implements Command {
       writer = new PrintWriter(System.out);
     }
 
-    DiffOperation.compare(leftOntology, rightOntology, writer);
+    Map<String, String> options = new HashMap<>();
+    options.put("labels", CommandLineHelper.getDefaultValue(line, "labels", "false"));
+
+    DiffOperation.compare(leftOntology, rightOntology, ioHelper, writer, options);
     writer.flush();
     writer.close();
 
diff --git a/robot-core/src/main/java/org/obolibrary/robot/DiffOperation.java b/robot-core/src/main/java/org/obolibrary/robot/DiffOperation.java
index ba33559d..424cbbdf 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/DiffOperation.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/DiffOperation.java
@@ -2,16 +2,11 @@ package org.obolibrary.robot;
 
 import java.io.IOException;
 import java.io.Writer;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
+import java.util.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import org.semanticweb.owlapi.model.IRI;
-import org.semanticweb.owlapi.model.OWLAnnotation;
-import org.semanticweb.owlapi.model.OWLAxiom;
-import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.model.*;
+import org.semanticweb.owlapi.util.DefaultPrefixManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -30,6 +25,17 @@ public class DiffOperation {
   /** A pattern for matching IRI strings. */
   private static Pattern iriPattern = Pattern.compile("<(http\\S+)>");
 
+  /**
+   * Return default diff options.
+   *
+   * @return diff options
+   */
+  public static Map<String, String> getDefaultOptions() {
+    Map<String, String> options = new HashMap<>();
+    options.put("labels", "false");
+    return options;
+  }
+
   /**
    * Given two ontologies, compare their sets of axiom strings, returning true if they are identical
    * and false otherwise.
@@ -58,9 +64,33 @@ public class DiffOperation {
    */
   public static boolean compare(OWLOntology ontology1, OWLOntology ontology2, Writer writer)
       throws IOException {
+    return compare(ontology1, ontology2, new IOHelper(), writer, getDefaultOptions());
+  }
 
-    // Map<IRI, String> labels = OntologyHelper.getLabels(ontology1);
-    // labels.putAll(OntologyHelper.getLabels(ontology2));
+  /**
+   * Given two ontologies, a Writer, and a set of diff options, get the differences between axiom
+   * strings and write then to the writer. The ontologies are not changed.
+   *
+   * @param ontology1 the first ontology
+   * @param ontology2 the second ontology
+   * @param ioHelper IOHelper to use for prefixes
+   * @param writer the Writer for the report, or null
+   * @param options diff options
+   * @return true if the ontologies are the same, false otherwise
+   * @throws IOException on writer failure
+   */
+  public static boolean compare(
+      OWLOntology ontology1,
+      OWLOntology ontology2,
+      IOHelper ioHelper,
+      Writer writer,
+      Map<String, String> options)
+      throws IOException {
+
+    boolean useLabels = OptionsHelper.optionIsTrue(options, "labels");
+
+    Map<IRI, String> labels = OntologyHelper.getLabels(ontology1);
+    labels.putAll(OntologyHelper.getLabels(ontology2));
 
     Set<String> strings1 = getAxiomStrings(ontology1);
     Set<String> strings2 = getAxiomStrings(ontology2);
@@ -85,7 +115,11 @@ public class DiffOperation {
     writer.write(strings1minus2.size() + " axioms in Ontology 1 but not in Ontology 2:\n");
     sorted = new TreeSet<>();
     for (String axiom : strings1minus2) {
-      sorted.add("- " + axiom + "\n");
+      if (useLabels) {
+        sorted.add("- " + addLabels(ioHelper, labels, axiom) + "\n");
+      } else {
+        sorted.add("- " + axiom + "\n");
+      }
     }
     for (String axiom : sorted) {
       writer.write(axiom);
@@ -96,7 +130,11 @@ public class DiffOperation {
     writer.write(strings2minus1.size() + " axioms in Ontology 2 but not in Ontology 1:\n");
     sorted = new TreeSet<>();
     for (String axiom : strings2minus1) {
-      sorted.add("+ " + axiom + "\n");
+      if (useLabels) {
+        sorted.add("+ " + addLabels(ioHelper, labels, axiom) + "\n");
+      } else {
+        sorted.add("+ " + axiom + "\n");
+      }
     }
     for (String axiom : sorted) {
       writer.write(axiom);
@@ -120,9 +158,9 @@ public class DiffOperation {
       IRI iri = IRI.create(matcher.group(1));
       String id = iri.toString();
       if (id.startsWith(oboBase)) {
-        id = id.substring(oboBase.length());
+        id = id.substring(oboBase.length()).replace("_", ":");
       }
-      String replacement = "<" + iri + ">";
+      String replacement = "<" + id + ">";
       if (labels.containsKey(iri)) {
         replacement = "<" + id + ">[" + labels.get(iri) + "]";
       }
@@ -132,6 +170,38 @@ public class DiffOperation {
     return sb.toString();
   }
 
+  /**
+   * Given an IOHelper to get short form of IRIs, a map from IRIs to labels, and an axiom string,
+   * add labels next to any IRIs in the string, shorten OBO IRIs, and return the updated string.
+   *
+   * @param ioHelper IOHelper to use for prefixes
+   * @param labels a map from IRIs to label strings
+   * @param axiom a string representation of an OWLAxiom
+   * @return a string with labels inserted next to CURIEs
+   */
+  public static String addLabels(IOHelper ioHelper, Map<IRI, String> labels, String axiom) {
+    DefaultPrefixManager pm = ioHelper.getPrefixManager();
+    Matcher matcher = iriPattern.matcher(axiom);
+    StringBuffer sb = new StringBuffer();
+    while (matcher.find()) {
+      IRI iri = IRI.create(matcher.group(1));
+      String id = pm.getShortForm(iri);
+      if (id.startsWith("obo:")) {
+        id = id.substring(4).replace("_", ":");
+      }
+      if (!id.startsWith("<") && !id.endsWith(">")) {
+        id = "<" + id + ">";
+      }
+      String replacement = id;
+      if (labels.containsKey(iri)) {
+        replacement = id + "[" + labels.get(iri) + "]";
+      }
+      matcher.appendReplacement(sb, replacement);
+    }
+    matcher.appendTail(sb);
+    return sb.toString();
+  }
+
   /**
    * Given an ontology, return a list of strings for all the axioms of that ontology.
    *


Test patch:
diff --git a/robot-core/src/test/java/org/obolibrary/robot/DiffOperationTest.java b/robot-core/src/test/java/org/obolibrary/robot/DiffOperationTest.java
index cb31bf9b..68cca997 100644
--- a/robot-core/src/test/java/org/obolibrary/robot/DiffOperationTest.java
+++ b/robot-core/src/test/java/org/obolibrary/robot/DiffOperationTest.java
@@ -5,7 +5,9 @@ import static org.junit.Assert.assertFalse;
 
 import java.io.IOException;
 import java.io.StringWriter;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 import org.apache.commons.io.IOUtils;
 import org.junit.Test;
@@ -54,4 +56,25 @@ public class DiffOperationTest extends CoreTest {
     String expected = IOUtils.toString(this.getClass().getResourceAsStream("/simple1.diff"));
     assertEquals(expected, writer.toString());
   }
+
+  /**
+   * Compare one ontology to a modified copy with labels in output.
+   *
+   * @throws IOException on file problem
+   * @throws OWLOntologyCreationException on ontology problem
+   */
+  @Test
+  public void testCompareModifiedWithLabels() throws IOException, OWLOntologyCreationException {
+    OWLOntology simple = loadOntology("/simple.owl");
+    OWLOntology elk = loadOntology("/simple_elk.owl");
+
+    StringWriter writer = new StringWriter();
+    Map<String, String> options = new HashMap<>();
+    options.put("labels", "true");
+    boolean actual = DiffOperation.compare(simple, elk, new IOHelper(), writer, options);
+    System.out.println(writer.toString());
+    assertFalse(actual);
+    String expected = IOUtils.toString(this.getClass().getResourceAsStream("/simple.diff"));
+    assertEquals(expected, writer.toString());
+  }
 }
diff --git a/robot-core/src/test/resources/simple.diff b/robot-core/src/test/resources/simple.diff
new file mode 100644
index 00000000..47242fa8
--- /dev/null
+++ b/robot-core/src/test/resources/simple.diff
@@ -0,0 +1,4 @@
+0 axioms in Ontology 1 but not in Ontology 2:
+
+1 axioms in Ontology 2 but not in Ontology 1:
++ SubClassOf(<https://github.com/ontodev/robot/robot-core/src/test/resources/simple.owl#test1>[Test 1] owl:Thing)