Repository: xlate/staedi
Pull request #200 (2021-07-03 02:41:15)

Fix for handling of alphabetic repetition separator
## Problem
Valid X12 ISA headers can sometimes include an alpha-numeric character at element 11. In that case, the value should be ignored in favor of the default repetition separator "^"

We've encountered real world messages that have a `V` at ISA 11 fail to parse with the following error message on later events. I'm Assuming that a `V` was encountered elsewhere in the segments and the parser assumed that it was a control character.

```
io.xlate.edi.internal.stream.tokenization.EDIException: EDIE003 - Invalid processing state; INVALID (previous: TAG_1); input: 'V' in segment AMT at position 25
	at io.xlate.edi.internal.stream.tokenization.Lexer.error(Lexer.java:432)
```

Besides the spec, there's supporting evidence for this here: https://www.ibm.com/support/pages/isa-11-repetition-separator-letter-u-and-u-character-data-it-does-not-fail-x12-deenvelope

## Fix
This change checks will `elementRepeater` to the default if the header value is alphabetic


Patch:
diff --git a/src/main/java/io/xlate/edi/internal/stream/tokenization/CharacterSet.java b/src/main/java/io/xlate/edi/internal/stream/tokenization/CharacterSet.java
index 4a1fb67..fb38bdb 100644
--- a/src/main/java/io/xlate/edi/internal/stream/tokenization/CharacterSet.java
+++ b/src/main/java/io/xlate/edi/internal/stream/tokenization/CharacterSet.java
@@ -236,6 +236,18 @@ public class CharacterSet {
         return getClass(character).equals(clazz);
     }
 
+    public boolean isValidDelimiter(int character) {
+        switch (getClass(character)) {
+        case CONTROL:
+            return !extraneousIgnored;
+        case WHITESPACE:
+        case OTHER:
+            return true;
+        default:
+            return false;
+        }
+    }
+
     public static boolean isValid(int character) {
         if (character >= prototype.length) {
             return true;
diff --git a/src/main/java/io/xlate/edi/internal/stream/tokenization/X12Dialect.java b/src/main/java/io/xlate/edi/internal/stream/tokenization/X12Dialect.java
index 66025aa..3fd46bd 100644
--- a/src/main/java/io/xlate/edi/internal/stream/tokenization/X12Dialect.java
+++ b/src/main/java/io/xlate/edi/internal/stream/tokenization/X12Dialect.java
@@ -94,7 +94,7 @@ public class X12Dialect extends Dialect {
 
         version = new String[] { new String(header, 84, 5) };
 
-        if (numericVersion() >= RELEASE_ELEMENT_I65) {
+        if (numericVersion() >= RELEASE_ELEMENT_I65 && characters.isValidDelimiter(elementRepeater)) {
             characters.setClass(elementRepeater, CharacterClass.ELEMENT_REPEATER);
         } else {
             /*


Test patch:
diff --git a/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java b/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java
index 86c043b..62597c3 100644
--- a/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java
@@ -355,6 +355,56 @@ class StaEDIStreamReaderTest implements ConstantsTest {
         assertEquals(1, delimiterExceptions, "Unexpected exceptions");
     }
 
+    @Test
+    void testGetDelimitersX12_WithISX_00501_Invalid_Repitition() throws EDIStreamException {
+        EDIInputFactory factory = EDIInputFactory.newFactory();
+        ByteArrayInputStream stream = new ByteArrayInputStream((""
+                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *200711*0100*V*00501*000000001*0*T*:~"
+                + "ISX*\\~"
+                + "GS*FA*ReceiverDept*SenderDept*20200711*010015*1*X*005010~"
+                + "ST*997*0001*005010X230~"
+                + "SE*2*0001~"
+                + "GE*1*1~"
+                + "IEA*1*000000001~").getBytes());
+
+        EDIStreamReader reader = factory.createEDIStreamReader(stream);
+        Map<String, Character> expected = new HashMap<>(5);
+        expected.put(Delimiters.SEGMENT, '~');
+        expected.put(Delimiters.DATA_ELEMENT, '*');
+        expected.put(Delimiters.COMPONENT_ELEMENT, ':');
+        expected.put(Delimiters.DECIMAL, '.');
+
+        Map<String, Character> delimiters = null;
+        int delimiterExceptions = 0;
+        List<EDIStreamValidationError> errors = new ArrayList<>();
+
+        while (reader.hasNext()) {
+            try {
+                reader.getDelimiters();
+            } catch (IllegalStateException e) {
+                delimiterExceptions++;
+            }
+
+            switch (reader.next()) {
+            case START_GROUP:
+                delimiters = reader.getDelimiters();
+                break;
+            case SEGMENT_ERROR:
+            case ELEMENT_OCCURRENCE_ERROR:
+            case ELEMENT_DATA_ERROR:
+                errors.add(reader.getErrorType());
+                break;
+            default:
+                break;
+            }
+        }
+
+        assertEquals(expected, delimiters, "Unexpected delimiters");
+        assertArrayEquals(new EDIStreamValidationError[] { EDIStreamValidationError.UNEXPECTED_SEGMENT },
+                          errors.toArray(new EDIStreamValidationError[errors.size()]));
+        assertEquals(1, delimiterExceptions, "Unexpected exceptions");
+    }
+
     @Test
     void testGetDelimitersX12_WithISX_00704() throws EDIStreamException {
         EDIInputFactory factory = EDIInputFactory.newFactory();