Getting Started with EDIFACTReader


Requirements:
IPWorks EDI

Introduction

IPWorks EDI and IPWorks EDI Translator include components for reading, and writing EDIFACT documents. This article will focus on using the EDIFACTReader component to parse EDI (EDIFACT) documents.

Contents

  1. Loading a Schema
  2. Parsing the Document
  3. Document Navigation

Loading a Schema

The EDIFACTReader 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:

If the document type is known ahead of time the LoadSchema method may be called before loading the file. For instance:

//Load the EDIFACT INVOICE schema string schemaFile = @"C:\Schemas\edifact_schemas\D97A\D97A_INVOIC.json"; Edifactreader edifactreader = new Edifactreader(); edifactreader.SchemaFormat = EdifactreaderSchemaFormats.schemaJSON; edifactreader.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 EDIFACT INVOIC schema from within the ResolveSchema event. edifactreader.OnResolveSchema += (s, e) => { Console.WriteLine("Standard Version: " + e.StandardVersion); Console.WriteLine("Transaction Code: " + e.TransactionCode); if (e.StandardVersion == "D97A" && e.TransactionCode == "INVOIC") //Load the INVOIC schema edifactreader.LoadSchema(schemaFile); }; edifactreader.InputFile = edifact_INVOIC_File; edifactreader.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.

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:

edifactreader.XPath = "/IX[1]/TX[1]/LINLoop1[1]/LIN[1]";

This example path means the following: Select the first LIN segment within the first iteration of the LINLoop1, within first 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 LINLoop that has a name=LIN01 and value=000030:

edifactreader.XPath = "IX[1]/TX[1]/LINLoop1[LIN01='000030']";

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 LINLoop1 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:

IXRefers to the Interchange (root) node
FGRefers to a Functional Group node
TXRefers 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 EDIFACT INVOIC document:

UNA:+.?*' UNB+UNOB:1+WAYNE_TECH+ACME+160707:1547+000000002++1234++++1' UNH+509010117+INVOIC:D:97A:UN' BGM+380:::TAX INVOICE+0013550417+9' DTM+3:20070926:102' DTM+4:20061123:102' FTX+AAI+1' TAX+7+VAT+++:::10072.14+S' CUX+2:EUR:4++0.67529' PAT+1' DTM+10:20070926:102' PCD+2:0:13' LIN+000030+' PIA+1+2265S13:BP::92' PIA+1+5029766832002:UP::92' IMD+F+' QTY+47:50.000:EA' DTM+11:20070926:102' MOA+203:19150.00' PRI+INV:383.00:TU' TAX+7+VAT+++:::17.500+S' MOA+125:19150.45' ALC+C+0.45+++FC' MOA+8:0.45' LIN+000040+' PIA+1+2269F22:BP::92' PIA+1+5051254078241:UP::92' IMD+F+' QTY+47:20.000:EA' DTM+11:20070926:102' MOA+203:21060.00' PRI+INV:1053.00:TU' TAX+7+VAT+++:::17.500+S' MOA+125:21060.50' ALC+C+0.50+++FC' MOA+8:0.50' LIN+000170+' PIA+1+2269F10:BP::92' PIA+1+5051254078326:UP::92' IMD+F+' QTY+47:10.000:EA' DTM+11:20070926:102' MOA+203:6950.00' PRI+INV:695.00:TU' TAX+7+VAT+++:::17.500+S' MOA+125:6950.16' ALC+C+0.16+++FC' MOA+8:0.16' LIN+000190+' PIA+1+2269F26:BP::92' PIA+1+5051254051190:UP::92' IMD+F+' QTY+47:5.000:EA' DTM+11:20070926:102' MOA+203:2375.00' PRI+INV:475.00:TU' TAX+7+VAT+++:::17.500+S' MOA+125:2375.06' ALC+C+0.06+++FC' MOA+8:0.06' LIN+000200+' PIA+1+2265S24:BP::92' PIA+1+5029766000685:UP::92' IMD+F+' QTY+47:3.000:EA' DTM+11:20070926:102' MOA+203:957.00' PRI+INV:319.00:TU' TAX+7+VAT+++:::17.500+S' MOA+125:957.02' ALC+C+0.02+++FC' MOA+8:0.02' LIN+000210+' PIA+1+2263T95:BP::92' PIA+1+5029766699575:UP::92' IMD+F+' QTY+47:3.000:EA' DTM+11:20070926:102' MOA+203:2085.00' PRI+INV:695.00:TU' TAX+7+VAT+++:::17.500+S' MOA+125:2085.05' ALC+C+0.05+++FC' MOA+8:0.05' LIN+000250+' PIA+1+2269F15:BP::92' PIA+1+5051254080091:UP::92' IMD+F+' QTY+47:3.000:EA' DTM+11:20070926:102' MOA+203:4977.00' PRI+INV:1659.00:TU' TAX+7+VAT+++:::17.500+S' MOA+125:4977.12' ALC+C+0.12+++FC' MOA+8:0.12' UNS+S' CNT+4:7' MOA+9:67627.50' MOA+79:57554.00' TAX+7+VAT+++:::17.500+S' MOA+125:57555.36:EUR:3' MOA+124:10072.14:EUR:3' ALC+C+1.36+++FC' MOA+8:1.36' UNT+104+509010117' UNZ+1+000000002'

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:

