The shell main program functions are very similar to those of the other XML to legacy format utilities. We do, however, add calls (1) before processing all documents to write the X12 interchange and group header segments, and (2) after finishing to write the trailer segments.
Logic for the Shell Main Routine for XML to X12
Arguments: Input Directory Name Output X12 Interchange Name File Description XML Document Name Options: Validate input Help Validate and process command line arguments IF help option specified Display help message Exit ENDIF Open output file Create new X12TargetConverter object, passing: Output Stream File Description Document Name Set up implementation dependent DOM environment for loading, parsing, and validating input documents Open input directory Get first file from input directory Call X12TargetConverter writeHeaders method DO for all files in input directory Input Document <- Load input file, validating it if requested Call X12TargetConverter processDocument method, passing the Input Document Increment number of documents processed ENDDO Call X12TargetConverter writeTrailers method Close output file Display completion message with number of documents processed
X12TargetConverter Class (Extends TargetConverter)
The X12TargetConverter is again very similar to the CSV and flat file target converters, but again we add some complexities due to the grammar differences and the requirement to create the X12 control segments.
The constructor method sets up the X12TargetConverter object and the X12 RecordWriter object.
Logic for the X12TargetConverter Constructor Method
Arguments: Output Stream X12 Interchange Output file String File Description Document Name Call base class constructor, passing File Description Document Name Create X12RecordWriter object, passing: File Description DOM Document and Output Stream
As was the case with the X12SourceConverter's processFile method, we aren't able to simply call the base TargetConverter class's processGroup method to perform the bulk of the required processing. Because of the way we have defined the grammar of X12 transaction sets, the first segment after the transaction set header isn't always required and we can't treat the document as just another group. We must include some of the same types of operations that we have in the main loop of the processGroup method, then call that method when we actually do need to process a group. We can use the processGroup method from the Target Converter base class.
Logic for the X12TargetConverter processDocument Method
Arguments: DOM Document Input Document Returns: Error status or throws exception Root Element <- Get Document's documentElement attribute Root Element Name <- Get Root Element's tagName attribute IF Root Element Name != Grammar Root Element Name Return error ENDIF Call X12RecordWriter's writeST method to write Transaction Set Header segment Segment Grammar Node <- Get firstChild from Grammar Element skipping over non-Element Nodes Segment Grammar Element <- Segment Grammar Node Grammar Tag <- Call Segment Grammar's getAttribute for "ElementName" Segment Element <- Get first childNode from Root Element, skipping over non-Element Nodes DO until all child Elements of the Root have been processed Segment Name <- call Segment Element's getNodeName DO until Grammar Name = Segment Name Segment Grammar Element <- Get next Segment Element from Grammar, skipping over non-Element Nodes IF Segment Grammar Element is NULL Return error // This record is not part of the document ENDIF Grammar Name <- Call Segment Grammar's getAttribute for "ElementName" ENDDO Grammar Element Name <- Call Segment Grammar Element's getNodeName IF Grammar Element Name = "GroupDescription" Call processGroup, passing Record Element and Record Grammar Element ELSE Call X12RecordWriter parseRecord, passing Record Element and Record Grammar Element Call X12RecordWriter writeRecord, passing Record Grammar Element ENDIF Record Element <- Get Record Element's nextSibling, skipping over non-Element Nodes ENDDO Call X12RecordWriter's writeSE method to write Transaction Set Trailer Segment Return success
This method calls the X12RecordWriter's methods to write the interchange and group header control segments. This and the writeTrailers methods are included here because the main program knows nothing of the X12RecordWriter class. Its methods to write header and trailer segments can be invoked only by a call to one of the X12TargetConverter's methods.
Logic for the X12TargetConverter writeHeaders Method
Arguments: None Returns: Status or throws exception Call X12RecordWriter writeISA Call X12RecordWriter writeGS Return success
This method calls the X12RecordWriter's methods to write the interchange and group trailer control segments.
Logic for the X12TargetConverter writeTrailers Method
Arguments: Integer Number of Included Transaction Sets Returns: Status or throws exception Call X12RecordWriter writeGE Call X12RecordWriter writeIEA Return success
EDIRecordWriter Class (Extends RecordWriter)
Our analysis showed us that there were sufficient commonalities among the possible RecordReader classes for EDI to XML conversions (one derived class for each specific EDI syntax) to create a base EDIRecordReader class and put the common functionality into it. We have the same situation with the RecordWriter derived classes used to process EDI formats as the target of our conversions. So, in this class we develop some common functions for converting from XML to generic EDI segments, maintaining control information that will be used by the derived classes. Again, we build in preliminary support for the repetition separator and the release character, even though they aren't implemented in version 004010 of X12.
The writeEDIControl method writes the updated EDI Control document to the EDIControl.xml file in BBHOME. It is fairly simple and very dependent on the DOM API implementation, so I won't discuss it further below.
When we read EDI we parse the input to determine the delimiters. However, when we write it we must derive the delimiters from a data store. The primary purpose of this method is to get the delimiters from the file description document. In addition, the constructor method loads the EDIControl.xml file for sequence number generation.
Logic for the EDIRecordWriter Constructor Method
Arguments: DOM Document File Description Document Output Stream Call RecordWriter base class constructor, passing File Description Document and Output Stream Delimiters Element <- Call File Description Document's getElementsByTagName for "Delimiters" Segment Terminator Element <- call Delimiters getElementsByTagName for "SegmentTerminator" Delimiter Value = call Segment Terminator's getAttribute for "value" Record Terminator1 <- Call setDelimiter, passing Delimiter Value Element Separator Element <- call Delimiters getElementsByTagName for "SegmentTerminator" Delimiter Value = call Element Separator Element's getAttribute for "value" Element Separator <- Call setDelimiter, passing Delimiter Value Component Separator Element <- call Delimiters getElementsByTagName for "ComponentSeparator" Delimiter Value = call Component Separator Element's getAttribute for "value" Component Separator <- Call setDelimiter, passing Delimiter Value Repetition Separator Element <- call Delimiters getElementsByTagName for "RepetitionSeparator" IF Repetition Separator Element is not null Delimiter Value = call Repetition Separator Element's getAttribute for "value" Repetition Separator <- Call setDelimiter, passing Delimiter Value ENDIF Release Character Element <- call Delimiters getElementsByTagName for "ReleaseCharacter" IF Release Character Element is not null Delimiter Value = call Release Character Element's getAttribute for "value" Release Character <- Call setDelimiter, passing Delimiter Value ENDIF BBHomePath <- from system property or environment variable for BBHome EDIControlPath <- BBHomePath + "EDIControl.xml" Call implementation dependent methods to load EDIControl Document
This method is very similar to the parseRecord method of the RecordReader base class, although again we add some functionality that is solely needed for processing the EDI representations of composite data structures. Rather than confuse the base class method I have created a more specialized method for the derived class. For Babel Blaster 1.0 this might be moved to the base class if design review and testing verify that it will work fine for other legacy formats.
Logic for the EDIRecordWriter parseRecord Method
Arguments: DOM Element Record DOM Element Record Grammar Returns: Void Field Grammar <- Get first Grammar child Element from Record Grammar Element, skipping over non-Element nodes Grammar Field Name <- call Field Grammar's getAttribute on "ElementName" DO for all Field Elements that are children of Record Element // Test for a composite data structure Composite <- False childNode <- get Field Element's firstChild DO until childNode is null or Composite is true IF childNode nodeType = ELEMENT Composite <- true ELSE childNode <- childNode's nextSibling ENDIF ENDDO IF Composite is false Element Text <- call getElement Text on Field Element IF Element Text is empty Proceed to next field ENDIF ENDIF Field Name <- Field Element's tagName attribute DO UNTIL Field Name == Grammar Field Name or Grammar Element is null Get next sibling Field Grammar Element, skipping over non-Element Nodes Grammar Field Name <- Field Element's tagName attribute ENDDO IF Grammar Element is null Return error ENDIF IF Composite is true Call base class parseRecord, passing Field Element and Grammar Element ELSE Field Number <- Call Field Grammar's getAttribute on "FieldNumber" New Cell <- call createDataCell, passing Field Number and Grammar New Cell <- call New Cell's putField, passing Element Text ENDIF ENDDO
Note that we don't need a special method to parse the XML representation of a composite data element. We can use the base class's parseRecord method because the structure of a composite data element's XML representation and grammar are the same as records in our other legacy formats.
Since we can identify a grammar that applies to the segments of most EDI syntaxes, we can develop this common method to write such segments. The overall logic flow is very similar to that of the CSVRecordWriter since both deal with delimited record formats. As with that method we walk the DataCell Array. For this method we must keep track of a few more pieces of information so that we write the correct delimiters. The Element Position counter keeps track of data element position within the output segment. The Component Position counter keeps track of component data element position within a composite data structure. The Previous Element Position indicates whether or not we have a repeating data element.
Logic for the EDIRecordWriter writeRecord Method
Arguments: DOM Element Segment Grammar Returns: Error status or throws exception Initialize Output Record Buffer to null Segment Tag <- Call Segment Grammar's getAttribute for "TagValue" Output Buffer <- Segment Tag Set Buffer Length to Tag length Element Position <- 0 Previous Element Position <- 0 Component Position <- 1 DO for Index from 0 through Highest Cell // Test for new element Cell Position = Get Cell's Field Number // Reset to start new composite if needed IF Element Position != Cell Position Component Position <- 1 ENDIF // Write preceding data element separators DO while Element Position < Cell Position Append Element Separator to Record Buffer Increment Element Position ENDDO // Write preceding repetition separator // NOTE - An additional test is needed here to write repeating // composite data structures correctly IF Element Position = Previous Element Position Append Repetition Separator to Record Buffer ENDIF Previous Element Position = Element Position // Write preceding component data element separators Cell SubPosition = Get Cell's SubField Number DO while Component Position < Cell SubPosition Append Component Separator to Record Buffer Increment Component Position ENDDO Call fromXML to convert Cell contents from XML data type Call prepareOutput to format Cell contents for EDI output Cell Contents <- Call getField to retrieve Cell Buffer contents Clear Array entry Append Cell Contents to Record Buffer ENDDO Highest Cell <- -1 Append base RecordHandler's Record Terminator1 to Record Buffer Call language's write routines to do physical write of Record Buffer Increment Segment Count Return success
Again, this version of the method doesn't fully support composite data structures when used as repeating data elements. A Babel Blaster 1.0 requirement is to widen support beyond the demands of X12 version 004010.
X12RecordWriter Class (Extends EDIRecordWriter)
The X12RecordWriter is derived from the EDIRecordWriter. It handles the specialized requirements of writing X12 control segments.
As with the case of the X12RecordReader class, only a few of these methods are very significant. The constructor method doesn't do anything except call the base class constructor. The methods that write transaction set and group trailer segments, writeGE and writeSE, are simple methods that write the number of included components , followed by the control number that was written in the header segment. The methods that write the header segments and the interchange trailer are worthy of a bit more discussion. I'll discuss them below, starting with the outer control segments of the interchange, moving inward.
Based on trading partner information in the file description document, this method looks up the last used interchange control number from the EDIControl.xml data store. The method increments the control number, updates the value in EDIControl, and calls language and system dependent functions to get the current date and time for the ISA and GS segments. The method then populates the rest of the ISA segment from other Elements in the file description document and writes the ISA segment.
Logic for the X12RecordWriter writeISA Method
Arguments: None Returns: Status or throws exception ISA Info <- Call File Description Document's GetElementsByTagName for "ISAInformation" ISA ReceiverID <- Call ISAInfo's getElementsByTagName for "ReceiverID", then getAttribute for "value" X12 Control <- Call EDI Control document's getElementsByTagName for "X12OutboundControl" TP NodeList <- Call X12 Control's getElementsByTagName for "TradingPartner" DO for all entries in TP NodeList or until Control Receiver ID = ISA Receiver ID Control Receiver ID <- call TP NodeList item's getAttribute for "ReceiverID" ENDDO IF no match found TP Element <- call EDI Control's createElement method using Element name of "TradingPartner" X12 Control <- call X12 Control's appendChild to attach TP Element Interchange Number <- 1 Call TP Element's setAttribute for "ReceiverID", passing ISA Receiver ID as value to set ELSE Interchange Number <- Call TP Element's getAttribute for "LastInterchangeNumber" Increment Interchange Number ENDIF Call TP Element's setAttribute for "LastInterchangeNumber", passing Interchange Number as value to set Date <- Get date from system, and reformat to YYMMDD Time <- Get time from system, and reformat to HHMM Get remaining values from ISA Info Element and pad to required Lengths Initialize Record Buffer Build ISA Segment in Record Buffer with "ISA" segment tag, values from ISA Info Element, Date, Time, and Delimiters from base class members, appending Record Terminator1 as segment terminator Write Record Buffer using language-specific methods Return success
We could, perhaps, use a more sophisticated method for looking up the trading partner in EDIControl than the algorithm in this method. However, I don't see significant performance gains in using something like XQuery for the number of entries likely to exist in this document. Also, we would be complicating the code by adding yet another type of technology.
This method retrieves the last group control number used from the EDIControl file and populates the remainder of the segment from values from the GSInformation Element in the file description document. It also initializes the transaction set count.
Logic for the X12RecordWriter writeGS Method
Arguments: None Returns: Status or throws exception Transaction Set Count <- 0 GSInfo <- Call File Description Document's getElementsByTagName for "GSInformation" GSReceiverID <- Call GSInfo's getElementsByTagName for "ApplicationReceiversCode", then getAttribute for "value" GSType <- Call GSInfo's getElementsByTagName for "FunctionalIDCode", then getAttribute for "value" GroupNodeList <- Call TP Element's getElementsByTagName for "FunctionalGroup" DO for all entries in GroupNodeList or until ControlReceiverID = GSReceiverID and ControlType = GSType ControlReceiverID <- call GroupNodeList item's getAttribute for "ReceiverID" ControlType <- call GroupNodeList item's getAttribute for "FunctionalIDCode" ENDDO IF no match found GroupElement <- call EDI Control's createElement method using Element name of "FunctionalGroup" TP Element <- call TP Element's appendChild to attach GroupElement Group Number <- 1 Call GroupElement's setAttribute for "ReceiverID", passing GSReceiverID as value to set Call GroupElement's setAttribute for "FunctionalIDCode", passing GSType as value to set ELSE Group Number <- Call Group Element's getAttribute for "LastGroupNumber" Increment Group Number ENDIF Call Group Element's setAttribute for "LastGroupNumber", passing Group Number as value to set Get remaining values from GSInfo Element Initialize Record Buffer Build GS Segment in Record Buffer with "GS" segment tag, values from GSInfo Element, Date, Time, and Delimiters from base class members, appending RecordTerminator1 as segment terminator Write Record Buffer using language-specific methods Return success
This method is quite a bit simpler that the writeISA and writeGS methods since we start a new sequence of transaction set control numbers with each new functional group we write. We need to retrieve only one value from the file description document.
Logic for the X12RecordWriter writeST Method
Arguments: None Returns: Status or throws exception Increment Transaction Set Count Pad Transaction Set Count to four digits with leading zeroes if required STInfo <- Call File Description Document's getElementsByTagName for "STInformation" STType <- Call STInfo's getAttribute for "TransactionSetIDCode" Initialize Record Buffer Build ST Segment in Record Buffer with "ST" segment tag, STType, and Transaction Set Count, using Element Separator from base class members and appending RecordTerminator1 as segment terminator Write Record Buffer using language-specific methods Segment Count <- 1 Return success
Writing the trailer segment is pretty simple since we write only two values. However, for control number integrity we don't save the EDIControl document until after the interchange trailer is successfully written.
Logic for the X12RecordWriter writeIEA Method
Arguments: None Returns: Status or throws exception Initialize Record Buffer Build IEA Segment in Record Buffer with "IEA" segment tag, "1" included functional group, and Interchange Control Number, using Element Separator from base class members and appending Record Terminator1 as segment terminator Write Record Buffer using language-specific methods Save EDI Control Document to EDI Control File Path Return success