Repository: ontodev/robot (java)

Issue:
Support SPLIT=\\n in template strings
SPLIT in template strings doesn't current support splitting on new lines.  I have a use case where I have multiple lines per cell in a CSV (as a list of synonyms for each term) and so I'd like to split on newlines.  Ideally I would like to allow SPLIT to support regexes but there isn't a clear, non-risky way of doing this without possibly breaking backwards compatibility.

After some discussion with @jamesaoverton on the OBO Slack channel we came to the conclusion that we would just explicitly support new line characters in SPLIT.

Note:  In the template string the slash character must be escaped so that it is preserved.  Thus `SPLIT=\\n` works but `SPLIT=\n` will not.

Pull request description:
Implemented #1245 (Support SPLIT=\\n)
Resolves #1245 

- [x] `docs/` have been added/updated
- [x] tests have been added/updated
- [x] `mvn verify` says all tests pass
- [x] `mvn site` says all JavaDocs correct
- [ ] `CHANGELOG.md` has been updated

As specified in #1245 this PR add the ability to split values in cells using a new line character

- Refactored code to use a single method call for splitting values
- Support splitting on new line characters
- Added tests for splitting values

Patch:
diff --git a/docs/template.md b/docs/template.md
index 30e72cf8..18abc4fe 100644
--- a/docs/template.md
+++ b/docs/template.md
@@ -66,7 +66,7 @@ If `--force true` is not included with `--errors <path>`, ROBOT will exit with a
     - `AI` annotation IRI: If the template string starts with an `AI` and a space, then the annotation will be made as with a string annotation, except that the cell value will be interpreted as an IRI.
 - `>A` (**axiom annotations**): ROBOT can also annotate logical and annotation axioms. The axiom annotation will be on the axiom created on the cell to the left of the `>A*` template string. The `>` symbol can be used in front of any valid annotation character (`>A`, `>AT`, `>AL`, `>AI`)
 
