Repository: xlate/staedi (java)

Issue:
EDIFACT Support empty (trigger) segments
Support empty segments like the "SRC" one in IATA PADIS EDIFACT (https://www.iata.org/contentassets/18a5fdb2dc144d619a8c10dc1472ae80/pnrgov20edifact20implementation20guide2016_1.pdf).

When an empty segment (SRC') is reached, this exception is thrown:
`Message: EDIE003 - Invalid processing state: INVALID (previous: TAG_3); input: '''`

We have "fixed" it, but we don't know if it's the best solution :)


Pull request description:
Add support for empty segments (no data elements)
Implement release character output in writer

Fixes #10 

Patch:
diff --git a/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java b/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java
index 17a8e33..aaf17c0 100644
--- a/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java
+++ b/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java
@@ -495,9 +495,15 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
         ensureLevelAtLeast(LEVEL_ELEMENT);
         for (int i = 0, m = text.length(); i < m; i++) {
             char curr = text.charAt(i);
+
             if (characters.isDelimiter(curr)) {
-                throw new IllegalArgumentException("Value contains separator");
+                if (releaseIndicator > 0) {
+                    write(releaseIndicator);
+                } else {
+                    throw new IllegalArgumentException("Value contains separator: " + curr);
+                }
             }
+
             write(curr);
             elementBuffer.put(curr);
         }
diff --git a/src/main/java/io/xlate/edi/internal/stream/tokenization/Lexer.java b/src/main/java/io/xlate/edi/internal/stream/tokenization/Lexer.java
index dca7ea9..69feec2 100644
--- a/src/main/java/io/xlate/edi/internal/stream/tokenization/Lexer.java
+++ b/src/main/java/io/xlate/edi/internal/stream/tokenization/Lexer.java
@@ -232,6 +232,10 @@ public class Lexer {
                 closeSegment();
                 eventsReady = nextEvent();
                 break;
+            case SEGMENT_EMPTY:
+                emptySegment();
+                eventsReady = nextEvent();
+                break;
             case COMPONENT_END:
                 handleComponent();
                 eventsReady = nextEvent();
@@ -265,12 +269,12 @@ public class Lexer {
     }
 
     void handleStateHeaderTag(int input) {
-    	buffer.put((char) input);
+        buffer.put((char) input);
         dialect.appendHeader(characters, (char) input);
     }
 
     void handleStateElementDataBinary() {
-    	/*
+        /*
          * Not all of the binary data has been consumed. I.e. #next was
          * called before completion.
          */
@@ -280,7 +284,7 @@ public class Lexer {
     }
 
     void handleStateInterchangeCandidate(int input) throws EDIException {
-    	stream.mark(500);
+        stream.mark(500);
         buffer.put((char) input);
         final char[] header = buffer.array();
         final int length = buffer.position();
@@ -293,7 +297,7 @@ public class Lexer {
     }
 
     void handleStateHeaderData(int input) throws EDIException {
-    	dialect.appendHeader(characters, (char) input);
+        dialect.appendHeader(characters, (char) input);
 
         if (characters.isDelimiter(input)) {
             if (characters.getDelimiter(CharacterClass.SEGMENT_DELIMITER) == input) {
@@ -441,6 +445,12 @@ public class Lexer {
         enqueue(sen, 0);
     }
 
+    private void emptySegment() throws EDIException {
+        openSegment();
+        popMode(Mode.SEGMENT);
+        enqueue(sen, 0);
+    }
+
     private void handleElement() throws EDIException {
         if (previous != State.ELEMENT_END_BINARY) {
             addElementEvent();
diff --git a/src/main/java/io/xlate/edi/internal/stream/tokenization/State.java b/src/main/java/io/xlate/edi/internal/stream/tokenization/State.java
index f425666..7cd7268 100644
--- a/src/main/java/io/xlate/edi/internal/stream/tokenization/State.java
+++ b/src/main/java/io/xlate/edi/internal/stream/tokenization/State.java
@@ -61,6 +61,7 @@ public enum State {
     ELEMENT_END_BINARY(17),
 
     SEGMENT_END(10),
+    SEGMENT_EMPTY(10),
 
     TRAILER_TAG_I(18),
     TRAILER_TAG_E(19),
@@ -111,6 +112,7 @@ public enum State {
     private static final State ER = State.ELEMENT_REPEAT;
     private static final State EE = State.ELEMENT_END;
     private static final State SE = State.SEGMENT_END;
+    private static final State SY = State.SEGMENT_EMPTY;
 
     private static final State ZI = State.TRAILER_TAG_I;
     private static final State ZE = State.TRAILER_TAG_E;
@@ -157,8 +159,8 @@ public enum State {
 
         /* SE+TS Tag Search */{ TS, T1, T1, T1, ZI, T1, T1, ZU, T1, T1, __, __, __, __, __, TS, __, __, __ },
         /* T1  Tag Char 1 * */{ __, T2, T2, T2, T2, T2, T2, T2, T2, T2, __, __, __, __, __, __, __, __, __ },
-        /* T2  Tag Char 2 * */{ __, T3, T3, T3, T3, T3, T3, T3, T3, T3, __, SB, __, __, __, __, __, __, __ },
-        /* T3  Tag Char 3 * */{ __, __, __, __, __, __, __, __, __, __, __, SB, __, __, __, __, __, __, __ },
+        /* T2  Tag Char 2 * */{ __, T3, T3, T3, T3, T3, T3, T3, T3, T3, SY, SB, __, __, __, __, __, __, __ },
+        /* T3  Tag Char 3 * */{ __, __, __, __, __, __, __, __, __, __, SY, SB, __, __, __, __, __, __, __ },
 
         /* Element Process  */{ ED, ED, ED, ED, ED, ED, ED, ED, ED, ED, SE, EE, CE, ER, DR, EI, EI, ED, EI },
         /* Data Release     */{ ED, ED, ED, ED, ED, ED, ED, ED, ED, ED, ED, ED, ED, ED, ED, EI, EI, ED, EI },


Test patch:
diff --git a/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamWriterTest.java b/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamWriterTest.java
index 218a319..833e007 100644
--- a/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamWriterTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamWriterTest.java
@@ -631,6 +631,107 @@ public class StaEDIStreamWriterTest {
         assertEquals(expected.toString().trim(), result.toString().trim());
     }
 
+    @Test
+    public void testInputEquivalenceEDIFACT_IATA_PNRGOV() throws Exception {
+        EDIInputFactory inputFactory = EDIInputFactory.newFactory();
+        final ByteArrayOutputStream expected = new ByteArrayOutputStream(16384);
+
+        InputStream source = new InputStream() {
+            final InputStream delegate;
+            {
+                delegate = getClass().getResourceAsStream("/EDIFACT/pnrgov.edi");
+            }
+
+            @Override
+            public int read() throws IOException {
+                int value = delegate.read();
+
+                if (value != -1) {
+                    expected.write(value);
+                    return value;
+                }
+
+                return -1;
+            }
+        };
+        EDIStreamReader reader = inputFactory.createEDIStreamReader(source);
+
+        EDIOutputFactory outputFactory = EDIOutputFactory.newFactory();
+        outputFactory.setProperty(EDIOutputFactory.PRETTY_PRINT, true);
+        ByteArrayOutputStream result = new ByteArrayOutputStream(16384);
+        EDIStreamWriter writer = null;
+
+        EDIStreamEvent event;
+        String tag = null;
+        boolean composite = false;
+
+        try {
+            while (reader.hasNext()) {
+                event = reader.next();
+
+                switch (event) {
+                case START_INTERCHANGE:
+                    for (Map.Entry<String, Character> delim : reader.getDelimiters().entrySet()) {
+                        outputFactory.setProperty(delim.getKey(), delim.getValue());
+                    }
+                    writer = outputFactory.createEDIStreamWriter(result);
+                    writer.startInterchange();
+                    break;
+                case END_INTERCHANGE:
+                    writer.endInterchange();
+                    break;
+                case START_SEGMENT:
+                    tag = reader.getText();
+                    writer.writeStartSegment(tag);
+                    break;
+                case END_SEGMENT:
+                    writer.writeEndSegment();
+                    break;
+                case START_COMPOSITE:
+                    writer.writeStartElement();
+                    composite = true;
+                    break;
+                case END_COMPOSITE:
+                    writer.endElement();
+                    composite = false;
+                    break;
+                case ELEMENT_DATA:
+                    String text = reader.getText();
+
+                    if ("UNA".equals(tag)) {
+                        continue;
+                    }
+
+                    if (composite) {
+                        writer.startComponent();
+                        writer.writeElementData(text);
+                        writer.endComponent();
+                    } else {
+                        if (reader.getLocation().getElementOccurrence() > 1) {
+                            writer.writeRepeatElement();
+                        } else {
+                            writer.writeStartElement();
+                        }
+                        writer.writeElementData(text);
+                        writer.endElement();
+                    }
+                    break;
+                case ELEMENT_DATA_BINARY:
+                    writer.writeStartElementBinary();
+                    writer.writeBinaryData(reader.getBinaryData());
+                    writer.endElement();
+                    break;
+                default:
+                    break;
+                }
+            }
+        } finally {
+            reader.close();
+        }
+
+        assertEquals(expected.toString().trim(), result.toString().trim());
+    }
+
     @Test
     public void testInputEquivalenceEDIFACTB() throws Exception {
         EDIInputFactory inputFactory = EDIInputFactory.newFactory();
diff --git a/src/test/resources/EDIFACT/pnrgov.edi b/src/test/resources/EDIFACT/pnrgov.edi
new file mode 100644
index 0000000..2e0802e
--- /dev/null
+++ b/src/test/resources/EDIFACT/pnrgov.edi
@@ -0,0 +1,88 @@
+UNA:+.\*'
+UNB+IATA:1+1A+KRC+130527:0649+0003'
+UNH+1+PNRGOV:11:1:IA+270513/0649/SQ/602'
+MSG+:22'
+ORG+1A:MUC'
+TVL+270513:1430:270513:2205+SIN+ICN+SQ+602'
+EQN+1'
+SRC'
+RCI+1A:3PGZOV::190313:1354'
+DAT+700:270513:0559'
+ORG+1A:MUC+32393340:SINSQ08AA+NCE+SQ:NCE+A+SG+ELPD+CFDE59+9C'
+TIF+BELT:I+ISABELLE MRS:A:2:1'
+FTI+SQ:8794285757'
+IFT+4:63::SQ'
+REF+:001C451486DFF0CC'
+SSR+DOCS:HK:1:SQ:::::/P/GBR/512731999/GBR/20SEP12/FI/25OCT17/BELT/SOP HY OLIVIA/'
+SSR+DOCS:HK:1:SQ:::::/P/GBR/509229987/GBR/01JUL78/F/12NOV22/BELT/ISAB ELLE RUTH/'
+TIF+BELT:I+SOPHY:IN:3'
+IFT+4:63::SQ'
+SSR+DOCS:HK:1:SQ:::::/P/GBR/512731999/GBR/20SEP12/FI/25OCT17/BELT/SOP HY OLIVIA/'
+TVL+270513:1430:270513:2205+SIN+ICN+SQ+602:D'
+RPI+1+HK'
+APD+333'
+SSR+INFT:HK:1:SQ:::SIN:ICN:BELT/SOPHY 20SEP12+::2'
+SSR+DOCS:HK:1:SQ:::SIN:ICN:/P/GBR/509229987/GBR/01JUL78/F/12NOV22/BEL T/ISABELLE RUTH/+::2'
+RCI+1A:3PGZOV::190313:1354'
+DAT'
+ORG+SQ++++A'
+TRI++SIN-168:::2'
+TIF+BELT:I+ISABELLE MRS:A:2'
+SSD+011D++++J'
+TBD++3:33:700++HP:SIN- 168+618:0123456789:2:ICN+618:0123456788:3:ICN+618:0123456787:722356:ICN'
+DAT'
+ORG+SQ++++A'
+TRI++SIN-169:::3'
+TIF+BELT:I+SOPHY:IN:3'
+SSD+011D++++J'
+LTS+0/O/NM/BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+0/O/SS/SQ 602 D 27MAY 1 SINICN LK1 1430 2205/NN \*1A/E\* /SQ/SG/C/I/CAB J//1///// /Y 1625/B 153//AY 1838/EY 1685/SINICN/D'
+LTS+0/O/SR/SSR INFTSQNN1 BELT/SOPHY 20SEP12/SQ 602 D 27MAY SINICN/BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+0/O/SR/SSR FQTVSQHK/ SQ8794285757 S/KFES/BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+0/O/SR/SSR FQTSSQHK1 SQ8794285757 S/KFES/SQ 602 D 27MAY SINICN/BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+0/O/SR/SSR DOCSSQHK1 P/GB/509229987/GB/01JUL78/F/12NOV22/BELT/ISABELLE//H/SQ 602 D 27MAY SINICN/BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+0/Z/AMADEUS E RETAIL CR-SINSQ08AA 32393340 SU 0001AA/DS- 9CCFDE59 19MAR1354Z'
+LTS+0/1/R/SR/SSR INFTSQKK1 BELT/SOPHY 20SEP12/HN/SQ 602 D 27MAY SINICN/BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+1/Z/1AINV RM SQ 191354 CR-1AINV RM SQ 0000 19MAR1354Z'
+LTS+2/P/QE/SINSQ0100/1C15D4'
+LTS+2/Z/1AINV RM SQ 191354 CR-1AI NV R 19MAR1354Z'
+LTS+3/Z/ -SQ/WSSQSAA CR-SINSQ08AA 32393340 GS 9999WS/RO-9C404C04 SAAW330SQ 00000000 19MAR1354Z'
+LTS+4/Z/1AINV RM SQ 191354 CR-1AINV RM SQ
+LTS+0/5/C/5/AP AMADEUS-H'
+LTS+0/5/C/27/TKXL 20MAR/0004/SINSQ08AA'
+LTS+5/A/27/TKOK 19MAR/SINSQ08AA'
+LTS+5/6/R/27/TKOK 19MAR/SINSQ08AA'
+LTS+6/Z/AA CR-SINSQ08AA 32393340 SU 0001AA/DS-9CCFDF66 19MAR1359Z'
+LTS+7/Z//DCS-SYNCUS CR-SINSQ00CO 00000000 PD 6160MC/DS-9CBABA44 23MAY1240Z'
+LTS+0/8/C/SR/SSR FQTVSQHK/ SQ8794285757 S/KFES/BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+0/8/C/SR/SSR FQTSSQHK1 SQ8794285757 S/KFES/SQ 602 D 27MAY SINICN/BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+8/A/SR/SSR FQTVSQHK/ SQ8794285757 G/QPPS/BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+8/A/SR/SSR FQTSSQHK1 SQ8794285757 G/QPPS/SQ 602 D 27MAY SINICN/BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+8/Z/CSXAPU CR-NCE1A0SQ0 SU 0001AA 24MAY2009Z'
+LTS+9/Z/1AINV RM SQ 242009 CR-1AINV RM SQ 0000 24MAY2009Z'
+LTS+1/10/R/SR/SSR INFTSQHK1 BELT/SOPHY 20SEP12/KK/SQ 602 D 27MAY SINICN/BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+4/10/R/SR/SSR BSCTSQHK1/KK/SQ 602 D 27MAY SINICN/BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+10/Z/ETK-ISSBOE CR-SINSQ01W0 32391122 GS 1916VV/RO-9CB39093 MUCPI2SQ1 00000000 25MAY0309Z'
+LTS+11/A/SK/SK LKPX SQ HK1 Z8OWIE-P1/SQ 602 D 27MAY SINICN/BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+11/Z/ISSBOE CR-SINSQ01W0 32391122 GS 1916VV/RO-9CB39093 MUCPI2SQ1 00000000 25MAY0313Z'
+LTS+12/Z/MRS BELT CR-SINSQ01W0 32391122 GS 1916VV/RO-9CB39093 MUCPI2SQ1 00000000 25MAY0315Z'
+LTS+13/Z/1AINV RM SQ 250315 CR-1AINV RM SQ 0000 25MAY0315Z'
+LTS+14/A/7/RX \*\*\*\*\*\*\*\* ATTN SINKKXH SINKDXH SINKNXH PLS ASSIST PAX WITH TKT 618-2402058077 AND 618-2402241436 TO BE SEATED TOGETHER FLT'
+LTS+14/A/7/RX SQ602 D SIN - ICN 27MAY13 14\:30 ON BSCT SEAT X MANY THANKS SINRRRSQ'
+LTS+14/A/7/RX MS BELT CALLED TO UPDATE SEAT RQST X ADDED SEAT / MEAL X TELEX SENT X NIL SEATS AND MEALS X MOBILE CONTACT NUMBER UPDATED'
+LTS+14/A/7/RX X VIAN / 8\:48 IST 25/5/2013'
+LTS+14/Z/MRS BELT CR-SINSQ01W0 32391122 GS 1916VV/RO-9CB39093 MUCPI2SQ1 00000000 25MAY0320Z'
+LTS+15/A/SR/SSR DOCSSQHK1 P/GBR/512731999/GBR/20SEP12/FI/25OCT17/BELT/SOPHY OLIVIA//BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+15/A/SR/SSR DOCSSQHK1 P/GBR/512731999/GBR/20SEP12/FI/25OCT17/BELT/SOPHY OLIVIA//SQ 602 D 27MAY SINICN/BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+15/Z//DCS-IREQ CR-SINSQ00VW 00000000 GS 9743EC/DS-9CBCCB00 27MAY0422Z'
+LTS+0/16/C/SR/SSR DOCSSQHK1 P/GB/509229987/GB/01JUL78/F/12NOV22/BELT/ISABELLE//H/SQ 602 D 27MAY SINICN/BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+16/A/SR/SSR DOCSSQHK1 P/GBR/509229987/GBR/01JUL78/F/12NOV22/BELT/ISABELLE RUTH//SQ 602 D 27MAY SINICN/BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+16/Z//DCS-IREQ CR-SINSQ00VW 00000000 GS 9743EC/DS-9CBAB9B5 27MAY0423Z'
+LTS+17/A/SR/SSR DOCSSQHK1 P/GBR/509229987/GBR/01JUL78/F/12NOV22/BELT/ISABELLE RUTH//BELT/ISABELLE MRS(ADT)(INF/SOPHY/20SEP12)'
+LTS+17/Z//DCS-IREQ CR-SINSQ00VW 00000000 GS 9743EC/DS-9CBAB9D5 27MAY0423Z'
+LTS+18/Z//DCS-SYNCUS CR-SINSQ00VW 00000000 GS 9743EC/DS-9CBABA1D 27MAY0423Z'
+LTS+19/Z//DCS-SYNCUS CR-SINSQ00VW 00000000 GS 9743EC/DS-9CBCCB14 27MAY0423Z'
+LTS+20/Z//DCS-SYNCUS CR-SINSQ00CO 00000000 PD 2092EL/DS-9CBAB94B 27MAY0559Z'
+LTS+21/Z//DCS-SYNCUS CR-SINSQ00CO 00000000 PD 2092EL/DS-9CBABA1A 27MAY0559Z'
+UNT+85+1'
+UNZ+1+0003'
\ No newline at end of file