Getting Started with X12Reader
Requirements:
IPWorks EDI
Introduction
IPWorks EDI and IPWorks EDI Translator include components for reading, and writing X12 documents. This article will focus on using the X12Reader component to parse EDI (X12) documents.
Contents
Loading a Schema
The X12Reader component is designed to work with multiple schema formats. A schema allows the component to validate the EDI document while parsing and provide additional details about the structure of the document, such as loops. Multiple schema formats are supported:
- Altova - http://www.altova.com/
- BizTalk (.xsd) - http://msdn.microsoft.com/en-us/library/aa559426(v=BTS.70).aspx
- TIBCO Standard Exchange Format (SEF) - https://docs.tibco.com/products/tibco-foresight-edisim-6-18-0
- JSON (.json)
If the document type is known ahead of time the LoadSchema method may be called before loading the file. For instance:
//Load the X12 810 schema
string schemaFile = @"C:\Schemas\x12_schemas\00401\00401_810.json";
X12reader x12reader = new X12reader();
x12reader.SchemaFormat = X12readerSchemaFormats.schemaJSON;
x12reader.LoadSchema(schemaFile);
If the document type is not known ahead of time, then the appropriate schema can be determined when ParseFile is called. In this case the ResolveSchema event will fire with information about the TransactionCode and StandardVersion which may then be used to call LoadSchema from within the ResolveSchema event. For instance:
//Load the X12 810 schema from within the ResolveSchema event.
x12reader.OnResolveSchema += (s, e) => {
Console.WriteLine("Standard Version: " + e.StandardVersion);
Console.WriteLine("Transaction Code: " + e.TransactionCode);
if (e.StandardVersion == "004010" && e.TransactionCode == "810") //Load the 810 schema
x12reader.LoadSchema(schemaFile);
};
x12reader.InputFile = x12_810_File;
x12reader.Parse();
Once a schema is loaded and the document is parsed the XPath properties and methods of the component can be used to navigate the document and get information.
Parsing the Document
EDI data may be parsed by either calling ParseFile to parse an existing file, or calling Input to parse a string. As a document is parsed various events will fire with information about the current progress. These events include:
- StartInterchange
- StartFunctionalGroup
- StartTransaction
- Segment
- StartLoop
- EndLoop
- EndTransaction
- EndFunctionalGroup
- EndInterchange
During parsing the component will validate the document. If validation fails the Warning event will fire with details about the error. Please see the documentation of the Warning event for more information.
Document Navigation
After the document is parsed the XPath property can be set to navigate within the document. Additionally the methods HasXPath and TryXPath can be used to check the existence of an XPath or navigate to an XPath only if it exists. For example:
x12reader.XPath = "/IX[1]/FG[1]/TX[2]/N1Loop1[1]/N1[1]";
This example path means the following: Select the first N1 segment within the first iteration of the N1Loop1, within second transaction in the first functional group and interchange.
You can also make use of XPath conditional statements to locate the first element which matches a name=value. For example, you could use the following XPath to locate the path of the first element within any N1Loop1 that has a name=N101 and value=BT:
x12reader.XPath = "IX[1]/FG[1]/TX[1]/N1Loop1[N101='BT']";
Note that the conditional statements will search the children, but not the grand children of the element on which the conditional statement is applied. For instance in the above example the children of N1Loop1 will be searched, but the grandchildren will not.
XPath can be set to an absolute path (begins with '/') or a relative path to the current XPath location. The following are possible values for an element accessor:
IX | Refers to the Interchange (root) node |
FG | Refers to a Functional Group node |
TX | Refers to a Transaction Set node |
'name' | The first segment or loop of the current container with the given schema name |
name[i] | The i-th segment of the current container with the given schema name |
[i] | The i-th segment of the current container |
[last()] | The last segment of the current container |
[last()-i] | The segment located at the last location minus i in the current container |
.. | The parent of the current container |
After setting XPath the following properties are populated to provide information about the selected path:
XChildren | The number of children of the current XPath |
XElements | A collection of values representing the elements of the segment |
XSegment | The name of the segment |
XSegmentNumber | The number of the segment |
XSegmentType | The type of the segment (Transaction, Segment, Loop, etc.) |
XTag | The tag of the current segment |
XTransactionCode | The transaction code of the current segment |
Basic XPath Usage
This section provides some basic examples of working with XPath and related properties to navigate and obtain information from a document. This section uses the following X12 810 document:
ISA*00* *00* *ZZ*ACME *ZZ*WAYNE_TECH *160707*1544*U*00401*000000006*0*T*>~
GS*IN*ACME*WAYNE_TECH*20160707*1544*6*T*004010~
ST*810*0001~
BIG*20150708*3003014445**0476553272***DR~
CUR*SE*USD~
REF*8M*0056~
N1*BY*Company*92*544380~
N3*Address~
N4*City*CA*Postal Code~
N1*ST*Name*92*0607047800010~
N3*Address~
N4*City**200131*US~
N1*RE*Name*92*5095956~
N3*Address~
N4*City*IL*Postal Code~
IT1*20*2500*EA*36.96**BP*335S0594~
REF*KK*0099778154~
REF*PO*0476553272*20~
TDS*9240000~
CTT*1~
SE*19*0001~
GE*1*6~
IEA*1*000000006~
In some cases the document structure is known ahead of time and a XPath value can be easily created. However, in other cases it's useful to see the schema structure of the document to get information about what XPath values may exist. To display schema information from the component call the DisplaySchemaInfo method. For instance:
x12reader.LoadSchema(schemaFile);
Console.WriteLine(x12reader.DisplaySchemaInfo());
This will output a schema structure like:
ST[0,1]
BIG[0,1]
NTE[0,100]
CUR[0,1]
REF[0,12]
YNQ[0,10]
PER[0,3]
N1Loop1[0,200]
N1[0,1]
N2[0,2]
N3[0,2]
N4[0,1]
REF_2[0,12]
PER_2[0,3]
DMG[0,1]
ITD[0,999999]
DTM[0,10]
FOB[0,1]
PID[0,200]
MEA[0,40]
PWK[0,25]
PKG[0,25]
L7[0,1]
BAL[0,999999]
INC[0,1]
PAM[0,999999]
LMLoop1[0,10]
LM[0,1]
LQ[0,100]
N9Loop1[0,1]
N9[0,1]
MSG[0,10]
V1Loop1[0,999999]
V1[0,1]
R4[0,999999]
DTM_2[0,999999]
FA1Loop1[0,999999]
FA1[0,1]
FA2[0,999999]
IT1Loop1[0,200000]
IT1[0,1]
CRC[0,1]
QTY[0,5]
CUR_2[0,1]
IT3[0,5]
TXI[0,10]
CTP[0,25]
PAM_2[0,10]
MEA_2[0,40]
PIDLoop1[0,1000]
PID_2[0,1]
MEA_3[0,10]
PWK_2[0,25]
PKG_2[0,25]
PO4[0,1]
ITD_2[0,2]
REF_3[0,999999]
YNQ_2[0,10]
PER_3[0,5]
SDQ[0,500]
DTM_3[0,10]
CAD[0,999999]
L7_2[0,999999]
SR[0,1]
SACLoop1[0,25]
SAC[0,1]
TXI_2[0,10]
SLNLoop1[0,1000]
SLN[0,1]
DTM_4[0,1]
REF_4[0,999999]
PID_3[0,1000]
SAC_2[0,25]
TC2[0,2]
TXI_3[0,10]
N1Loop2[0,200]
N1_2[0,1]
N2_2[0,2]
N3_2[0,2]
N4_2[0,1]
REF_5[0,12]
PER_4[0,3]
DMG_2[0,1]
LMLoop2[0,10]
LM_2[0,1]
LQ_2[0,100]
V1Loop2[0,999999]
V1_2[0,1]
R4_2[0,999999]
DTM_5[0,999999]
FA1Loop2[0,999999]
FA1_2[0,1]
FA2_2[0,999999]
TDS[0,1]
TXI_4[0,10]
CAD_2[0,1]
AMT[0,999999]
SACLoop2[0,25]
SAC_3[0,1]
TXI_5[0,10]
ISSLoop1[0,999999]
ISS[0,1]
PID_4[0,1]
CTT[0,1]
SE[0,1]
To output a representation of the current file call the DisplayXMLInfo method after calling ParseFile. Calling DisplayXMLInfo will return the structure of the current document represented as XML. For instance:
x12reader.LoadSchema(schemaFile);
Console.WriteLine(x12reader.DisplayXMLInfo());
Will output a string like:
This information provides a starting place if there is no prior knowledge about the document structure.
Note: If XML translation is desired a separate EDI Translator component is included in the toolkit specifically for that task.
When setting XPath, examining the current segment's elements is a common task. To select this segment from the example document:
BIG*20150708*3003014445**0476553272***DR~
The following code can be used:
x12reader.XPath = "/IX/FG/TX/BIG";
for (int i = 0; i < x12reader.XElements.Count; i++)
{
Console.WriteLine(x12reader.XElements[i].Name + ": " + x12reader.XElements[i].Value);
}
Which will output:
BIG01: 20150708
BIG02: 3003014445
BIG03:
BIG04: 0476553272
BIG05:
BIG06:
BIG07: DR
The XElements[i].DataType property provides information about the element's data type which may be useful for interpreting the value. For instance:
x12reader.XPath = "/IX/FG/TX/BIG";
for (int i = 0; i < x12reader.XElements.Count; i++)
{
Console.WriteLine(x12reader.XElements[i].Name + "[" + x12reader.XElements[i].DataType + "]: " + x12reader.XElements[i].Value);
}
Outputs:
BIG01[DT]: 20150708
BIG02[AN]: 3003014445
BIG03[DT]:
BIG04[AN]: 0476553272
BIG05[AN]:
BIG06[AN]:
BIG07[None]: DR
Possible DataType values are:
AN | AlphaNumeric |
ID | Identifier; allowed values might be defined by the transaction set schema |
N | Numeric |
R | Floating-point number |
DT | DateTime |
TM | Time |
None | Type is unknown or not provided by the schema |
Composite | This element has multiple components |
Using Schema Information
The X12Reader component provides Element specific fields which provide additional information about the element. This additional information includes the element name as taken from the Schema Id, and a textual description of the element. The following fields provide information obtained from the schema:
- XElements.SchemaName
- XElements.SchemaDesc
- XElements.ComponentSchemaName
- XElements.ComponentSchemaDesc
- XElements.SchemaDesc
Name holds positional (ref) value like "N101". SchemaName holds the Id taken from the schama.
For instance:
x12reader.XPath = "/IX/FG/TX/BIG";
for (int i = 0; i < x12reader.XElements.Count; i++)
{
Console.WriteLine(x12reader.XElements[i].SchemaName + "[" + x12reader.XElements[i].SchemaDesc + "]: " + x12reader.XElements[i].Value);
}
When SchemaName and SchemaDesc are output this will use the name and description from the schema and will result values like:
373[Date]: 20150708
76[Invoice Number]: 3003014445
373[Date]:
324[Purchase Order Number]: 0476553272
328[Release Number]:
327[Change Order Sequence Number]:
640[Transaction Type Code]: DR
In contrast, if Name was used the output would look like:
BIG01: 20150708
BIG02: 3003014445
BIG03:
BIG04: 0476553272
BIG05:
BIG06:
BIG07: DR
Note: These fields are only applicable when a JSON schema is loaded.
Working with Loops
Within this particular document there are multiple N1 Loops representing the buying party (BY), the party to receive remittance (RE) and the party to which the shipment will go (ST). This is the section of the document holding these values:
N1*BY*Company*92*544380~
N3*Address~
N4*City*CA*Postal Code~
N1*ST*Name*92*0607047800010~
N3*Address~
N4*City**200131*US~
N1*RE*Name*92*5095956~
N3*Address~
N4*City*IL*Postal Code~
To select the first instance of the N1 Loop set:
x12reader.XPath = "/IX/FG/TX/N1Loop1[1]";
Console.WriteLine("Elements: " + x12reader.XElements.Count);
Console.WriteLine("Children: " + x12reader.XChildren);
Console.WriteLine("Segment: " + x12reader.XSegment);
Console.WriteLine("Segment Type: " + x12reader.XSegmentType);
Which outputs:
Elements: 0
Children: 3
Segment: N1Loop1
Segment Type: stTransactionLoop
In this case there are no elements because this is the XPath to the first instance of the N1 Loop itself. To access the individual segments within the loop the child segments of the loop must be accessed. For instance:
x12reader.XPath = "/IX/FG/TX/N1Loop1[1]/N1";
for (int i = 0; i < x12reader.XElements.Count; i++)
{
Console.WriteLine(x12reader.XElements[i].Name + ": " + x12reader.XElements[i].Value);
}
Outputs:
N101: BY
N102: Company
N103: 92
N104: 544380
Accessing other instances of the loop can be done simply by incrementing the index used N1Loop[1] part of the XPath. For instance:
x12reader.XPath = "/IX/FG/TX/N1Loop1[3]/N1";
for (int i = 0; i < x12reader.XElements.Count; i++)
{
Console.WriteLine(x12reader.XElements[i].Name + ": " + x12reader.XElements[i].Value);
}
Outputs:
N101: RE
N102: Name
N103: 92
N104: 5095956
To select a specific N1 Loop based on the value of one of the elements within the loop, a conditional XPath can be specified. For instance if the desired N1 Loop contains:
N1*ST*Name*92*0607047800010~
The following code can be used to select the specific N1 Loop where the party is the shipment destination (ST):
x12reader.Config("ResolveXPathOnSet=true");
x12reader.XPath = "/IX/FG/TX/N1Loop1[N101='ST']";
Console.WriteLine(x12reader.XPath);
Note the conditional part of the XPath N1Loop1[N101='ST'] uses the element id and desired element value. When using the JSON schemas the element may also be referred by its name as well (in this case "98"). For instance:
x12reader.XPath = "/IX/FG/TX/N1Loop1[98='ST']";
The configuration setting ResolveXPathOnSet is used so when querying the XPath property, a resolved XPath without the original conditional statement is returned. This code will output:
/IX[1]/FG[1]/TX[1]/N1Loop1[2]
Now that the desired loop XPath is known, it may be used in the construction of additional XPath values. Alternatively, the conditional statement may be included as part of an XPath to a specific child. For instance, to both select the N1 Loop where for the shipment destination (ST) and access the N4 segment within the loop, the following code can be used:
x12reader.XPath = "/IX/FG/TX/N1Loop1[N101='ST']/N4";
for (int i = 0; i < x12reader.XElements.Count; i++)
{
Console.WriteLine(x12reader.XElements[i].Name + ": " + x12reader.XElements[i].Value);
}
Which outputs:
N401: City
N402:
N403: 200131
N404: Country
HasXPath and TryXPath
The component includes a HasXPath method which can be used to determine if a specific XPath exists before attempting to navigate to it. In addition, the TryXPath method can be used to navigate to the XPath only if it exists. TryXPath returns True if the navigation was successful, False otherwise.
In the test document there is one IT1Loop:
IT1*20*2500*EA*36.96**BP*335S0594~
These methods can be used to detect how many instances of the loop exist and navigate accordingly. For instance, here are two different approaches that both output the elements for each IT1 Loop in the document
//Use HasXPath to check for the XPath before navigating
int index = 1;
while (x12reader.HasXPath("/IX/FG/TX/IT1Loop1[" + index + "]"))
{
Console.WriteLine("***** Start IT1Loop1 *****");
x12reader.XPath = "/IX/FG/TX/IT1Loop1[" + index + "]/IT1";
for (int i = 0; i < x12reader.XElements.Count; i++)
{
Console.WriteLine(x12reader.XElements[i].Name + ": " + x12reader.XElements[i].Value);
}
index++;
Console.WriteLine("***** End IT1Loop1 *****");
}
//Use TryXPath to try navigating
int index = 1;
while (x12reader.TryXPath("/IX/FG/TX/IT1Loop1[" + index + "]/IT1"))
{
Console.WriteLine("***** Start IT1Loop1 *****");
for (int i = 0; i < x12reader.XElements.Count; i++)
{
Console.WriteLine(x12reader.XElements[i].Name + ": " + x12reader.XElements[i].Value);
}
index++;
Console.WriteLine("***** End IT1Loop1 *****");
}
Both approaches will output the same values:
***** Start IT1Loop1 *****
IT101: 20
IT102: 2500
IT103: EA
IT104: 36.96
IT105:
IT106: BP
IT107: 335S0594
***** End IT1Loop1 *****
Composite and Repeat Elements
Composite and Repeat elements are identified by the component when examining the element properties.
Composite Elements
The XElement[i].Composite* properties hold information about the components of the composite element. Below is an example 846 document that contains a composite element in the QTY segment.
ISA*00* *00* *14*060704780001900*ZZ*COMPANYTEST *140902*1433*U*00401*000040157*1*T*:~
GS*IB*060704780500*TESTEDI*20130428*2033*000040157*X*004010~
ST*846*0001~
BIA*00*01*5000412255*20090320~
DTM*050*20090320*043510~
REF*PO*0476401324~
REF*DD*Document Identification Code~
REF*D2*123456789~
N1*16*Operations Center*6*LU10~
N4*Daventry***GB~
LIN*00110*MG*M9057Z/A~
PID*F****COMPANYWORKS~
QTY*87*300*SM:2:3:28~
CTT*1~
SE*13*0001~
GE*1*000040157~
IEA*1*000040157~
In this document, the QTY segment includes a composite element with the : separator:
QTY*87*300*SM:2:3:28~
When inspecting the elements of the QTY segment the XElements[i].DataType will indicate the type of element. In this case DataType will be "Composite". After identifying the composite element the XElements[i].ComponentIndex can be set to iterate over the components from 0 to XElements[i].ComponentCount. Setting XElements[i].ComponentIndex populates the XElements[i].ComponentType, XElements[i].ComponentName, and XElements[i].ComponentValue properties. For instance:
x12reader.XPath = "/IX/FG/TX/LINLoop1[1]/QTYLoop1[1]/QTY";
for (int i = 0; i < x12reader.XElements.Count; i++)
{
Console.WriteLine(x12reader.XElements[i].Name + ": " + x12reader.XElements[i].DataType + ": " + x12reader.XElements[i].Value);
if (x12reader.XElements[i].DataType == "Composite")
{
//Loop through the composite element components.
for (int compIdx = 0; compIdx < x12reader.XElements[i].ComponentCount; compIdx++)
{
x12reader.XElements[i].ComponentIndex = compIdx;
Console.WriteLine(" Component " + compIdx + ": " + x12reader.XElements[i].ComponentValue);
}
}
}
Outputs:
QTY01: None: 87
QTY02: R: 300
QTY03: Composite: SM:2:3:28
Component 0: SM
Component 1: 2
Component 2: 3
Component 3: 28
Repeat Elements
The XElement[i].RepeatCount property indicates the number of time this element is repeated in the segment. If the element is repeated set XElement[i].RepeatIndex to a value from 0 to RepeatCount to select an instance of the repeated element and examining that instance's properties. For instance if the EDI document contains a segment:
TST*TEST*1^4^8~
In this case the element at TST02 is repeated using the ^ delimiter. The following code:
x12reader.XPath = "/IX/FG/TX/TST";
for (int j = 0; j < x12reader.XElements[1].RepeatCount - 1; j++)
{
x12reader.XElements[1].RepeatIndex = j;
Console.WriteLine("Value: " + x12reader.XElements[1].Value);
}
Will output:
Value: 1
Value: 4
Value: 8
We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at kb@nsoftware.com.