-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 separate 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=, `).
+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 separate 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=, `).  Note that ROBOT can split on new lines using `SPLIT=\\n` (a double slash is required).
 
 ### Class Template Strings
 
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 1c5e8efa..38d46605 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/TemplateHelper.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/TemplateHelper.java
@@ -204,7 +204,7 @@ public class TemplateHelper {
     } else if (template.startsWith("AI ") || template.startsWith("CI ")) {
       Set<OWLAnnotation> annotations = new HashSet<>();
       if (split != null) {
-        String[] values = value.split(Pattern.quote(split));
+        String[] values = getSplitValues(value, split);
         for (String v : values) {
           annotations.add(maybeGetIRIAnnotation(checker, template, v, column));
         }
@@ -220,6 +220,29 @@ public class TemplateHelper {
     }
   }
 
+  /**
+   * Splits the specified value using the specified delimiter.
+   *
+   * @param value The value to be split.
+   * @param delimiter The delimiter. This will be treated as is except in the case where the
+   *     delimiter is "\\n". In this case the value is split using a new line character.
+   * @return The split string.
+   */
+  protected static String[] getSplitValues(String value, String delimiter) {
+    if (value == null) {
+      return new String[0];
+    }
+    // Special case handling for new lines.  In a template string these are encoded as the string
+    // "\\n"
+    // for example SPLIT=\\n, because "\n" is parsed as 'n' by the CSV parser.
+    if (delimiter.trim().equals("\\n")) {
+      return value.split("\n");
+    } else {
+      String quotedPattern = Pattern.quote(delimiter);
+      return value.split(quotedPattern);
+    }
+  }
+
   /**
    * Given a Manchester Syntax parser, a template string, and a template value, insert the value
    * into the template string and parse to a set of class expressions. If the template string
@@ -278,7 +301,7 @@ public class TemplateHelper {
     if (template.startsWith("CI")) {
       // CI indicates its just an IRI
       if (split != null) {
-        String[] values = value.split(Pattern.quote(split));
+        String[] values = getSplitValues(value, split);
         for (String v : values) {
           String content = QuotedEntityChecker.wrap(v);
           expressions.add(tryParse(tableName, checker, parser, content, rowNum, col));
@@ -289,7 +312,7 @@ public class TemplateHelper {
       }
     } else {
       if (split != null) {
-        String[] values = value.split(Pattern.quote(split));
+        String[] values = getSplitValues(value, split);
         for (String v : values) {
           expressions.add(getClassExpression(tableName, template, v, checker, parser, rowNum, col));
         }
@@ -724,7 +747,7 @@ public class TemplateHelper {
 
     Set<OWLAnnotation> annotations = new HashSet<>();
     if (split != null) {
-      String[] values = value.split(Pattern.quote(split));
+      String[] values = getSplitValues(value, split);
       for (String v : values) {
         annotations.add(dataFactory.getOWLAnnotation(property, dataFactory.getOWLLiteral(v)));
       }
@@ -764,7 +787,7 @@ public class TemplateHelper {
 
     Set<OWLAnnotation> annotations = new HashSet<>();
     if (split != null) {
-      String[] values = value.split(Pattern.quote(split));
+      String[] values = getSplitValues(value, split);
       for (String v : values) {
         annotations.add(
             dataFactory.getOWLAnnotation(property, dataFactory.getOWLLiteral(v, datatype)));
@@ -1080,7 +1103,7 @@ public class TemplateHelper {
   private static List<String> getAllValues(String value, String split) {
     List<String> allValues = new ArrayList<>();
     if (split != null) {
-      String[] values = value.split(Pattern.quote(split));
+      String[] values = getSplitValues(value, split);
       for (String v : values) {
         allValues.add(v.trim());
       }
@@ -1694,7 +1717,7 @@ public class TemplateHelper {
 
     Set<OWLAnnotation> annotations = new HashSet<>();
     if (split != null) {
-      String[] values = value.split(Pattern.quote(split));
+      String[] values = getSplitValues(value, split);
       for (String v : values) {
         annotations.add(dataFactory.getOWLAnnotation(property, dataFactory.getOWLLiteral(v)));
       }


Test patch:
diff --git a/robot-core/src/test/java/org/obolibrary/robot/TemplateHelperTest.java b/robot-core/src/test/java/org/obolibrary/robot/TemplateHelperTest.java
index 5d97f63a..3325f231 100644
--- a/robot-core/src/test/java/org/obolibrary/robot/TemplateHelperTest.java
+++ b/robot-core/src/test/java/org/obolibrary/robot/TemplateHelperTest.java
@@ -1,7 +1,6 @@
 package org.obolibrary.robot;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
 
 import com.google.common.collect.Sets;
 import java.io.IOException;
@@ -282,4 +281,31 @@ public class TemplateHelperTest extends CoreTest {
         TemplateHelper.getIRIAnnotation(checker, "AI rdfs:label", IRI.create("http://bar.com"), 0);
     assertEquals("Annotation(rdfs:label <http://bar.com>)", ann.toString());
   }
+
+  @Test
+  public void testGetSplitValuesShouldSplitOnDelimiterCharacter() {
+    String[] values = TemplateHelper.getSplitValues("A,B,C", ",");
+    String[] expectedValues = {"A", "B", "C"};
+    assertArrayEquals(expectedValues, values);
+  }
+
+  @Test
+  public void testGetSplitValuesShouldSplitOnPipeCharacter() {
+    String[] values = TemplateHelper.getSplitValues("A|B|C", "|");
+    String[] expectedValues = {"A", "B", "C"};
+    assertArrayEquals(expectedValues, values);
+  }
+
+  @Test
+  public void testGetSplitValuesShouldSplitOnNewlineCharacterRepresentation() {
+    String[] values = TemplateHelper.getSplitValues("A\nB\nC", "\\n");
+    String[] expectedValues = {"A", "B", "C"};
+    assertArrayEquals(expectedValues, values);
+  }
+
+  @Test
+  public void testSplitEmptyString() {
+    String[] values = TemplateHelper.getSplitValues("", ",");
+    assertArrayEquals(new String[] {""}, values);
+  }
 }