diff --git a/build.gradle b/build.gradle index 6026aa7..9d66b7b 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,7 @@ dependencies { api 'com.thoughtworks.xstream:xstream:1.4.11.1' testImplementation 'junit:junit:4.13' + testImplementation 'commons-io:commons-io:2.6' } jar { diff --git a/src/main/java/com/imsweb/x12/LineBreak.java b/src/main/java/com/imsweb/x12/LineBreak.java new file mode 100644 index 0000000..3a45a8c --- /dev/null +++ b/src/main/java/com/imsweb/x12/LineBreak.java @@ -0,0 +1,30 @@ +package com.imsweb.x12; + +/** + * Choice of line break when serializing x12 documents. + */ +public enum LineBreak { + + /** + * Unix line endings. + */ + LF("\n"), + /** + * Windows line endings. + */ + CRLF("\r\n"), + /** + * No line breaks at all. + */ + NONE(""); + + private String _lineBreakString; + + LineBreak(String lineBreakString) { + this._lineBreakString = lineBreakString; + } + + public String getLineBreakString() { + return _lineBreakString; + } +} diff --git a/src/main/java/com/imsweb/x12/Loop.java b/src/main/java/com/imsweb/x12/Loop.java index e08b311..4df6836 100644 --- a/src/main/java/com/imsweb/x12/Loop.java +++ b/src/main/java/com/imsweb/x12/Loop.java @@ -6,8 +6,14 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; import java.util.stream.Collectors; +import com.imsweb.x12.converters.ElementConverter; +import com.imsweb.x12.mapping.LoopDefinition; +import com.imsweb.x12.mapping.Positioned; +import com.imsweb.x12.mapping.SegmentDefinition; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamOmitField; @@ -19,8 +25,6 @@ import com.thoughtworks.xstream.security.NoTypePermission; import com.thoughtworks.xstream.security.WildcardTypePermission; -import com.imsweb.x12.converters.ElementConverter; - /** * The Loop class is the representation of an Loop in a ANSI X12 transaction. The building block of an X12 transaction is an element. Some * elements may be made of sub elements. Elements combine to form segments. Segments are grouped as loops. And a set of loops form an X12 @@ -567,7 +571,9 @@ public Loop findTopParentById(String parentId) { } /** - * Returns the Loop in X12 String format. This method is used to convert the X12 object into a X12 transaction. + * Returns an X12 String for this loop, but it will not be + * properly ordered. For properly ordered X12 string use toX12String. + * * @return String representation */ @Override @@ -584,6 +590,71 @@ public String toString() { return dump.toString(); } + /** + * Returns the Loop in X12 String format. This method is used to convert the X12 object into a X12 transaction. + * + * This will first go through each segment and will return the properly separated string for the segment. + * + * After the segments are seriaized to X12 strings, it will then go through the Loops (in the correct order) and + * recursively call this function for the child loops. + * + * @param loopDefinition The definition of the loop that we are currently on. + * @return String representation The segments from this loop, including child loops. + */ + public String toX12String(LoopDefinition loopDefinition) { + StringBuilder dump = new StringBuilder(); + + Set segmentsAndLoops = new TreeSet<>(); + if (loopDefinition.getLoop() != null) { + segmentsAndLoops.addAll(loopDefinition.getLoop()); + } + if (loopDefinition.getSegment() != null) { + segmentsAndLoops.addAll(loopDefinition.getSegment()); + } + for (Positioned positioned : segmentsAndLoops) { + if (positioned instanceof SegmentDefinition) { + SegmentDefinition segmentDefinition = (SegmentDefinition)positioned; + int idx = 0; + Segment segment; + while ((segment = getSegment(segmentDefinition.getXid(), idx++)) != null) { + dump.append(segment); + dump.append(_separators.getSegment()); + dump.append(_separators.getLineBreak().getLineBreakString()); + } + } + else if (positioned instanceof LoopDefinition) { + LoopDefinition innerLoopDefinition = (LoopDefinition)positioned; + int idx = 0; + Loop innerLoop; + while ((innerLoop = getLoopForPrinting(innerLoopDefinition, idx++)) != null) { + dump.append(innerLoop.toX12String(innerLoopDefinition)); + } + } + } + return dump.toString(); + } + + /** + * Send a LoopDefinition and the index of an loop, fetch the loop from the + * child loops that matches, given that the parentLoop has this loop as a child. + * @param loopDefinition Loop definition for this spot in the x12 document. + * @param idx The index of the loops returned thus far. + * @return The child loop from the loops, or null otherwise. + */ + private Loop getLoopForPrinting(LoopDefinition loopDefinition, int idx) { + Loop loop = getLoop(loopDefinition.getXid(), idx); + + // We need to check that the loop we have gotten from getLoop + // is actually a direct child of the current loop we are processing from the + // loop definition. + + if (loop != null && _loops.stream().noneMatch(parentLoop -> parentLoop.getId().equals(loop.getId()))) { + return null; + } + + return loop; + } + /** * Returns the Loop in XML String format. * @return XML String diff --git a/src/main/java/com/imsweb/x12/Separators.java b/src/main/java/com/imsweb/x12/Separators.java index 92bf2a0..0de77fd 100644 --- a/src/main/java/com/imsweb/x12/Separators.java +++ b/src/main/java/com/imsweb/x12/Separators.java @@ -14,6 +14,7 @@ public class Separators { private Pattern _segmentPattern; private Pattern _elementPattern; private Pattern _compositePattern; + private LineBreak _lineBreak; /** * Default constructor. @@ -22,6 +23,7 @@ public Separators() { setSegment('~'); setElement('*'); setCompositeElement(':'); + setLineBreak(LineBreak.NONE); } /** @@ -107,6 +109,14 @@ public String[] splitComposite(String line) { return (line != null && _compositePattern != null) ? _compositePattern.split(line) : null; } + public LineBreak getLineBreak() { + return _lineBreak; + } + + public void setLineBreak(LineBreak lineBreak) { + this._lineBreak = lineBreak; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/com/imsweb/x12/mapping/LoopDefinition.java b/src/main/java/com/imsweb/x12/mapping/LoopDefinition.java index db2f93c..7b8202c 100644 --- a/src/main/java/com/imsweb/x12/mapping/LoopDefinition.java +++ b/src/main/java/com/imsweb/x12/mapping/LoopDefinition.java @@ -4,16 +4,16 @@ package com.imsweb.x12.mapping; import java.util.List; +import java.util.Objects; +import com.imsweb.x12.mapping.TransactionDefinition.Usage; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAsAttribute; import com.thoughtworks.xstream.annotations.XStreamConverter; import com.thoughtworks.xstream.annotations.XStreamImplicit; -import com.imsweb.x12.mapping.TransactionDefinition.Usage; - @XStreamAlias("loop") -public class LoopDefinition { +public class LoopDefinition implements Positioned { @XStreamAlias("xid") @XStreamAsAttribute @@ -37,6 +37,7 @@ public class LoopDefinition { @XStreamImplicit private List _loop; + @Override public String getXid() { return _xid; } @@ -49,6 +50,7 @@ public Usage getUsage() { return _usage; } + @Override public String getPos() { return _pos; } @@ -69,4 +71,23 @@ public List getLoop() { return _loop; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LoopDefinition that = (LoopDefinition) o; + return Objects.equals(_xid, that._xid) && + Objects.equals(_type, that._type) && + _usage == that._usage && + Objects.equals(_pos, that._pos) && + Objects.equals(_repeat, that._repeat) && + Objects.equals(_name, that._name) && + Objects.equals(_segment, that._segment) && + Objects.equals(_loop, that._loop); + } + + @Override + public int hashCode() { + return Objects.hash(_xid, _type, _usage, _pos, _repeat, _name, _segment, _loop); + } } diff --git a/src/main/java/com/imsweb/x12/mapping/Positioned.java b/src/main/java/com/imsweb/x12/mapping/Positioned.java new file mode 100644 index 0000000..445ba31 --- /dev/null +++ b/src/main/java/com/imsweb/x12/mapping/Positioned.java @@ -0,0 +1,17 @@ +package com.imsweb.x12.mapping; + +import java.util.Objects; + +public interface Positioned extends Comparable { + String getXid(); + String getPos(); + + @Override + default int compareTo(Positioned o) { + Objects.requireNonNull(o); + if (getPos().equals(o.getPos())) { + return getXid().compareTo(o.getXid()); + } + return getPos().compareTo(o.getPos()); + } +} diff --git a/src/main/java/com/imsweb/x12/mapping/SegmentDefinition.java b/src/main/java/com/imsweb/x12/mapping/SegmentDefinition.java index 6ae8c24..f0d0925 100644 --- a/src/main/java/com/imsweb/x12/mapping/SegmentDefinition.java +++ b/src/main/java/com/imsweb/x12/mapping/SegmentDefinition.java @@ -4,16 +4,16 @@ package com.imsweb.x12.mapping; import java.util.List; +import java.util.Objects; +import com.imsweb.x12.mapping.TransactionDefinition.Usage; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAsAttribute; import com.thoughtworks.xstream.annotations.XStreamConverter; import com.thoughtworks.xstream.annotations.XStreamImplicit; -import com.imsweb.x12.mapping.TransactionDefinition.Usage; - @XStreamAlias("segment") -public class SegmentDefinition { +public class SegmentDefinition implements Positioned { @XStreamAlias("xid") @XStreamAsAttribute @@ -37,6 +37,7 @@ public class SegmentDefinition { @XStreamImplicit private List _composites; + @Override public String getXid() { return _xid; } @@ -49,6 +50,7 @@ public Usage getUsage() { return _usage; } + @Override public String getPos() { return _pos; } @@ -69,4 +71,23 @@ public List getComposites() { return _composites; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SegmentDefinition that = (SegmentDefinition) o; + return Objects.equals(_xid, that._xid) && + Objects.equals(_name, that._name) && + _usage == that._usage && + Objects.equals(_pos, that._pos) && + Objects.equals(_maxUse, that._maxUse) && + Objects.equals(_syntax, that._syntax) && + Objects.equals(_elements, that._elements) && + Objects.equals(_composites, that._composites); + } + + @Override + public int hashCode() { + return Objects.hash(_xid, _name, _usage, _pos, _maxUse, _syntax, _elements, _composites); + } } diff --git a/src/main/java/com/imsweb/x12/reader/X12Reader.java b/src/main/java/com/imsweb/x12/reader/X12Reader.java index a1a1af0..dec24a4 100644 --- a/src/main/java/com/imsweb/x12/reader/X12Reader.java +++ b/src/main/java/com/imsweb/x12/reader/X12Reader.java @@ -19,11 +19,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import com.thoughtworks.xstream.XStream; -import com.thoughtworks.xstream.io.xml.StaxDriver; -import com.thoughtworks.xstream.security.NoTypePermission; -import com.thoughtworks.xstream.security.WildcardTypePermission; - +import com.imsweb.x12.LineBreak; import com.imsweb.x12.Loop; import com.imsweb.x12.Segment; import com.imsweb.x12.Separators; @@ -33,6 +29,10 @@ import com.imsweb.x12.mapping.SegmentDefinition; import com.imsweb.x12.mapping.TransactionDefinition; import com.imsweb.x12.mapping.TransactionDefinition.Usage; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.StaxDriver; +import com.thoughtworks.xstream.security.NoTypePermission; +import com.thoughtworks.xstream.security.WildcardTypePermission; public class X12Reader { @@ -55,6 +55,7 @@ public class X12Reader { private List _config = new ArrayList<>(); private List _dataLoops = new ArrayList<>(); private Map>> _childLoopTracker = new HashMap<>(); + private Separators _separators; TransactionDefinition _definition; /** @@ -99,6 +100,8 @@ protected synchronized TransactionDefinition getDefinition() { } } + private FileType _type; + static { _TYPES.put(FileType.ANSI835_4010_X091, _X091_ANSI_VERSION); _TYPES.put(FileType.ANSI837_4010_X096, _X096_ANSI_VERSION); @@ -116,7 +119,8 @@ protected synchronized TransactionDefinition getDefinition() { * @throws IOException if there was an error reading the input file */ public X12Reader(FileType type, File file) throws IOException { - parse(type, new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.defaultCharset()))); + this._type = type; + parse(new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.defaultCharset()))); } /** @@ -127,7 +131,8 @@ public X12Reader(FileType type, File file) throws IOException { * @throws IOException if there was an error reading the input file */ public X12Reader(FileType type, File file, Charset charset) throws IOException { - parse(type, new BufferedReader(new InputStreamReader(new FileInputStream(file), charset))); + this._type = type; + parse(new BufferedReader(new InputStreamReader(new FileInputStream(file), charset))); } /** @@ -137,7 +142,8 @@ public X12Reader(FileType type, File file, Charset charset) throws IOException { * @throws IOException if there was an error reading the input file */ public X12Reader(FileType type, InputStream input) throws IOException { - parse(type, new BufferedReader(new InputStreamReader(input, Charset.defaultCharset()))); + this._type = type; + parse(new BufferedReader(new InputStreamReader(input, Charset.defaultCharset()))); } /** @@ -148,7 +154,8 @@ public X12Reader(FileType type, InputStream input) throws IOException { * @throws IOException if there was an error reading the input file */ public X12Reader(FileType type, InputStream input, Charset charset) throws IOException { - parse(type, new BufferedReader(new InputStreamReader(input, charset))); + this._type = type; + parse(new BufferedReader(new InputStreamReader(input, charset))); } /** @@ -158,11 +165,34 @@ public X12Reader(FileType type, InputStream input, Charset charset) throws IOExc * @throws IOException if there was an error reading the input file */ public X12Reader(FileType type, Reader reader) throws IOException { + this._type = type; // the Reader must support mark; if it does not, wrap the reader in a BufferedReader if (!reader.markSupported()) - parse(type, new BufferedReader(reader)); + parse(new BufferedReader(reader)); else - parse(type, reader); + parse(reader); + } + + /** + * Gets an X12 formatted string representing this X12 reader. Will use no line + * breaks after separators. + * + * @return X12 formatted string representing this X12 reader. + */ + public String toX12String() { + _separators.setLineBreak(LineBreak.NONE); + return toX12StringImpl(); + } + + /** + * Gets an X12 formatted string representing this X12 reader. + * + * @param lineBreak Line break to use for separators. + * @return X12 formatted string representing this X12 reader. + */ + public String toX12String(LineBreak lineBreak) { + _separators.setLineBreak(lineBreak); + return toX12StringImpl(); } /** @@ -184,21 +214,27 @@ public List getErrors() { public List getFatalErrors() { return _fatalErrors; } + + public Separators getSeparators() { + return _separators; + } /** * Parse a Readable into a Loop - * @param type file type definition * @param reader reader */ - private void parse(FileType type, Reader reader) throws IOException { + private void parse(Reader reader) throws IOException { Scanner scanner = new Scanner(reader); // set up delimiters - Separators separators = getSeparators(reader); + _separators = getSeparators(reader); - if (separators != null && checkVersionsAreConsistent(type, separators, reader)) { - Character segmentSeparator = separators.getSegment(); + if (_separators != null && checkVersionsAreConsistent(_separators, reader)) { + Character segmentSeparator = _separators.getSegment(); String quotedSegmentSeparator = Pattern.quote(segmentSeparator.toString()); + + // The following delimiter patterns will accept the segment delimiter with + // optional line breaks. scanner.useDelimiter(quotedSegmentSeparator + "\r\n|" + quotedSegmentSeparator + "\n|" + quotedSegmentSeparator); List loopLines = new ArrayList<>(); // holds the lines from the claims files that all belong to the same loop @@ -207,7 +243,7 @@ private void parse(FileType type, Reader reader) throws IOException { Loop lastLoopStored = null; // parse _definition file - _definition = type.getDefinition(); + _definition = _type.getDefinition(); // cache definitions of loop starting segments getLoopConfiguration(_definition.getLoop(), null); @@ -217,12 +253,12 @@ private void parse(FileType type, Reader reader) throws IOException { String line = scanner.next().trim(); while (scanner.hasNext()) { // Determine if we have started a new loop - loopConfig = getMatchedLoop(separators.splitElement(line), currentLoopConfig == null ? null : currentLoopConfig.getLoopId()); + loopConfig = getMatchedLoop(_separators.splitElement(line), currentLoopConfig == null ? null : currentLoopConfig.getLoopId()); if (loopConfig == null) loopLines.add(line); // didn't start a new loop, just add the lines for the current loop else { if (loopConfig.getLastSegmentXid() != null && line.startsWith(loopConfig.getLastSegmentXid().getXid()) && !loopConfig.equals(currentLoopConfig)) { - lastLoopStored = appendEndingSegment(lastLoopStored, currentLoopConfig, loopConfig, separators, line, loopLines); + lastLoopStored = appendEndingSegment(lastLoopStored, currentLoopConfig, loopConfig, _separators, line, loopLines); if (lastLoopStored != null) { loopLines = new ArrayList<>(); currentLoopConfig = loopConfig; @@ -233,14 +269,14 @@ private void parse(FileType type, Reader reader) throws IOException { else if (loopConfig.getLoopId().equals(_definition.getLoop().getXid())) { // we are processing a new transaction - store any old data if necessary if (lastLoopStored != null && !loopLines.isEmpty()) { - if (storeData(currentLoopConfig, loopLines, lastLoopStored, separators) == null) + if (storeData(currentLoopConfig, loopLines, lastLoopStored, _separators) == null) break; loopLines = new ArrayList<>(); } currentLoopConfig = loopConfig; lastLoopStored = null; Loop loop = new Loop(null); - loop.setSeparators(separators); + loop.setSeparators(_separators); _dataLoops.add(loop); loopLines.add(line); } @@ -252,7 +288,7 @@ else if (loopConfig.getLoopId().equals(_definition.getLoop().getXid())) { updateLoopCounts(loopConfig.getLoopId()); // store the data from processing the last loop if (!loopLines.isEmpty()) - lastLoopStored = storeData(currentLoopConfig, loopLines, lastLoopStored, separators); + lastLoopStored = storeData(currentLoopConfig, loopLines, lastLoopStored, _separators); if (lastLoopStored == null) break; // fatal error recorded during storing the loop @@ -276,8 +312,8 @@ else if (loopConfig.getLoopId().equals(_definition.getLoop().getXid())) { // store the final segment if the last line of the file has data. if (!line.isEmpty() && _fatalErrors.isEmpty()) { if (currentLoopConfig != null) { - loopConfig = getMatchedLoop(separators.splitElement(line), currentLoopConfig.getLoopId()); - lastLoopStored = appendEndingSegment(lastLoopStored, currentLoopConfig, loopConfig, separators, line, loopLines); + loopConfig = getMatchedLoop(_separators.splitElement(line), currentLoopConfig.getLoopId()); + lastLoopStored = appendEndingSegment(lastLoopStored, currentLoopConfig, loopConfig, _separators, line, loopLines); if (lastLoopStored == null || !_definition.getLoop().getXid().equals(lastLoopStored.getId())) _fatalErrors.add("Unable to find end of transaction"); } @@ -359,8 +395,8 @@ else if (loop.getLoop() != null) return requiredChildList; } - private boolean checkVersionsAreConsistent(FileType type, Separators separators, Reader reader) throws IOException { - if (reader == null || separators == null || type == null) + private boolean checkVersionsAreConsistent(Separators separators, Reader reader) throws IOException { + if (reader == null || separators == null || _type == null) return false; char segmentSeparator = separators.getSegment(); @@ -382,10 +418,10 @@ private boolean checkVersionsAreConsistent(FileType type, Separators separators, } reader.reset(); - boolean result = _TYPES.get(type).equals(version); + boolean result = _TYPES.get(_type).equals(version); if (!result) - _errors.add("ANSI version " + version + " not consistent with version specified " + type); + _errors.add("ANSI version " + version + " not consistent with version specified " + _type); return result; } @@ -400,18 +436,24 @@ private Separators getSeparators(Reader reader) throws IOException { reader.mark(1); char[] firstLine = new char[_ISA_LENGTH]; int ret = reader.read(firstLine); - boolean isAlphaNumeric = Character.isDigit(firstLine[_SEGMENT_SEPARATOR_POS]) || Character.isDigit(firstLine[_ELEMENT_SEPARATOR_POS]) || Character.isDigit(firstLine[_COMPOSITE_SEPARATOR_POS]) - || - Character.isLetter(firstLine[_SEGMENT_SEPARATOR_POS]) || Character.isLetter(firstLine[_ELEMENT_SEPARATOR_POS]) || Character.isLetter(firstLine[_COMPOSITE_SEPARATOR_POS]); - - boolean isWhiteSpace = Character.isWhitespace(firstLine[_SEGMENT_SEPARATOR_POS]) || Character.isWhitespace(firstLine[_ELEMENT_SEPARATOR_POS]) || Character.isWhitespace( - firstLine[_COMPOSITE_SEPARATOR_POS]); + boolean isAlphaNumeric = Character.isDigit(firstLine[_SEGMENT_SEPARATOR_POS]) || + Character.isDigit(firstLine[_ELEMENT_SEPARATOR_POS]) || + Character.isDigit(firstLine[_COMPOSITE_SEPARATOR_POS]) || + Character.isLetter(firstLine[_SEGMENT_SEPARATOR_POS]) || + Character.isLetter(firstLine[_ELEMENT_SEPARATOR_POS]) || + Character.isLetter(firstLine[_COMPOSITE_SEPARATOR_POS]); + + boolean isWhiteSpace = Character.isWhitespace(firstLine[_SEGMENT_SEPARATOR_POS]) || + Character.isWhitespace(firstLine[_ELEMENT_SEPARATOR_POS]) || + Character.isWhitespace(firstLine[_COMPOSITE_SEPARATOR_POS]); if (ret != _ISA_LENGTH || (isAlphaNumeric || isWhiteSpace)) { _errors.add("Error getting separators"); return null; } // don't need to reset the reader---we need to check the version on the next line - return new Separators(firstLine[_SEGMENT_SEPARATOR_POS], firstLine[_ELEMENT_SEPARATOR_POS], firstLine[_COMPOSITE_SEPARATOR_POS]); + return new Separators(firstLine[_SEGMENT_SEPARATOR_POS], + firstLine[_ELEMENT_SEPARATOR_POS], + firstLine[_COMPOSITE_SEPARATOR_POS]); } /** @@ -996,7 +1038,7 @@ private List getRequiredElementPositions(SegmentDefinition seg) { /** * Returns the positions that must have composite data in them in a segment - * @param seg segmetn format we want to to know the required element composites for + * @param seg segment format we want to to know the required element composites for * @return list of positions that have required composites */ private List getRequiredCompositePositions(SegmentDefinition seg) { @@ -1011,4 +1053,13 @@ private List getRequiredCompositePositions(SegmentDefinition seg) { return requiredPositions; } + + private String toX12StringImpl() { + StringBuilder builder = new StringBuilder(); + for (Loop loop : _dataLoops) { + builder.append(loop.toX12String(_definition.getLoop())); + builder.append(_separators.getLineBreak().getLineBreakString()); + } + return builder.toString(); + } } diff --git a/src/test/java/com/imsweb/x12/reader/X12ReaderTest.java b/src/test/java/com/imsweb/x12/reader/X12ReaderTest.java index b2244f0..f2f2354 100644 --- a/src/test/java/com/imsweb/x12/reader/X12ReaderTest.java +++ b/src/test/java/com/imsweb/x12/reader/X12ReaderTest.java @@ -1,5 +1,11 @@ package com.imsweb.x12.reader; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -9,19 +15,15 @@ import java.nio.charset.StandardCharsets; import java.util.List; +import org.apache.commons.io.IOUtils; import org.junit.Assert; import org.junit.Test; +import com.imsweb.x12.LineBreak; import com.imsweb.x12.Loop; import com.imsweb.x12.mapping.TransactionDefinition; import com.imsweb.x12.reader.X12Reader.FileType; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - public class X12ReaderTest { @Test @@ -46,12 +48,59 @@ public void testConstructors() throws IOException { assertEquals(fromFileUtf8.getLoops().get(0).toString(), fromReaderUtf8.getLoops().get(0).toString()); } + /** + * Here we will test that you can go from x12, make changes, then serialize the + * x12 once again. + */ + @Test + public void testSerializeBasic() throws IOException { + URL url = this.getClass().getResource("/837_5010/x12_valid.txt"); + + X12Reader fromFileUtf8 = new X12Reader(FileType.ANSI837_5010_X222, new File(url.getFile()), + StandardCharsets.UTF_8); + + String expected = IOUtils + .toString(this.getClass().getResourceAsStream("/837_5010/x12_valid.txt"), StandardCharsets.UTF_8) + .trim(); + LineBreak lineBreak; + if (expected.contains(LineBreak.CRLF.getLineBreakString())) { + lineBreak = LineBreak.CRLF; + } + else { + lineBreak = LineBreak.LF; + } + Assert.assertEquals(expected, fromFileUtf8.toX12String(lineBreak).trim()); + } + + /** + * Test a more complex x12 doc and see if we can serialize it. + */ + @Test + public void testSerializeComplex() throws IOException { + URL url = this.getClass().getResource("/837_5010/x12_complex.txt"); + + X12Reader fromFileUtf8 = new X12Reader(FileType.ANSI837_5010_X222, new File(url.getFile()), + StandardCharsets.UTF_8); + + String expected = IOUtils + .toString(this.getClass().getResourceAsStream("/837_5010/x12_complex.txt"), StandardCharsets.UTF_8) + .trim(); + + LineBreak lineBreak; + if (expected.contains(LineBreak.CRLF.getLineBreakString())) { + lineBreak = LineBreak.CRLF; + } + else { + lineBreak = LineBreak.LF; + } + Assert.assertEquals(expected, fromFileUtf8.toX12String(lineBreak).trim()); + } + @Test public void testMultipleGSLoops() throws Exception { URL url = this.getClass().getResource("/837_5010/x12_multiple_gs.txt"); X12Reader reader = new X12Reader(FileType.ANSI837_5010_X222, new File(url.getFile())); validateMultipleGSLoops(reader.getLoops().get(0)); - } @Test