Repository: xlate/staedi (java)

Issue:
Support X12 ISX Segment Release Character

X12 TR4 (Clarification Paper)

> The ISX segment first appeared in version/release 007040 of the X12 EDI Standard, with ISX01 and ISX02. ISX03 and ISX04 were introduced in version/release 008010. The I11 value in the ISA segment determines the version of the ISX which may be used in the interchange. 

The elements features in ISX are listed below. This issue will address the first element.
1. Release Character - ISX01
2. Character Encoding - ISX02
3. Overriding X12 Version / Release Code - ISX03
4. Industry Identifier - ISX04

Pull request:
ISX release character support, X12 schema 007040 and above
Fixes #51 

Patch:
diff --git a/src/main/java/io/xlate/edi/internal/stream/tokenization/Dialect.java b/src/main/java/io/xlate/edi/internal/stream/tokenization/Dialect.java
index c0b09ac..af206f5 100644
--- a/src/main/java/io/xlate/edi/internal/stream/tokenization/Dialect.java
+++ b/src/main/java/io/xlate/edi/internal/stream/tokenization/Dialect.java
@@ -17,35 +17,54 @@ package io.xlate.edi.internal.stream.tokenization;
 
 import io.xlate.edi.stream.Location;
 
-public interface Dialect {
+public abstract class Dialect {
 
-    String getStandard();
+    protected char segmentDelimiter;
+    protected char elementDelimiter;
+    protected char decimalMark;
+    protected char releaseIndicator;
+    protected char componentDelimiter;
+    protected char elementRepeater;
 
-    String[] getVersion();
+    public char getComponentElementSeparator() {
+        return componentDelimiter;
+    }
 
-    void setHeaderTag(String tag);
+    public char getDataElementSeparator() {
+        return elementDelimiter;
+    }
 
-    String getHeaderTag();
+    public char getDecimalMark() {
+        return decimalMark;
+    }
 
-    boolean isConfirmed();
+    public char getReleaseIndicator() {
+        return releaseIndicator;
+    }
 
-    boolean isRejected();
+    public char getRepetitionSeparator() {
+        return elementRepeater;
+    }
 
-    boolean isServiceAdviceSegment(String tag);
+    public char getSegmentTerminator() {
+        return segmentDelimiter;
+    }
 
-    boolean appendHeader(CharacterSet characters, char value);
+    public abstract String getStandard();
 
-    char getSegmentTerminator();
+    public abstract String[] getVersion();
 
-    char getDataElementSeparator();
+    public abstract void setHeaderTag(String tag);
 
-    char getComponentElementSeparator();
+    public abstract String getHeaderTag();
 
-    char getRepetitionSeparator();
+    public abstract boolean isConfirmed();
 
-    char getReleaseIndicator();
+    public abstract boolean isRejected();
 
-    char getDecimalMark();
+    public abstract boolean isServiceAdviceSegment(String tag);
+
+    public abstract boolean appendHeader(CharacterSet characters, char value);
 
     /**
      * Notify the dialect of element data and its location in the stream. Does
@@ -56,17 +75,17 @@ public interface Dialect {
      * @param location
      *            the location of the element
      */
-    void elementData(CharSequence data, Location location);
+    public abstract void elementData(CharSequence data, Location location);
 
     /**
      * Notify the dialect that a transaction is complete.
      */
-    void transactionEnd();
+    public abstract void transactionEnd();
 
     /**
      * Notify the dialect that a group is complete.
      */
-    void groupEnd();
+    public abstract void groupEnd();
 
     /**
      * Returns the identifying elements of the current transaction's version.
@@ -74,7 +93,7 @@ public interface Dialect {
      * @return the array of elements identifying the current transaction's
      *         version
      */
-    String[] getTransactionVersion();
+    public abstract String[] getTransactionVersion();
 
     /**
      * Returns the identifying elements of the current transaction's version as
@@ -83,5 +102,5 @@ public interface Dialect {
      * @return the String representation of the elements identifying the current
      *         transaction's version
      */
-    String getTransactionVersionString();
+    public abstract String getTransactionVersionString();
 }
diff --git a/src/main/java/io/xlate/edi/internal/stream/tokenization/EDIFACTDialect.java b/src/main/java/io/xlate/edi/internal/stream/tokenization/EDIFACTDialect.java
index 0a962cc..8681db7 100644
--- a/src/main/java/io/xlate/edi/internal/stream/tokenization/EDIFACTDialect.java
+++ b/src/main/java/io/xlate/edi/internal/stream/tokenization/EDIFACTDialect.java
@@ -18,7 +18,7 @@ package io.xlate.edi.internal.stream.tokenization;
 import io.xlate.edi.stream.Location;
 import io.xlate.edi.stream.EDIStreamConstants.Standards;
 
-public class EDIFACTDialect implements Dialect {
+public class EDIFACTDialect extends Dialect {
 
     public static final String UNA = "UNA";
     public static final String UNB = "UNB";
@@ -26,13 +26,6 @@ public class EDIFACTDialect implements Dialect {
     private static final String[] EMPTY = new String[0];
     private static final int EDIFACT_UNA_LENGTH = 9;
 
-    private char componentDelimiter = ':';
-    private char elementDelimiter = '+';
-    private char decimalMark = '.';
-    private char releaseIndicator = '?';
-    private char elementRepeater = '*';
-    private char segmentDelimiter = '\'';
-
     private String headerTag;
     private String[] version;
     StringBuilder header;
@@ -49,6 +42,13 @@ public class EDIFACTDialect implements Dialect {
     private String transactionVersionString;
 
     EDIFACTDialect() {
+        componentDelimiter = ':';
+        elementDelimiter = '+';
+        decimalMark = '.';
+        releaseIndicator = '?';
+        elementRepeater = '*';
+        segmentDelimiter = '\'';
+
         clearTransactionVersion();
     }
 
@@ -258,36 +258,6 @@ public class EDIFACTDialect implements Dialect {
         transactionVersionString = transactionVersion != null ? String.join(".", transactionVersion) : "";
     }
 
-    @Override
-    public char getComponentElementSeparator() {
-        return componentDelimiter;
-    }
-
-    @Override
-    public char getDataElementSeparator() {
-        return elementDelimiter;
-    }
-
-    @Override
-    public char getDecimalMark() {
-        return decimalMark;
-    }
-
-    @Override
-    public char getReleaseIndicator() {
-        return releaseIndicator;
-    }
-
-    @Override
-    public char getRepetitionSeparator() {
-        return elementRepeater;
-    }
-
-    @Override
-    public char getSegmentTerminator() {
-        return segmentDelimiter;
-    }
-
     @Override
     public void elementData(CharSequence data, Location location) {
         if ("UNH".equals(location.getSegmentTag())) {
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 ce12b16..df4b4fd 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
@@ -18,9 +18,16 @@ package io.xlate.edi.internal.stream.tokenization;
 import io.xlate.edi.stream.EDIStreamConstants.Standards;
 import io.xlate.edi.stream.Location;
 
-public class X12Dialect implements Dialect {
+public class X12Dialect extends Dialect {
 
     private static final String ISA = "ISA";
+    private static final String ISX = "ISX";
+    private static final String GS = "GS";
+    private static final String ST = "ST";
+
+    private static final int RELEASE_ISX_SEGMENT = 704; // 007040 (Version 7, release 4)
+    private static final int RELEASE_ELEMENT_I65 = 402; // 004020 (Version 4, release 2)
+
     static final char DFLT_SEGMENT_TERMINATOR = '~';
     static final char DFLT_DATA_ELEMENT_SEPARATOR = '*';
     static final char DFLT_COMPONENT_ELEMENT_SEPARATOR = ':';
@@ -39,11 +46,8 @@ public class X12Dialect implements Dialect {
     private int index = -1;
     private boolean initialized;
     private boolean rejected;
-    private char sd = DFLT_SEGMENT_TERMINATOR;
-    private char ed = DFLT_DATA_ELEMENT_SEPARATOR;
-    private char cd = DFLT_COMPONENT_ELEMENT_SEPARATOR;
-    private char er = DFLT_REPETITION_SEPARATOR;
 
+    private CharacterSet characters;
     private static final int TX_AGENCY = 0;
     private static final int TX_VERSION = 1;
 
@@ -53,6 +57,13 @@ public class X12Dialect implements Dialect {
     private String groupVersion;
 
     X12Dialect() {
+        segmentDelimiter = DFLT_SEGMENT_TERMINATOR;
+        elementDelimiter = DFLT_DATA_ELEMENT_SEPARATOR;
+        decimalMark = '.';
+        releaseIndicator = 0;
+        componentDelimiter = DFLT_COMPONENT_ELEMENT_SEPARATOR;
+        elementRepeater = DFLT_REPETITION_SEPARATOR;
+
         clearTransactionVersion();
     }
 
@@ -76,33 +87,38 @@ public class X12Dialect implements Dialect {
             }
         }
 
-        cd = header[X12_COMPONENT_OFFSET];
-        sd = header[X12_SEGMENT_OFFSET];
-        er = header[X12_REPEAT_OFFSET];
+        componentDelimiter = header[X12_COMPONENT_OFFSET];
+        segmentDelimiter = header[X12_SEGMENT_OFFSET];
+        elementRepeater = header[X12_REPEAT_OFFSET];
 
-        characters.setClass(cd, CharacterClass.COMPONENT_DELIMITER);
-        characters.setClass(sd, CharacterClass.SEGMENT_DELIMITER);
+        characters.setClass(componentDelimiter, CharacterClass.COMPONENT_DELIMITER);
+        characters.setClass(segmentDelimiter, CharacterClass.SEGMENT_DELIMITER);
 
-        try {
-            version = new String[] { new String(header, 84, 5) };
+        version = new String[] { new String(header, 84, 5) };
 
-            if (Integer.parseInt(version[0]) >= 402) {
-                characters.setClass(er, CharacterClass.ELEMENT_REPEATER);
-            } else {
-                er = '\0';
-            }
-        } catch (@SuppressWarnings("unused") NumberFormatException nfe) {
+        if (numericVersion() >= RELEASE_ELEMENT_I65) {
+            characters.setClass(elementRepeater, CharacterClass.ELEMENT_REPEATER);
+        } else {
             /*
-             * Ignore exception - the ELEMENT_REPEATER will not be set due to a
-             * non-numeric version.
+             * Exception parsing the version or older version - the ELEMENT_REPEATER
+             * will not be set.
              */
-            er = '\0';
+            elementRepeater = '\0';
         }
 
+        this.characters = characters;
         initialized = true;
         return true;
     }
 
+    int numericVersion() {
+        try {
+            return Integer.parseInt(version[0]);
+        } catch (NumberFormatException nfe) {
+            return 0;
+        }
+    }
+
     @Override
     public void setHeaderTag(String tag) {
         // No operation, can only be ISA
@@ -138,8 +154,8 @@ public class X12Dialect implements Dialect {
                 header = new char[X12_ISA_LENGTH];
                 break;
             case X12_ELEMENT_OFFSET:
-                ed = value;
-                characters.setClass(ed, CharacterClass.ELEMENT_DELIMITER);
+                elementDelimiter = value;
+                characters.setClass(elementDelimiter, CharacterClass.ELEMENT_DELIMITER);
                 break;
             default:
                 break;
@@ -159,36 +175,6 @@ public class X12Dialect implements Dialect {
         return true;
     }
 
-    @Override
-    public char getComponentElementSeparator() {
-        return cd;
-    }
-
-    @Override
-    public char getDataElementSeparator() {
-        return ed;
-    }
-
-    @Override
-    public char getDecimalMark() {
-        return '.';
-    }
-
-    @Override
-    public char getReleaseIndicator() {
-        return 0;
-    }
-
-    @Override
-    public char getRepetitionSeparator() {
-        return er;
-    }
-
-    @Override
-    public char getSegmentTerminator() {
-        return sd;
-    }
-
     void clearTransactionVersion() {
         agencyCode = "";
         groupVersion = "";
@@ -203,7 +189,12 @@ public class X12Dialect implements Dialect {
 
     @Override
     public void elementData(CharSequence data, Location location) {
-        if ("GS".equals(location.getSegmentTag())) {
+        if (ISX.equals(location.getSegmentTag()) && numericVersion() >= RELEASE_ISX_SEGMENT) {
+            if (location.getElementPosition() == 1 && data.length() == 1) {
+                releaseIndicator = data.charAt(0);
+                characters.setClass(releaseIndicator, CharacterClass.RELEASE_CHARACTER);
+            }
+        } else if (GS.equals(location.getSegmentTag())) {
             switch (location.getElementPosition()) {
             case 1:
                 clearTransactionVersion();
@@ -220,7 +211,7 @@ public class X12Dialect implements Dialect {
             default:
                 break;
             }
-        } else if ("ST".equals(location.getSegmentTag()) && location.getElementPosition() == 3 && data.length() > 0) {
+        } else if (ST.equals(location.getSegmentTag()) && location.getElementPosition() == 3 && data.length() > 0) {
             transactionVersion[TX_VERSION] = data.toString();
             updateTransactionVersionString(transactionVersion);
         }
diff --git a/src/main/resources/X12/common.xml b/src/main/resources/X12/common.xml
index d946046..5f5f777 100644
--- a/src/main/resources/X12/common.xml
+++ b/src/main/resources/X12/common.xml
@@ -135,6 +135,7 @@
       <value>00701</value>
       <value>00702</value>
       <value>00703</value>
+      <value>00704</value>
       <value>00800</value>
       <value>00801</value>
     </enumeration>
diff --git a/src/main/resources/X12/v00704.xml b/src/main/resources/X12/v00704.xml
new file mode 100644
index 0000000..476407f
--- /dev/null
+++ b/src/main/resources/X12/v00704.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
+<!--
+  Copyright 2020 xlate.io LLC, http://www.xlate.io
+
+  Licensed under the Apache License, Version 2.0 (the "License"); you may not
+  use this file except in compliance with the License.  You may obtain a copy
+  of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+  License for the specific language governing permissions and limitations under
+  the License.
+-->
+<schema xmlns="http://xlate.io/EDISchema/v4">
+
+  <include schemaLocation="common.xml"/>
+
+  <interchange header="ISA" trailer="IEA">
+    <sequence>
+      <segment type="ISB" title="Grade of Service Request"/>
+      <segment type="ISE" title="Deferred Delivery Request"/>
+      <segment type="TA1" title="Interchange Acknowledgment" maxOccurs="99999"/>
+      <segment type="ISX" title="Interchange Syntax Extension"/>
+
+      <group header="GS" trailer="GE">
+        <transaction header="ST" trailer="SE"/>
+      </group>
+
+      <transaction header="ST" trailer="SE" use="prohibited"/>
+    </sequence>
+  </interchange>
+
+  <segmentType name="ISA">
+    <sequence>
+      <element type="I01" minOccurs="1"/>
+      <element type="I02" minOccurs="1"/>
+      <element type="I03" minOccurs="1"/>
+      <element type="I04" minOccurs="1"/>
+      <element type="I05" minOccurs="1"/>
+      <element type="I06" minOccurs="1"/>
+      <element type="I05" minOccurs="1"/>
+      <element type="I07" minOccurs="1"/>
+      <element type="I08" minOccurs="1"/>
+      <element type="I09" minOccurs="1"/>
+      <element type="I65" minOccurs="1"/>
+      <element type="I11" minOccurs="1"/>
+      <element type="I12" minOccurs="1"/>
+      <element type="I13" minOccurs="1"/>
+      <element type="I14" minOccurs="1"/>
+      <element type="I15" minOccurs="1"/>
+    </sequence>
+  </segmentType>
+</schema>
diff --git a/src/main/resources/staedi-control-index.properties b/src/main/resources/staedi-control-index.properties
index f6b9b36..9fbd255 100644
--- a/src/main/resources/staedi-control-index.properties
+++ b/src/main/resources/staedi-control-index.properties
@@ -15,6 +15,8 @@
 #-------------------------------------------------------------------------------
 X12.00200=/X12/v00200.xml
 X12.00402=/X12/v00402.xml
+X12.00704=/X12/v00704.xml
+
 EDIFACT.1=/EDIFACT/v2.xml
 EDIFACT.2=/EDIFACT/v2.xml
 EDIFACT.3=/EDIFACT/v3.xml


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 4f58dfc..8bffe23 100644
--- a/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java
@@ -210,6 +210,261 @@ class StaEDIStreamReaderTest implements ConstantsTest {
         assertEquals(1, matches);
     }
 
+    @Test
+    void testGetDelimitersX12_WithISX_00401() throws EDIStreamException {
+        EDIInputFactory factory = EDIInputFactory.newFactory();
+        ByteArrayInputStream stream = new ByteArrayInputStream((""
+                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *200711*0100*U*00401*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.REPETITION, '*');
+        //expected.put(Delimiters.RELEASE, '\\');
+        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_00501() throws EDIStreamException {
+        EDIInputFactory factory = EDIInputFactory.newFactory();
+        ByteArrayInputStream stream = new ByteArrayInputStream((""
+                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *200711*0100*^*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.REPETITION, '^');
+        //expected.put(Delimiters.RELEASE, '\\');
+        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();
+        ByteArrayInputStream stream = new ByteArrayInputStream((""
+                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *200711*0100*^*00704*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.REPETITION, '^');
+        expected.put(Delimiters.RELEASE, '\\');
+        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");
+        assertEquals(0, errors.size(), "Unexpected errors");
+        assertEquals(1, delimiterExceptions, "Unexpected exceptions");
+    }
+
+    @Test
+    void testGetDelimitersX12_WithISX_CharTooLong_00704() throws EDIStreamException {
+        EDIInputFactory factory = EDIInputFactory.newFactory();
+        ByteArrayInputStream stream = new ByteArrayInputStream((""
+                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *200711*0100*^*00704*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.REPETITION, '^');
+        //expected.put(Delimiters.RELEASE, '\\');
+        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.DATA_ELEMENT_TOO_LONG },
+                          errors.toArray(new EDIStreamValidationError[errors.size()]));
+        assertEquals(1, delimiterExceptions, "Unexpected exceptions");
+    }
+
+    @Test
+    void testX12_ReleaseCharacter() throws EDIStreamException {
+        EDIInputFactory factory = EDIInputFactory.newFactory();
+        ByteArrayInputStream stream = new ByteArrayInputStream((""
+                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *200711*0100*^*00704*000000001*0*T*:~"
+                + "ISX*\\~"
+                + "GS*FA*Receiver\\*Dept*Sender\\*Dept*20200711*010015*1*X*005010~"
+                + "ST*997*0001*005010X230~"
+                + "SE*2*0001~"
+                + "GE*1*1~"
+                + "IEA*1*000000001~").getBytes());
+
+        EDIStreamReader reader = factory.createEDIStreamReader(stream);
+        List<EDIStreamValidationError> errors = new ArrayList<>();
+        String senderId = null;
+        String receiverId = null;
+
+        while (reader.hasNext()) {
+            switch (reader.next()) {
+            case ELEMENT_DATA:
+                if ("GS".equals(reader.getLocation().getSegmentTag())) {
+                    switch (reader.getLocation().getElementPosition()) {
+                    case 2:
+                        receiverId = reader.getText();
+                        break;
+                    case 3:
+                        senderId = reader.getText();
+                        break;
+                    default:
+                        break;
+                    }
+                }
+                break;
+            case SEGMENT_ERROR:
+            case ELEMENT_OCCURRENCE_ERROR:
+            case ELEMENT_DATA_ERROR:
+                errors.add(reader.getErrorType());
+                break;
+            default:
+                break;
+            }
+        }
+
+        assertEquals(0, errors.size(), "Unexpected errors");
+        assertEquals("Receiver*Dept", receiverId);
+        assertEquals("Sender*Dept", senderId);
+    }
+
     @Test
     void testNext() throws EDIStreamException {
         EDIInputFactory factory = EDIInputFactory.newFactory();