edifactreader.LoadSchema(schemaFile); Console.WriteLine(edifactreader.DisplaySchemaInfo());

This will output a schema structure like:

UNH[0,1] BGM[0,1] DTM[0,35] PAI[0,1] ALI[0,5] IMD[0,1] FTX[0,10] LOC[0,10] GIS[0,10] DGS[0,1] RFFLoop1[0,99] RFF[0,1] DTM_2[0,5] GIR[0,5] LOC_2[0,2] MEA[0,5] QTY[0,2] FTX_2[0,5] MOA[0,2] NADLoop1[0,99] NAD[0,1] LOC_3[0,25] FII[0,5] RFFLoop2[0,9999] RFF_2[0,1] DTM_3[0,5] DOCLoop1[0,5] DOC[0,1] DTM_4[0,5] CTALoop1[0,5] CTA[0,1] COM[0,5] TAXLoop1[0,5] TAX[0,1] MOA_2[0,1] LOC_4[0,5] CUXLoop1[0,5] CUX[0,1] DTM_5[0,5] PATLoop1[0,10] PAT[0,1] DTM_6[0,5] PCD[0,1] MOA_3[0,1] PAI_2[0,1] FII_2[0,1] TDTLoop1[0,10] TDT[0,1] TSR[0,1] LOCLoop1[0,10] LOC_5[0,1] DTM_7[0,5] RFFLoop3[0,9999] RFF_3[0,1] DTM_8[0,5] TODLoop1[0,5] TOD[0,1] LOC_6[0,2] PACLoop1[0,1000] PAC[0,1] MEA_2[0,5] EQD[0,1] PCILoop1[0,5] PCI[0,1] RFF_4[0,1] DTM_9[0,5] GIN[0,5] ALCLoop1[0,9999] ALC[0,1] ALI_2[0,5] FTX_3[0,1] RFFLoop4[0,5] RFF_5[0,1] DTM_10[0,5] QTYLoop1[0,1] QTY_2[0,1] RNG[0,1] PCDLoop1[0,1] PCD_2[0,1] RNG_2[0,1] MOALoop1[0,2] MOA_4[0,1] RNG_3[0,1] CUX_2[0,1] DTM_11[0,1] RTELoop1[0,1] RTE[0,1] RNG_4[0,1] TAXLoop2[0,5] TAX_2[0,1] MOA_5[0,1] RCSLoop1[0,100] RCS[0,1] RFF_6[0,5] DTM_12[0,5] FTX_4[0,5] AJTLoop1[0,1] AJT[0,1] FTX_5[0,5] INPLoop1[0,1] INP[0,1] FTX_6[0,5] LINLoop1[0,9999999] LIN[0,1] PIA[0,25] IMD_2[0,10] MEA_3[0,5] QTY_3[0,5] PCD_3[0,1] ALI_3[0,5] DTM_13[0,35] GIN_2[0,1000] GIR_2[0,1000] QVR[0,1] EQD_2[0,1] FTX_7[0,5] DGS_2[0,1] MOALoop2[0,10] MOA_6[0,1] CUX_3[0,1] PATLoop2[0,10] PAT_2[0,1] DTM_14[0,5] PCD_4[0,1] MOA_7[0,1] PRILoop1[0,25] PRI[0,1] CUX_4[0,1] APR[0,1] RNG_5[0,1] DTM_15[0,5] RFFLoop5[0,10] RFF_7[0,1] DTM_16[0,5] PACLoop2[0,10] PAC_2[0,1] MEA_4[0,10] EQD_3[0,1] PCILoop2[0,10] PCI_2[0,1] RFF_8[0,1] DTM_17[0,5] GIN_3[0,10] LOCLoop2[0,9999] LOC_7[0,1] QTY_4[0,100] DTM_18[0,5] TAXLoop3[0,99] TAX_3[0,1] MOA_8[0,1] LOC_8[0,5] NADLoop2[0,99] NAD_2[0,1] LOC_9[0,5] RFFLoop6[0,5] RFF_9[0,1] DTM_19[0,5] DOCLoop2[0,5] DOC_2[0,1] DTM_20[0,5] CTALoop2[0,5] CTA_2[0,1] COM_2[0,5] ALCLoop2[0,30] ALC_2[0,1] ALI_4[0,5] DTM_21[0,5] FTX_8[0,1] QTYLoop2[0,1] QTY_5[0,1] RNG_6[0,1] PCDLoop2[0,1] PCD_5[0,1] RNG_7[0,1] MOALoop3[0,2] MOA_9[0,1] RNG_8[0,1] CUX_5[0,1] DTM_22[0,1] RTELoop2[0,1] RTE_2[0,1] RNG_9[0,1] TAXLoop4[0,5] TAX_4[0,1] MOA_10[0,1] TDTLoop2[0,10] TDT_2[0,1] LOCLoop3[0,10] LOC_10[0,1] DTM_23[0,5] TODLoop2[0,5] TOD_2[0,1] LOC_11[0,2] RCSLoop2[0,100] RCS_2[0,1] RFF_10[0,5] DTM_24[0,5] FTX_9[0,5] GISLoop1[0,10] GIS_2[0,1] RFF_11[0,1] DTM_25[0,5] GIR_3[0,5] LOC_12[0,2] MEA_5[0,5] QTY_6[0,2] FTX_10[0,5] MOA_11[0,2] UNS[0,1] CNT[0,10] MOALoop4[0,100] MOA_12[0,1] RFFLoop7[0,1] RFF_12[0,1] DTM_26[0,5] TAXLoop5[0,10] TAX_5[0,1] MOA_13[0,2] ALCLoop3[0,15] ALC_3[0,1] ALI_5[0,1] MOA_14[0,2] FTX_11[0,1] UNT[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:

edifactreader.LoadSchema(schemaFile); Console.WriteLine(edifactreader.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 EDIFACT 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 the segment from the example document:

BGM+380:::TAX INVOICE+0013550417+9'~

The following code can be used:

edifactreader.XPath = "/IX/TX/BGM"; for (int i = 0; i < edifactreader.XElements.Count; i++) { Console.WriteLine(edifactreader.XElements[i].Name + ": " + edifactreader.XElements[i].Value); }

Which will output:

BGM01: 380:::TAX INVOICE BGM02: 0013550417 BGM03: 9

The XElements[i].DataType property provides information about the element's data type which may be useful for interpreting the value. For instance:

edifactreader.XPath = "/IX/TX/BGM"; for (int i = 0; i < edifactreader.XElements.Count; i++) { Console.WriteLine(edifactreader.XElements[i].Name + "[" + edifactreader.XElements[i].DataType + "]: " + edifactreader.XElements[i].Value); }

Outputs:

BGM01[Composite]: 380:::TAX INVOICE BGM02[Composite]: 0013550417 BGM03[None]: 9

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 EDIFACTReader 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

Name holds positional (ref) value like "BGM01". SchemaName holds the Id taken from the schama.

For instance:

edifactreader.XPath = "/IX/TX/BGM"; for (int i = 0; i < edifactreader.XElements.Count; i++) { Console.WriteLine(edifactreader.XElements[i].SchemaName + "[" + edifactreader.XElements[i].SchemaDesc + "]: " + edifactreader.XElements[i].Value); }

When SchemaName and SchemaDesc are output this will use the name and description from the schema and will result values like:

C002[DOCUMENT/MESSAGE NAME]: 380:::TAX INVOICE C106[DOCUMENT/MESSAGE IDENTIFICATION]: 0013550417 1225[Message function, coded]: 9

In contrast, if Name was used the output would look like:

BGM01: 380:::TAX INVOICE BGM02: 0013550417 BGM03: 9

Note: These fields are only applicable when a JSON schema is loaded.

Working with Loops

Within this particular document there are multiple LIN Loops representing a line item in the invoice (LIN), additional product id (PIA), item decription (IMD), measurements (MEA), quantity (QTY), and date/time/period (DTM). This is a section of the document holding these values:

LIN+000030+' PIA+1+2265S13:BP::92' PIA+1+5029766832002:UP::92' IMD+F+' QTY+47:50.000:EA' DTM+11:20070926:102'

To select the first instance of the LIN Loop set:

edifactreader.XPath = "/IX/TX/LINLoop1[1]"; Console.WriteLine("Elements: " + edifactreader.XElements.Count); Console.WriteLine("Children: " + edifactreader.XChildren); Console.WriteLine("Segment: " + edifactreader.XSegment); Console.WriteLine("Segment Type: " + edifactreader.XSegmentType);

Which outputs:

Elements: 0 Children: 10 Segment: LINLoop1 Segment Type: stTransactionLoop

In this case there are no elements because this is the XPath to the first instance of the LIN Loop itself. To access the individual segments within the loop the child segments of the loop must be accessed. For instance:

edifactreader.XPath = "/IX/TX/LINLoop1[1]/LIN"; for (int i = 0; i < edifactreader.XElements.Count; i++) { Console.WriteLine(edifactreader.XElements[i].Name + ": " + edifactreader.XElements[i].Value); }

Outputs:

LIN01: 000030 LIN02:

Accessing other instances of the loop can be done simply by incrementing the index used LINLoop1[1] part of the XPath. For instance:

edifactreader.XPath = "/IX/TX/LINLoop1[2]/LIN"; for (int i = 0; i < edifactreader.XElements.Count; i++) { Console.WriteLine(edifactreader.XElements[i].Name + ": " + edifactreader.XElements[i].Value); }

Outputs:

LIN01: 000040 LIN02:

To select a specific LIN Loop based on the value of one of the elements within the loop a conditional XPath can be specified. For instance if the desired LIN Loop contains:

LIN+000030+'

The following code can be used to select the specific LIN Loop with LIN01='000030':

edifactreader.Config("ResolveXPathOnSet=true"); edifactreader.XPath = "/IX/TX/LINLoop1[LIN01='000030']"; Console.WriteLine(edifactreader.XPath);

Note the conditional part of the XPath LINLoop1[LIN01='000030'] 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 "1082"). For instance:

edifactreader.XPath = "/IX/TX/LINLoop1[1082='000030']";

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]/TX[1]/LINLoop1[1]

