Repository: ontodev/robot
Pull request #309 (2018-06-19 12:42:34)

Add SPLIT functionality for C template strings


Patch:
diff --git a/docs/template.md b/docs/template.md
index 80689e92..dcc945da 100644
--- a/docs/template.md
+++ b/docs/template.md
@@ -35,7 +35,7 @@ The `template` command accepts an optional input ontology, either using the `--i
     - `>A` annotation on annotation: Annotates the annotation axiom created from the cell to the left with the cell value. The column to the left must be an `A*` template string.
     - `>C` annotation on class expression: Annotates the class expression axiom created from the cell to the left with the cell value. The column to the left must be a `C` template string.
 
-Sometimes you want to include zero or more values in a single spreadsheet cell, for example when you want to allow for multiple annotations. If an annotation template string also contains `SPLIT=|`, then ROBOT will use the `|` character to split the contents of a cell in that column and add an annotation for each result (if there are any). Instead of `|` you can specify a string of non-whitespace characters of your choice to split on.
+Sometimes you want to include zero or more values in a single spreadsheet cell, for example when you want to allow for multiple annotations or have seperate logical axioms. If a template string also contains `SPLIT=|`, then ROBOT will use the `|` character to split the contents of a cell in that column and add an annotation for each result (if there are any). Instead of `|` you can specify a string of characters of your choice - other than pure whitespace - to split on (e.g. `SPLIT=, `).
 
 
 ## Merging
diff --git a/robot-core/src/main/java/org/obolibrary/robot/TemplateHelper.java b/robot-core/src/main/java/org/obolibrary/robot/TemplateHelper.java
index fa8dda2c..e5d71508 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/TemplateHelper.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/TemplateHelper.java
@@ -15,6 +15,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import org.apache.commons.io.FilenameUtils;
 import org.semanticweb.owlapi.model.IRI;
 import org.semanticweb.owlapi.model.OWLAnnotation;
@@ -78,6 +80,30 @@ public class TemplateHelper {
   /** OWL Namespace. */
   private static String owl = "http://www.w3.org/2002/07/owl#";
 
+  /**
+   * Given a set of rows, the row number, and the column number, get the content in the column for
+   * the row. If there are any issues, return an empty string. If the cell is empty, return null.
+   *
+   * @param rows list of rows (lists of strings)
+   * @param row row number to get ID of
+   * @param column column number
+   * @return content, null, or empty string.
+   */
+  public static String getCellContent(List<List<String>> rows, int row, Integer column) {
+    String id = null;
+    if (column != null && column != -1) {
+      try {
+        id = rows.get(row).get(column);
+      } catch (IndexOutOfBoundsException e) {
+        return "";
+      }
+      if (id == null || id.trim().isEmpty()) {
+        return "";
+      }
+    }
+    return id;
+  }
+
   /**
    * Create an OWLAnnotation based on the template string and cell value.
    *
@@ -548,4 +574,30 @@ public class TemplateHelper {
     csv.close();
     return rows;
   }
+
+  /**
+   * Given a template string, a cell value, and an empty list, fill the list with any number of
+   * values based on a SPLIT character, then return the template string without SPLIT. If there are
+   * no SPLITs, only add the original cell to the values.
+   *
+   * @param template template string
+   * @param cell cell contents
+   * @param values empty list to fill
+   * @return template string without SPLIT
+   */
+  public static String processSplit(String template, String cell, List<String> values) {
+    // If the template contains SPLIT=X,
+    // then split the cell value
+    // and remove that string from the template.
+    Pattern splitter = Pattern.compile("SPLIT=(\\S+)");
+    Matcher matcher = splitter.matcher(template);
+    if (matcher.find()) {
+      Pattern split = Pattern.compile(Pattern.quote(matcher.group(1)));
+      values.addAll(Arrays.asList(split.split(cell)));
+      template = matcher.replaceAll("").trim();
+    } else {
+      values.add(cell);
+    }
+    return template;
+  }
 }
diff --git a/robot-core/src/main/java/org/obolibrary/robot/TemplateOperation.java b/robot-core/src/main/java/org/obolibrary/robot/TemplateOperation.java
index b938ef7a..a9856308 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/TemplateOperation.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/TemplateOperation.java
@@ -1,15 +1,7 @@
 package org.obolibrary.robot;
 
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import java.util.*;
 import org.semanticweb.owlapi.apibinding.OWLManager;
 import org.semanticweb.owlapi.manchestersyntax.parser.ManchesterOWLSyntaxClassExpressionParser;
 import org.semanticweb.owlapi.manchestersyntax.renderer.ParserException;
@@ -390,19 +382,8 @@ public class TemplateOperation {
         continue;
       }
 
-      // If the template contains SPLIT=X,
-      // then split the cell value
-      // and remove that string from the template.
-      List<String> values = new ArrayList<String>();
-      Pattern splitter = Pattern.compile("SPLIT=(\\S+)");
-      Matcher matcher = splitter.matcher(template);
-      if (matcher.find()) {
-        Pattern split = Pattern.compile(Pattern.quote(matcher.group(1)));
-        values = new ArrayList<String>(Arrays.asList(split.split(cell)));
-        template = matcher.replaceAll("").trim();
-      } else {
-        values.add(cell);
-      }
+      List<String> values = new ArrayList<>();
+      template = TemplateHelper.processSplit(template, cell, values);
 
       // Create OWLAnnotation objects
       // Link any axiom annotations in a map
@@ -582,50 +563,54 @@ public class TemplateOperation {
       if (cell.trim().isEmpty()) {
         continue;
       }
-      String content = QuotedEntityChecker.wrap(cell);
-
-      if (template.equals("CLASS_TYPE")) {
-        classType = cell.trim().toLowerCase();
-      } else if (template.startsWith("C ")) {
-        String sub = template.substring(2).trim().replaceAll("%", content);
-        try {
-          lastExpression = parser.parse(sub);
-        } catch (ParserException e) {
-          throw new Exception(
-              String.format(
-                  parseError, tableName, row + 1, id, column + 1, header, sub, e.getMessage()),
-              e);
-        }
-        classExpressions.add(lastExpression);
-      } else if (template.startsWith("CI")) {
-        IRI iri = ioHelper.createIRI(cell);
-        lastExpression = dataFactory.getOWLClass(iri);
-        classExpressions.add(lastExpression);
-      } else if (template.startsWith(">C ")) {
-        if (lastExpression == null) {
-          throw new Exception(
-              String.format(
-                  axiomAnnotationError,
-                  tableName,
-                  row + 1,
-                  id,
-                  column + 1,
-                  template,
-                  cell,
-                  "a class expression"));
-        }
-        Set<OWLAnnotation> annotations;
-        if (annotatedExpressions.containsKey(lastExpression)) {
-          annotations = annotatedExpressions.get(lastExpression);
-        } else {
-          annotations = new HashSet<>();
-        }
-        annotations.add(
-            TemplateHelper.getStringAnnotation(checker, template.substring(1).trim(), cell));
-        annotatedExpressions.put(lastExpression, annotations);
-        // Remove to prevent duplication
-        if (classExpressions.contains(lastExpression)) {
-          classExpressions.remove(lastExpression);
+
+      List<String> values = new ArrayList<>();
+      template = TemplateHelper.processSplit(template, cell, values);
+
+      for (String value : values) {
+        String content = QuotedEntityChecker.wrap(value);
+        if (template.equals("CLASS_TYPE")) {
+          classType = cell.trim().toLowerCase();
+        } else if (template.startsWith("C ")) {
+          String sub = template.substring(2).trim().replaceAll("%", content);
+          try {
+            lastExpression = parser.parse(sub);
+          } catch (ParserException e) {
+            throw new Exception(
+                String.format(
+                    parseError, tableName, row + 1, id, column + 1, header, sub, e.getMessage()));
+          }
+          classExpressions.add(lastExpression);
+        } else if (template.startsWith("CI")) {
+          IRI iri = ioHelper.createIRI(cell);
+          lastExpression = dataFactory.getOWLClass(iri);
+          classExpressions.add(lastExpression);
+        } else if (template.startsWith(">C ")) {
+          if (lastExpression == null) {
+            throw new Exception(
+                String.format(
+                    axiomAnnotationError,
+                    tableName,
+                    row + 1,
+                    id,
+                    column + 1,
+                    template,
+                    cell,
+                    "a class expression"));
+          }
+          Set<OWLAnnotation> annotations;
+          if (annotatedExpressions.containsKey(lastExpression)) {
+            annotations = annotatedExpressions.get(lastExpression);
+          } else {
+            annotations = new HashSet<>();
+          }
+          annotations.add(
+              TemplateHelper.getStringAnnotation(checker, template.substring(1).trim(), cell));
+          annotatedExpressions.put(lastExpression, annotations);
+          // Remove to prevent duplication
+          if (classExpressions.contains(lastExpression)) {
+            classExpressions.remove(lastExpression);
+          }
         }
       }
     }


Test patch:
diff --git a/robot-core/src/test/java/org/obolibrary/robot/TemplateOperationTest.java b/robot-core/src/test/java/org/obolibrary/robot/TemplateOperationTest.java
index a2be5ec6..b8dbbb13 100644
--- a/robot-core/src/test/java/org/obolibrary/robot/TemplateOperationTest.java
+++ b/robot-core/src/test/java/org/obolibrary/robot/TemplateOperationTest.java
@@ -116,7 +116,7 @@ public class TemplateOperationTest extends CoreTest {
     OWLOntology simpleParts = loadOntology("/simple_parts.owl");
     OWLOntology template = TemplateOperation.template(tables, simpleParts);
     assertEquals("Count classes", 4, template.getClassesInSignature().size());
-    assertEquals("Count logical axioms", 3, template.getLogicalAxiomCount());
-    assertEquals("Count all axioms", 9, template.getAxiomCount());
+    assertEquals("Count logical axioms", 4, template.getLogicalAxiomCount());
+    assertEquals("Count all axioms", 10, template.getAxiomCount());
   }
 }
diff --git a/robot-core/src/test/resources/template-labels.csv b/robot-core/src/test/resources/template-labels.csv
index 081f301d..898e4849 100644
--- a/robot-core/src/test/resources/template-labels.csv
+++ b/robot-core/src/test/resources/template-labels.csv
@@ -1,4 +1,4 @@
 Label,Synonyms,Type,Parent,Parts
-LABEL,A IAO:0000118 SPLIT=|,CLASS_TYPE,C %,C part_of some %
-test 3,synonym 1|synonym 2,subclass,test2,test one
+LABEL,A IAO:0000118 SPLIT=|,CLASS_TYPE,C % SPLIT=|,C part_of some %
+test 3,synonym 1|synonym 2,subclass,test one|test2,test one
 test 4,,equivalent,test one,(test2 and 'test 3')