Now that the desired loop XPath is known it may be used to construct 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 LIN Loop with LIN01=000030 and access the QTY segment within the loop the following code can be used:

edifactreader.XPath = "/IX/TX/LINLoop1[LIN01='000030']/QTY"; for (int i = 0; i < edifactreader.XElements.Count; i++) { Console.WriteLine(edifactreader.XElements[i].Name + ": " + edifactreader.XElements[i].Value); }

Which outputs:

QTY01: 47:50.000:EA

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 TAXLoop1:

TAX+7+VAT+++:::10072.14+S'

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 TAX Loop in the document

//Use HasXPath to check for the XPath before navigating int index = 1; while (edifactreader.HasXPath("/IX/TX/TAXLoop1[" + index + "]")) { Console.WriteLine("***** Start TAXLoop1 *****"); edifactreader.XPath = "/IX/TX/TAXLoop1[" + index + "]/TAX"; for (int i = 0; i < edifactreader.XElements.Count; i++) { Console.WriteLine(edifactreader.XElements[i].Name + ": " + edifactreader.XElements[i].Value); } index++; Console.WriteLine("***** End TAXLoop1 *****"); } //Use TryXPath to try navigating int index = 1; while (edifactreader.TryXPath("/IX/TX/TAXLoop1[" + index + "]/TAX")) { Console.WriteLine("***** Start TAXLoop1 *****"); for (int i = 0; i < edifactreader.XElements.Count; i++) { Console.WriteLine(edifactreader.XElements[i].Name + ": " + edifactreader.XElements[i].Value); } index++; Console.WriteLine("***** End TAXLoop1 *****"); }

Both approaches will output the same values:

***** Start TAXLoop1 ***** TAX01: 7 TAX02: VAT TAX03: TAX04: TAX05: :::10072.14 TAX06: S ***** End TAXLoop1 *****

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. In the INVOIC document from above the QTY segment includes a composite element with the : separator:

QTY+47:50.000:EA'

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:

edifactreader.XPath = "/IX/TX/LINLoop1[LIN01='000030']/QTY"; for (int i = 0; i < edifactreader.XElements.Count; i++) { Console.WriteLine(edifactreader.XElements[i].Name + ": " + edifactreader.XElements[i].DataType + ": " + edifactreader.XElements[i].Value); if (edifactreader.XElements[i].DataType == "Composite") { //Loop through the composite element components. for (int compIdx = 0; compIdx < edifactreader.XElements[i].ComponentCount; compIdx++) { edifactreader.XElements[i].ComponentIndex = compIdx; Console.WriteLine(" Component " + compIdx + ": " + edifactreader.XElements[i].ComponentValue); } } }

Outputs:

QTY01: Composite: 47:50.000:EA Component 0: 47 Component 1: 50.000 Component 2: EA

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:

edifactreader.XPath = "/IX/TX/TST"; for (int j = 0; j < edifactreader.XElements[1].RepeatCount; j++) { edifactreader.XElements[1].RepeatIndex = j; Console.WriteLine("Value: " + edifactreader.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 support@nsoftware.com.