Parse and Generate HL7 Output - C#
Document Parser SDK sample in C# demonstrating ‘Parse and Generate HL7 Output’
HL7Helper.cs
using HL7.Dotnetcore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace HL7CreationFromJson
{
class Hl7Helper
{
/// <summary>
/// Get HL7 format representation from input model
/// </summary>
public static string GetHL7Format(JsonHL7Fields inpModel)
{
// https://github.com/Efferent-Health/HL7-dotnetcore
// http://www.j4jayant.com/2013/05/hl7-parsing-in-csharp.html
Message oHl7Message = new Message();
// Add MSH Segment
Segment mshSegment = new Segment("MSH", new HL7Encoding());
mshSegment.AddNewField("SendingApp", 3);
mshSegment.AddNewField(inpModel.LabName ?? "", 4);
mshSegment.AddNewField(DateTime.Now.ToString("yyyymmddhhMMss"), 7);
mshSegment.AddNewField("ORM", 9); // Message type
mshSegment.AddNewField("2.3", 12); // Message version
oHl7Message.AddNewSegment(mshSegment);
// Add PID Segment
Segment pidSegment = new Segment("PID", new HL7Encoding());
pidSegment.AddNewField("1", 1);
pidSegment.AddNewField(inpModel.PatientChartNo ?? "", 2); // Patient ID
pidSegment.AddNewField(inpModel.PatientChartNo ?? "", 4); // Alternate Patient ID
pidSegment.AddNewField($"{inpModel.PatientLastName ?? ""}^{inpModel.PatientFirstName ?? ""}", 5); // Patient Name
pidSegment.AddNewField(inpModel.PatientDOB ?? "", 7); // Patient DOB
pidSegment.AddNewField(inpModel.PatientGender ?? "", 8); // Patient Gender
pidSegment.AddNewField(inpModel.PatientAddress ?? "", 11); // Patient Address
pidSegment.AddNewField(inpModel.PatientPhoneHome ?? "", 13); // Patient Home Phone number
pidSegment.AddNewField(inpModel.PatientSSN ?? "", 19); // Patient SSN Number
oHl7Message.AddNewSegment(pidSegment);
// Add PV1 Segment
Segment pv1Segment = new Segment("PV1", new HL7Encoding());
pv1Segment.AddNewField($"{inpModel.PhysicianNpi ?? ""}^{inpModel.PhysicianName}", 7); // Physician information
oHl7Message.AddNewSegment(pv1Segment);
// Add IN1 Segment
Segment in1Segment = new Segment("IN1", new HL7Encoding());
in1Segment.AddNewField("1", 1);
in1Segment.AddNewField(inpModel.InsuranceName ?? "", 4); // Insurance Name
in1Segment.AddNewField(inpModel.InsuranceGroup ?? "", 8); // Insurance Group Name
in1Segment.AddNewField(inpModel.InsuredName ?? "", 16); // Insured Name
in1Segment.AddNewField(inpModel.RelationToPatient ?? "", 17); // Insured Relatino
in1Segment.AddNewField(inpModel.InsuredDob ?? "", 18); // Insured Date of Birth
in1Segment.AddNewField(inpModel.InsurancePolicy ?? "", 36); // Insurance Policy Number
oHl7Message.AddNewSegment(in1Segment);
// Add ORC Segment
Segment orcSegment = new Segment("ORC", new HL7Encoding());
orcSegment.AddNewField("NW", 1); // New Order
orcSegment.AddNewField(inpModel.CollectionDateTime ?? "", 9); // Date/Time of Transaction
orcSegment.AddNewField($"{inpModel.PhysicianNpi ?? ""}^{inpModel.PhysicianName ?? ""}", 12); // Ordering Provider
oHl7Message.AddNewSegment(orcSegment);
// Add OBR Segment
Segment obrSegment = new Segment("OBR", new HL7Encoding());
obrSegment.AddNewField(inpModel.CollectionDateTime ?? "", 7); // Date/Time of Transaction
obrSegment.AddNewField($"{inpModel.PhysicianNpi ?? ""}^{inpModel.PhysicianName ?? ""}", 16); // Ordering Provider
oHl7Message.AddNewSegment(obrSegment);
// Add Diagnosis
for (int i = 0; i < inpModel.Icd10Codes.Count; i++)
{
Segment dg1Segment = new Segment("DG1", new HL7Encoding());
dg1Segment.AddNewField((i + 1).ToString(), 1);
dg1Segment.AddNewField("I10", 2); // Icd Type
dg1Segment.AddNewField(inpModel.Icd10Codes[i], 3); // Icd Code
oHl7Message.AddNewSegment(dg1Segment);
}
// Add OBX
for (int i = 0; i < inpModel.QuestionAnswer.Count; i++)
{
Segment obxSegment = new Segment("OBX", new HL7Encoding());
obxSegment.AddNewField((i + 1).ToString(), 1);
obxSegment.AddNewField("ST", 2); // Value Type
obxSegment.AddNewField(inpModel.QuestionAnswer[i].Key, 3); // Question
obxSegment.AddNewField(inpModel.QuestionAnswer[i].Value, 5); // Answer
oHl7Message.AddNewSegment(obxSegment);
}
string oRetMessage = oHl7Message.SerializeMessage(false);
return oRetMessage;
}
}
}
JsonHL7Fields.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace HL7CreationFromJson
{
public class JsonHL7Fields
{
public JsonHL7Fields()
{
Icd10Codes = new List<string>();
QuestionAnswer = new List<KeyValuePair<string, string>>();
}
public string LabName { get; set; }
public string PatientLastName { get; set; }
public string PatientFirstName { get; set; }
public string PatientSSN { get; set; }
public string PatientDOB { get; set; }
public string PatientPhoneHome { get; set; }
public string PatientPhoneWork { get; set; }
public string PatientChartNo { get; set; }
public string PatientGender { get; set; }
public string PatientAddress { get; set; }
public string PhysicianName { get; set; }
public string PhysicianAccountNo { get; set; }
public string PhysicianNpi { get; set; }
public string InsuranceName { get; set; }
public string InsurancePolicy { get; set; }
public string InsuranceGroup { get; set; }
public string InsuredName { get; set; }
public string InsuredSSN { get; set; }
public string InsuredDob { get; set; }
public string RelationToPatient { get; set; }
public string CollectionDateTime { get; set; }
public List<string> Icd10Codes { get; set; }
public List<KeyValuePair<string, string>> QuestionAnswer { get; set; }
}
}
JsonParserHelper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
namespace HL7CreationFromJson
{
class JsonParserHelper
{
/// <summary>
/// Parse Json Fileds in class format
/// </summary>
public static JsonHL7Fields ParseJsonHL7Fields(string jsonData)
{
// Get Object data from input file
JObject jsonObj = JObject.Parse(jsonData);
var oRet = new JsonHL7Fields();
oRet.LabName = Convert.ToString(jsonObj["fields"]["labName"]["value"]);
oRet.PatientLastName = Convert.ToString(jsonObj["fields"]["patientLastName"]["value"]);
oRet.PatientFirstName = Convert.ToString(jsonObj["fields"]["patientFirstName"]["value"]);
oRet.PatientSSN = Convert.ToString(jsonObj["fields"]["patientSSN"]["value"]);
oRet.PatientDOB = Convert.ToString(jsonObj["fields"]["patientDOB"]["value"]);
oRet.PatientPhoneHome = Convert.ToString(jsonObj["fields"]["patientHomePhone"]["value"]);
oRet.PatientPhoneWork = Convert.ToString(jsonObj["fields"]["patientWorkPhone"]["value"]);
oRet.PatientAddress = Convert.ToString(jsonObj["fields"]["patientAddress"]["value"]);
oRet.PatientChartNo = Convert.ToString(jsonObj["fields"]["patientChartNo"]["value"]);
string patGenderMaleSelectedVal = Convert.ToString(jsonObj["fields"]["patientGenderMale"]["value"]);
string patGenderFemaleSelectedVal = Convert.ToString(jsonObj["fields"]["patientGenderFemale"]["value"]);
if (!string.IsNullOrEmpty(patGenderMaleSelectedVal))
{
oRet.PatientGender = "M";
}
else if (!string.IsNullOrEmpty(patGenderFemaleSelectedVal))
{
oRet.PatientGender = "F";
}
oRet.PhysicianName = Convert.ToString(jsonObj["fields"]["physicianName"]["value"]);
oRet.PhysicianAccountNo = Convert.ToString(jsonObj["fields"]["physicianAccountName"]["value"]);
oRet.PhysicianNpi = Convert.ToString(jsonObj["fields"]["physicianNPI"]["value"]);
oRet.InsuranceName = Convert.ToString(jsonObj["fields"]["insuranceName"]["value"]);
oRet.InsurancePolicy = Convert.ToString(jsonObj["fields"]["insurancePolicy"]["value"]);
oRet.InsuranceGroup = Convert.ToString(jsonObj["fields"]["insuranceGroup"]["value"]);
oRet.InsuredName = Convert.ToString(jsonObj["fields"]["insuredName"]["value"]);
oRet.InsuredSSN = Convert.ToString(jsonObj["fields"]["insuredSSN"]["value"]);
oRet.InsuredDob = Convert.ToString(jsonObj["fields"]["insuredDOB"]["value"]);
string relToPatIsSelf = Convert.ToString(jsonObj["fields"]["relationToPatientIsSelf"]["value"]);
string relToPatIsSpouse = Convert.ToString(jsonObj["fields"]["relationToPatientIsSpouse"]["value"]);
string relToPatIsDependent = Convert.ToString(jsonObj["fields"]["relationToPatientIsDependent"]["value"]);
if (!string.IsNullOrEmpty(relToPatIsSelf))
{
oRet.RelationToPatient = "Self";
}
else if (!string.IsNullOrEmpty(relToPatIsSpouse))
{
oRet.RelationToPatient = "Spouse";
}
else if (!string.IsNullOrEmpty(relToPatIsDependent))
{
oRet.RelationToPatient = "Dependent";
}
// Add Collection Date/Time
string colDate = Convert.ToString(jsonObj["fields"]["collectionDate"]["value"]);
string colTime = Convert.ToString(jsonObj["fields"]["collectionTime"]["value"]);
string colTimeIsAm = Convert.ToString(jsonObj["fields"]["collectionTimeIsAM"]["value"]);
string colTimeIsPm = Convert.ToString(jsonObj["fields"]["collectionTimeIsPM"]["value"]);
string colTimeAmPm = "";
if (!string.IsNullOrEmpty(colTimeIsAm))
{
colTimeAmPm = "AM";
}
else if (!string.IsNullOrEmpty(colTimeIsPm))
{
colTimeAmPm = "PM";
}
oRet.CollectionDateTime = $"{colDate} {colTime} {colTimeAmPm}";
// Add ICD Codes
string IcdCodes = Convert.ToString(jsonObj["fields"]["icD10DxCodes"]["value"]);
if (!string.IsNullOrEmpty(IcdCodes))
{
var arrIcdCodes = IcdCodes.Split(',');
foreach (var itmIcd in arrIcdCodes)
{
oRet.Icd10Codes.Add(itmIcd.Trim());
}
}
// Add Question/Answers
string Ques_ClinicalHistoryIsRoutinePap = string.IsNullOrEmpty(Convert.ToString(jsonObj["fields"]["clinicalHistoryIsRoutinePap"]["value"])) ? "No" : "Yes";
string Ques_ClinicalHistoryIsAbnormalBleeding = string.IsNullOrEmpty(Convert.ToString(jsonObj["fields"]["clinicalHistoryIsAbnormalBleeding"]["value"])) ? "No" : "Yes";
oRet.QuestionAnswer.Add(new KeyValuePair<string, string>("Is Routine PAP?", Ques_ClinicalHistoryIsRoutinePap));
oRet.QuestionAnswer.Add(new KeyValuePair<string, string>("Is Abnormal Bleeding?", Ques_ClinicalHistoryIsAbnormalBleeding));
return oRet;
}
}
}
Program.cs
using System;
using System.Diagnostics;
using ByteScout.DocumentParser;
// This example demonstrates document data parsing to JSON, YAML and XML formats.
namespace HL7CreationFromJson
{
class Program
{
static void Main(string[] args)
{
// Step 1: Generate Parse PDF File With Template and Generate Json
string inputPDF = "InputData/Test_Report_Format.pdf";
string template = "InputData/TestReportFormat.yml";
// Create Document Parser Instance
DocumentParser docParser = new DocumentParser("demo", "demo");
// Add Template
docParser.AddTemplate(template);
// Parse document data in JSON format
string jsonString = docParser.ParseDocument(inputPDF, OutputFormat.JSON);
// Step 2: Parse Json fileds in class format
var oInpModel = JsonParserHelper.ParseJsonHL7Fields(jsonString);
// Step 3: Get Data in HL7 Format
var oHL7Format = Hl7Helper.GetHL7Format(oInpModel);
// Store HL7 File and open with default associated program
var outputFile = "outputHl7.txt";
System.IO.File.WriteAllText(outputFile, oHL7Format);
Process.Start(outputFile);
}
}
}
TestReportFormat.yml
templateName: Untitled document kind
templateVersion: 4
templatePriority: 0
detectionRules:
keywords: []
objects:
- name: LabName
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 98.4065933
- 33.2967033
- 90
- 8.24175835
pageIndex: 0
- name: LabAddress
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 99.0659332
- 42.3626366
- 89.175827
- 9.23077011
pageIndex: 0
- name: LabCity
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 100.549454
- 53.0769272
- 21.7582436
- 8.90109921
pageIndex: 0
- name: LabState
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 125.109894
- 52.9120865
- 12.5274725
- 8.24175835
pageIndex: 0
- name: LabZip
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 138.131882
- 52.5824203
- 24.3956051
- 8.90109921
pageIndex: 0
- name: LabPhone
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 115.219788
- 63.4615402
- 51.7582436
- 7.74725294
pageIndex: 0
- name: LabFax
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 184.780212
- 63.9560471
- 52.5824203
- 7.91208792
pageIndex: 0
- name: PatientLastName
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 18.6075935
- 106.139236
- 21.8354435
- 9.49367046
pageIndex: 0
- name: PatientFirstName
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 150.759491
- 106.329109
- 37.0253143
- 8.54430389
pageIndex: 0
- name: PatientSSN
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 18.6075935
- 125.316452
- 99.4936676
- 9.68354416
pageIndex: 0
- name: PatientAge
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 150.949371
- 125.126579
- 35.3164558
- 10.2531643
pageIndex: 0
- name: PatientDOB
objectType: field
fieldProperties:
fieldType: rectangle
dataType: date
rectangle:
- 190.5
- 125.25
- 38.25
- 9.75
pageIndex: 0
- name: PatientAddress
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 18.7974682
- 144.493668
- 189.113922
- 9.8734169
pageIndex: 0
- name: PatientHomePhone
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 18.3870964
- 163.548386
- 90.9677429
- 11.1290321
pageIndex: 0
- name: PatientWorkPhone
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 114.677414
- 164.032242
- 71.6128998
- 11.1290321
pageIndex: 0
- name: PatientChartNo
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 209.516129
- 164.032242
- 91.9354858
- 11.1290321
pageIndex: 0
- name: PhysicianName
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 305.806427
- 104.999992
- 259.838715
- 10.6451607
pageIndex: 0
- name: PhysicianAccountName
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 305.806427
- 125.80645
- 165.967743
- 9.1935482
pageIndex: 0
- name: PhysicianAccountNo
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 480
- 125.322578
- 92.9032211
- 10.1612902
pageIndex: 0
- name: PhysicianNPI
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 308.035736
- 163.392853
- 94.2857132
- 11.7857141
pageIndex: 0
- name: PhysicianPhone
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 409.821442
- 163.928589
- 79.821434
- 9.64285755
pageIndex: 0
- name: PhysicianFax
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 494.516113
- 164.032242
- 77.4193497
- 11.1290321
pageIndex: 0
- name: InsuranceName
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 17.9032249
- 263.2258
- 279.677399
- 8.70967674
pageIndex: 0
- name: InsurancePolicy
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 16.9354839
- 302.903229
- 57.5806465
- 9.67741871
pageIndex: 0
- name: InsuranceGroup
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 151.935471
- 302.903229
- 62.4193573
- 9.67741871
pageIndex: 0
- name: InsuredName
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 18.3870964
- 322.741943
- 77.9032211
- 12.0967741
pageIndex: 0
- name: InsuredSSN
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 149.032257
- 323.709686
- 89.5161285
- 10.1612902
pageIndex: 0
- name: InsuredDOB
objectType: field
fieldProperties:
fieldType: rectangle
dataType: date
rectangle:
- 244.5
- 322.5
- 54.75
- 10.5
pageIndex: 0
- name: CollectionDate
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 86.756752
- 351.891876
- 55.9459457
- 9.32432365
pageIndex: 0
- name: CollectionTime
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 263.035736
- 352.5
- 29.4642868
- 8.57142925
pageIndex: 0
- name: PatientGenderMale
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 273.214294
- 128.035721
- 8.0357151
- 6.96428585
pageIndex: 0
- name: PatientGenderFemale
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 289.285736
- 127.5
- 7.50000048
- 8.0357151
pageIndex: 0
- name: PhysicianAddress
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 305.892883
- 145.714294
- 176.785721
- 8.0357151
pageIndex: 0
- name: BillToIsPhysician
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 53.035717
- 206.250015
- 8.57142925
- 6.96428585
pageIndex: 0
- name: BillToIsMedicare
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 95.8928604
- 206.250015
- 8.0357151
- 7.50000048
pageIndex: 0
- name: BillToIsMedicaid
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 139.285721
- 206.250015
- 8.0357151
- 8.0357151
pageIndex: 0
- name: BillToIsInsurance
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 183.750015
- 206.250015
- 7.50000048
- 7.50000048
pageIndex: 0
- name: BillToIsPatient
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 231.428589
- 206.250015
- 7.50000048
- 6.4285717
pageIndex: 0
- name: RelationToPatientIsSelf
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 117.321434
- 245.357147
- 7.50000048
- 8.0357151
pageIndex: 0
- name: RelationToPatientIsSpouse
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 144.642868
- 244.821442
- 8.0357151
- 8.57142925
pageIndex: 0
- name: RelationToPatientIsDependent
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 184.821442
- 245.357147
- 6.96428585
- 7.50000048
pageIndex: 0
- name: CollectionTimeIsAM
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 303.75
- 354.107178
- 6.96428585
- 6.4285717
pageIndex: 0
- name: CollectionTimeIsPM
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 328.392883
- 354.642853
- 7.50000048
- 7.50000048
pageIndex: 0
- name: ICD10DxCodes
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 17.6785736
- 381.964325
- 73.9285736
- 10.7142868
pageIndex: 0
- name: ClinicalHistoryIsRoutinePap
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 18.7500019
- 423.214294
- 7.50000048
- 8.0357151
pageIndex: 0
- name: ClinicalHistoryIsAbnormalBleeding
objectType: field
fieldProperties:
fieldType: rectangle
rectangle:
- 20.3571453
- 433.392853
- 6.96428585
- 9.1071434
pageIndex: 0
Component.cs
using System;
using System.Collections.Generic;
/// <summary>
/// https://github.com/Efferent-Health/HL7-dotnetcore
/// </summary>
namespace HL7.Dotnetcore
{
public class Component : MessageElement
{
internal List<SubComponent> SubComponentList { get; set; }
public bool IsSubComponentized { get; set; } = false;
private bool isDelimiter = false;
public Component(HL7Encoding encoding, bool isDelimiter = false)
{
this.isDelimiter = isDelimiter;
this.SubComponentList = new List<SubComponent>();
this.Encoding = encoding;
}
public Component(string pValue, HL7Encoding encoding)
{
this.SubComponentList = new List<SubComponent>();
this.Encoding = encoding;
this.Value = pValue;
}
protected override void ProcessValue()
{
List<string> allSubComponents;
if (this.isDelimiter)
allSubComponents = new List<string>(new [] {this.Value});
else
allSubComponents = MessageHelper.SplitString(_value, this.Encoding.SubComponentDelimiter);
if (allSubComponents.Count > 1)
{
this.IsSubComponentized = true;
}
this.SubComponentList = new List<SubComponent>();
foreach (string strSubComponent in allSubComponents)
{
SubComponent subComponent = new SubComponent(this.Encoding.Decode(strSubComponent), this.Encoding);
SubComponentList.Add(subComponent);
}
}
public SubComponent SubComponents(int position)
{
position = position - 1;
SubComponent sub = null;
try
{
sub = SubComponentList[position];
}
catch (Exception ex)
{
throw new HL7Exception("SubComponent not availalbe Error-" + ex.Message);
}
return sub;
}
public List<SubComponent> SubComponents()
{
return SubComponentList;
}
}
internal class ComponentCollection : List<Component>
{
/// <summary>
/// Component indexer
/// </summary>
internal new Component this[int index]
{
get
{
Component component = null;
if (index < base.Count)
component = base[index];
return component;
}
set
{
base[index] = value;
}
}
/// <summary>
/// Add Component at next position
/// </summary>
/// <param name="component">Component</param>
internal new void Add(Component component)
{
base.Add(component);
}
/// <summary>
/// Add component at specific position
/// </summary>
/// <param name="component">Component</param>
/// <param name="position">Position</param>
internal void Add(Component component, int position)
{
int listCount = base.Count;
position = position - 1;
if (position < listCount)
{
base[position] = component;
}
else
{
for (int comIndex = listCount; comIndex < position; comIndex++)
{
Component blankComponent = new Component(component.Encoding);
blankComponent.Value = string.Empty;
base.Add(blankComponent);
}
base.Add(component);
}
}
}
}
Encoding.cs
using System;
using System.Globalization;
using System.Text;
/// <summary>
/// https://github.com/Efferent-Health/HL7-dotnetcore
/// </summary>
namespace HL7.Dotnetcore
{
public class HL7Encoding
{
public string AllDelimiters { get; private set; } = @"|^~\&";
public char FieldDelimiter { get; set; } = '|'; // \F\
public char ComponentDelimiter { get; set; } = '^'; // \S\
public char RepeatDelimiter { get; set; } = '~'; // \R\
public char EscapeCharacter { get; set; } = '\\'; // \E\
public char SubComponentDelimiter { get; set; } = '&'; // \T\
public string SegmentDelimiter { get; set; } = "\r";
public string PresentButNull { get; set; } = "\"\"";
public HL7Encoding()
{
}
public void EvaluateDelimiters(string delimiters)
{
this.FieldDelimiter = delimiters[0];
this.ComponentDelimiter = delimiters[1];
this.RepeatDelimiter = delimiters[2];
this.EscapeCharacter = delimiters[3];
this.SubComponentDelimiter = delimiters[4];
}
public void EvaluateSegmentDelimiter(string message)
{
string[] delimiters = new[] { "\r\n", "\n\r", "\r", "\n" };
foreach (var delim in delimiters)
{
if (message.Contains(delim))
{
this.SegmentDelimiter = delim;
return;
}
}
throw new HL7Exception("Segment delimiter not found in message", HL7Exception.BAD_MESSAGE);
}
// Encoding methods based on https://github.com/elomagic/hl7inspector
public string Encode(string val)
{
if (val == null)
return PresentButNull;
if (string.IsNullOrWhiteSpace(val))
return val;
var sb = new StringBuilder();
for (int i = 0; i < val.Length; i++)
{
char c = val[i];
if (c == this.ComponentDelimiter) {
sb.Append(this.EscapeCharacter);
sb.Append("S");
sb.Append(this.EscapeCharacter);
}
else if (c == this.EscapeCharacter) {
sb.Append(this.EscapeCharacter);
sb.Append("E");
sb.Append(this.EscapeCharacter);
}
else if (c == this.FieldDelimiter) {
sb.Append(this.EscapeCharacter);
sb.Append("F");
sb.Append(this.EscapeCharacter);
}
else if (c == this.RepeatDelimiter) {
sb.Append(this.EscapeCharacter);
sb.Append("R");
sb.Append(this.EscapeCharacter);
}
else if (c == this.SubComponentDelimiter) {
sb.Append(this.EscapeCharacter);
sb.Append("T");
sb.Append(this.EscapeCharacter);
}
else if(c < 32) {
string v = string.Format("{0:X2}",(int)c);
if ((v.Length | 2) != 0)
v = "0" + v;
sb.Append(this.EscapeCharacter);
sb.Append("X");
sb.Append(v);
sb.Append(this.EscapeCharacter);
}
else {
sb.Append(c);
}
}
return sb.ToString();
}
public string Decode(string encodedValue)
{
if (string.IsNullOrWhiteSpace(encodedValue))
return encodedValue;
var result = new StringBuilder();
for (int i = 0; i < encodedValue.Length; i++)
{
char c = encodedValue[i];
if (c != this.EscapeCharacter)
{
result.Append(c);
continue;
}
i++;
int li = encodedValue.IndexOf(this.EscapeCharacter, i);
if (li == -1)
throw new HL7Exception("Invalid escape sequence in HL7 string");
string seq = encodedValue.Substring(i, li-i);
i = li;
if (seq.Length == 0)
continue;
switch (seq)
{
case "H": // Start higlighting
result.Append("<B>");
break;
case "N": // normal text (end highlighting)
result.Append("</B>");
break;
case "F": // field separator
result.Append(this.FieldDelimiter);
break;
case "S": // component separator
result.Append(this.ComponentDelimiter);
break;
case "T": // subcomponent separator
result.Append(this.SubComponentDelimiter);
break;
case "R": // repetition separator
result.Append(this.RepeatDelimiter);
break;
case "E": // escape character
result.Append(this.EscapeCharacter);
break;
case ".br":
result.Append("<BR>");
break;
default:
if (seq.StartsWith("X"))
result.Append(((char)int.Parse(seq.Substring(1), NumberStyles.AllowHexSpecifier)));
else
result.Append(seq);
break;
}
}
return result.ToString();
}
}
}
Field.cs
using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// https://github.com/Efferent-Health/HL7-dotnetcore
/// </summary>
namespace HL7.Dotnetcore
{
public class Field : MessageElement
{
private List<Field> _RepetitionList;
internal ComponentCollection ComponentList { get; set; }
public bool IsComponentized { get; set; } = false;
public bool HasRepetitions { get; set; } = false;
public bool IsDelimiters { get; set; } = false;
internal List<Field> RepeatitionList
{
get
{
if (_RepetitionList == null)
_RepetitionList = new List<Field>();
return _RepetitionList;
}
set
{
_RepetitionList = value;
}
}
protected override void ProcessValue()
{
if (this.IsDelimiters) // Special case for the delimiters fields (MSH)
{
var subcomponent = new SubComponent(_value, this.Encoding);
this.ComponentList = new ComponentCollection();
Component component = new Component(this.Encoding, true);
component.SubComponentList.Add(subcomponent);
this.ComponentList.Add(component);
return;
}
this.HasRepetitions = _value.Contains(this.Encoding.RepeatDelimiter);
if (this.HasRepetitions)
{
_RepetitionList = new List<Field>();
List<string> individualFields = MessageHelper.SplitString(_value, this.Encoding.RepeatDelimiter);
for (int index = 0; index < individualFields.Count; index++)
{
Field field = new Field(individualFields[index], this.Encoding);
_RepetitionList.Add(field);
}
}
else
{
List<string> allComponents = MessageHelper.SplitString(_value, this.Encoding.ComponentDelimiter);
this.ComponentList = new ComponentCollection();
foreach (string strComponent in allComponents)
{
Component component = new Component(this.Encoding);
component.Value = strComponent;
this.ComponentList.Add(component);
}
this.IsComponentized = this.ComponentList.Count > 1;
}
}
public Field(HL7Encoding encoding)
{
this.ComponentList = new ComponentCollection();
this.Encoding = encoding;
}
public Field(string value, HL7Encoding encoding)
{
this.ComponentList = new ComponentCollection();
this.Encoding = encoding;
this.Value = value;
}
public bool AddNewComponent(Component com)
{
try
{
this.ComponentList.Add(com);
return true;
}
catch (Exception ex)
{
throw new HL7Exception("Unable to add new component Error - " + ex.Message);
}
}
public bool AddNewComponent(Component component, int position)
{
try
{
this.ComponentList.Add(component, position);
return true;
}
catch (Exception ex)
{
throw new HL7Exception("Unable to add new component Error - " + ex.Message);
}
}
public Component Components(int position)
{
position = position - 1;
Component com = null;
try
{
com = ComponentList[position];
}
catch (Exception ex)
{
throw new HL7Exception("Component not available Error - " + ex.Message);
}
return com;
}
public List<Component> Components()
{
return ComponentList;
}
public List<Field> Repetitions()
{
if (this.HasRepetitions)
{
return _RepetitionList;
}
return null;
}
public Field Repetitions(int repeatitionNumber)
{
if (this.HasRepetitions)
{
return _RepetitionList[repeatitionNumber - 1];
}
return null;
}
}
internal class FieldCollection : List<Field>
{
internal new Field this[int index]
{
get
{
Field field = null;
if (index < base.Count)
field = base[index];
return field;
}
set
{
base[index] = value;
}
}
/// <summary>
/// add field at next position
/// </summary>
/// <param name="field">Field</param>
internal new void Add(Field field)
{
base.Add(field);
}
/// <summary>
/// Add field at specific position
/// </summary>
/// <param name="field">Field</param>
/// <param name="position">position</param>
internal void Add(Field field, int position)
{
int listCount = base.Count;
if (position < listCount)
{
base[position] = field;
}
else
{
for (int fieldIndex = listCount; fieldIndex < position; fieldIndex++)
{
Field blankField = new Field(string.Empty, field.Encoding);
base.Add(blankField);
}
base.Add(field);
}
}
}
}
HL7Exception.cs
using System;
/// <summary>
/// https://github.com/Efferent-Health/HL7-dotnetcore
/// </summary>
namespace HL7.Dotnetcore
{
public class HL7Exception : Exception
{
public const string REQUIRED_FIELD_MISSING = "Validation Error - Required field missing in message";
public const string UNSUPPORTED_MESSAGE_TYPE = "Validation Error - Message Type Not Supported by this Implementation";
public const string BAD_MESSAGE = "Validation Error - Bad Message";
public const string PARSING_ERROR = "Parseing Error";
public const string SERIALIZATION_ERROR = "Serialization Error";
public string ErrorCode { get; set; }
public HL7Exception(string message) : base(message)
{
}
public HL7Exception(string message, string Code) : base(message)
{
ErrorCode = Code;
}
public override string ToString()
{
return ErrorCode + " : " + Message;
}
}
}
Message.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
/// <summary>
/// https://github.com/Efferent-Health/HL7-dotnetcore
/// </summary>
namespace HL7.Dotnetcore
{
public class Message
{
private List<string> allSegments = null;
internal Dictionary<string, List<Segment>> SegmentList { get; set;} = new Dictionary<string, List<Segment>>();
public string HL7Message { get; set; }
public string Version { get; set; }
public string MessageStructure { get; set; }
public string MessageControlID { get; set; }
public string ProcessingID { get; set; }
public short SegmentCount { get; set; }
public HL7Encoding Encoding { get; set; } = new HL7Encoding();
public Message()
{
}
public Message(string strMessage)
{
HL7Message = strMessage;
}
public override bool Equals(object obj)
{
if (obj is Message)
return this.Equals((obj as Message).HL7Message);
if (obj is string)
{
var arr1 = MessageHelper.SplitString(this.HL7Message, this.Encoding.SegmentDelimiter, StringSplitOptions.RemoveEmptyEntries);
var arr2 = MessageHelper.SplitString(obj as string, this.Encoding.SegmentDelimiter, StringSplitOptions.RemoveEmptyEntries);
return arr1.SequenceEqual(arr2);
}
return false;
}
public override int GetHashCode()
{
return this.HL7Message.GetHashCode();
}
/// <summary>
/// Parse the HL7 message in text format, throws HL7Exception if error occurs
/// </summary>
/// <returns>boolean</returns>
public bool ParseMessage()
{
bool isValid = false;
bool isParsed = false;
try
{
isValid = this.validateMessage();
}
catch (HL7Exception ex)
{
throw ex;
}
catch (Exception ex)
{
throw new HL7Exception("Unhandled Exception in validation - " + ex.Message, HL7Exception.BAD_MESSAGE);
}
if (isValid)
{
try
{
if (this.allSegments == null || this.allSegments.Count <= 0)
this.allSegments = MessageHelper.SplitMessage(HL7Message);
short SegSeqNo = 0;
foreach (string strSegment in this.allSegments)
{
if (string.IsNullOrWhiteSpace(strSegment))
continue;
Segment newSegment = new Segment(this.Encoding);
string segmentName = strSegment.Substring(0, 3);
newSegment.Name = segmentName;
newSegment.Value = strSegment;
newSegment.SequenceNo = SegSeqNo++;
this.AddNewSegment(newSegment);
}
this.SegmentCount = SegSeqNo;
string strSerializedMessage = string.Empty;
try
{
strSerializedMessage = this.SerializeMessage(false);
}
catch (HL7Exception ex)
{
throw new HL7Exception("Failed to serialize parsed message with error - " + ex.Message, HL7Exception.PARSING_ERROR);
}
if (!string.IsNullOrEmpty(strSerializedMessage))
{
if (this.Equals(strSerializedMessage))
isParsed = true;
}
else
{
throw new HL7Exception("Unable to serialize to original message - ", HL7Exception.PARSING_ERROR);
}
}
catch (Exception ex)
{
throw new HL7Exception("Failed to parse the message with error - " + ex.Message, HL7Exception.PARSING_ERROR);
}
}
return isParsed;
}
/// <summary>
/// Serialize the message in text format
/// </summary>
/// <param name="validate">Validate the message before serializing</param>
/// <returns>string with HL7 message</returns>
public string SerializeMessage(bool validate)
{
if (validate && !this.validateMessage())
throw new HL7Exception("Failed to validate the updated message", HL7Exception.BAD_MESSAGE);
var strMessage = new StringBuilder();
string currentSegName = string.Empty;
List<Segment> _segListOrdered = getAllSegmentsInOrder();
try
{
try
{
foreach (Segment seg in _segListOrdered)
{
currentSegName = seg.Name;
strMessage.Append(seg.Name).Append(Encoding.FieldDelimiter);
int startField = currentSegName == "MSH" ? 1 : 0;
for (int i = startField; i<seg.FieldList.Count; i++)
{
if (i > startField)
strMessage.Append(Encoding.FieldDelimiter);
var field = seg.FieldList[i];
if (field.IsDelimiters)
{
strMessage.Append(field.Value);
continue;
}
if (field.HasRepetitions)
{
for (int j = 0; j < field.RepeatitionList.Count; j++)
{
if (j > 0)
strMessage.Append(Encoding.RepeatDelimiter);
serializeField(field.RepeatitionList[j], strMessage);
}
}
else
serializeField(field, strMessage);
}
strMessage.Append(Encoding.SegmentDelimiter);
}
}
catch (Exception ex)
{
if (currentSegName == "MSH")
throw new HL7Exception("Failed to serialize the MSH segment with error - " + ex.Message, HL7Exception.SERIALIZATION_ERROR);
else
throw;
}
return strMessage.ToString();
}
catch (Exception ex)
{
throw new HL7Exception("Failed to serialize the message with error - " + ex.Message, HL7Exception.SERIALIZATION_ERROR);
}
}
/// <summary>
/// Get the Value of specific Field/Component/SubCpomponent, throws error if field/component index is not valid
/// </summary>
/// <param name="strValueFormat">Field/Component position in format SEGMENTNAME.FieldIndex.ComponentIndex.SubComponentIndex example PID.5.2</param>
/// <returns>Value of specified field/component/subcomponent</returns>
public string GetValue(string strValueFormat)
{
bool isValid = false;
string segmentName = string.Empty;
int fieldIndex = 0;
int componentIndex = 0;
int subComponentIndex = 0;
int comCount = 0;
string strValue = string.Empty;
List<string> allComponents = MessageHelper.SplitString(strValueFormat, new char[] { '.' });
comCount = allComponents.Count;
isValid = validateValueFormat(allComponents);
if (isValid)
{
segmentName = allComponents[0];
if (SegmentList.ContainsKey(segmentName))
{
if (comCount == 4)
{
Int32.TryParse(allComponents[1], out fieldIndex);
Int32.TryParse(allComponents[2], out componentIndex);
Int32.TryParse(allComponents[3], out subComponentIndex);
try
{
strValue = SegmentList[segmentName].First().FieldList[fieldIndex - 1].ComponentList[componentIndex - 1].SubComponentList[subComponentIndex - 1].Value;
}
catch (Exception ex)
{
throw new HL7Exception("SubComponent not available - " + strValueFormat + " Error: " + ex.Message);
}
}
else if (comCount == 3)
{
Int32.TryParse(allComponents[1], out fieldIndex);
Int32.TryParse(allComponents[2], out componentIndex);
try
{
strValue = SegmentList[segmentName].First().FieldList[fieldIndex - 1].ComponentList[componentIndex - 1].Value;
}
catch (Exception ex)
{
throw new HL7Exception("Component not available - " + strValueFormat + " Error: " + ex.Message);
}
}
else if (comCount == 2)
{
Int32.TryParse(allComponents[1], out fieldIndex);
try
{
strValue = SegmentList[segmentName].First().FieldList[fieldIndex - 1].Value;
}
catch (Exception ex)
{
throw new HL7Exception("Field not available - " + strValueFormat + " Error: " + ex.Message);
}
}
else
{
try
{
strValue = SegmentList[segmentName].First().Value;
}
catch (Exception ex)
{
throw new HL7Exception("Segment value not available - " + strValueFormat + " Error: " + ex.Message);
}
}
}
else
{
throw new HL7Exception("Segment name not available: " + strValueFormat);
}
}
else
{
throw new HL7Exception("Request format is not valid: " + strValueFormat);
}
return strValue;
}
/// <summary>
/// Sets the Value of specific Field/Component/SubCpomponent, throws error if field/component index is not valid
/// </summary>
/// <param name="strValueFormat">Field/Component position in format SEGMENTNAME.FieldIndex.ComponentIndex.SubComponentIndex example PID.5.2</param>
/// <param name="strValue">Value for the specified field/component</param>
/// <returns>boolean</returns>
public bool SetValue(string strValueFormat, string strValue)
{
bool isValid = false;
bool isSet = false;
string segmentName = string.Empty;
int fieldIndex = 0;
int componentIndex = 0;
int subComponentIndex = 0;
int comCount = 0;
List<string> AllComponents = MessageHelper.SplitString(strValueFormat, new char[] { '.' });
comCount = AllComponents.Count;
isValid = validateValueFormat(AllComponents);
if (isValid)
{
segmentName = AllComponents[0];
if (SegmentList.ContainsKey(segmentName))
{
if (comCount == 4)
{
Int32.TryParse(AllComponents[1], out fieldIndex);
Int32.TryParse(AllComponents[2], out componentIndex);
Int32.TryParse(AllComponents[3], out subComponentIndex);
try
{
SegmentList[segmentName].First().FieldList[fieldIndex - 1].ComponentList[componentIndex - 1].SubComponentList[subComponentIndex - 1].Value = strValue;
isSet = true;
}
catch (Exception ex)
{
throw new HL7Exception("SubComponent not available - " + strValueFormat + " Error: " + ex.Message);
}
}
else if (comCount == 3)
{
Int32.TryParse(AllComponents[1], out fieldIndex);
Int32.TryParse(AllComponents[2], out componentIndex);
try
{
SegmentList[segmentName].First().FieldList[fieldIndex - 1].ComponentList[componentIndex - 1].Value = strValue;
isSet = true;
}
catch (Exception ex)
{
throw new HL7Exception("Component not available - " + strValueFormat + " Error: " + ex.Message);
}
}
else if (comCount == 2)
{
Int32.TryParse(AllComponents[1], out fieldIndex);
try
{
SegmentList[segmentName].First().FieldList[fieldIndex - 1].Value = strValue;
isSet = true;
}
catch (Exception ex)
{
throw new HL7Exception("Field not available - " + strValueFormat + " Error: " + ex.Message);
}
}
else
{
throw new HL7Exception("Cannot overwrite a segment value");
}
}
else
throw new HL7Exception("Segment name not available");
}
else
throw new HL7Exception("Request format is not valid");
return isSet;
}
/// <summary>
/// check if specified field has components
/// </summary>
/// <param name="strValueFormat">Field/Component position in format SEGMENTNAME.FieldIndex.ComponentIndex.SubComponentIndex example PID.5.2</param>
/// <returns>boolean</returns>
public bool IsComponentized(string strValueFormat)
{
bool isComponentized = false;
bool isValid = false;
string segmentName = string.Empty;
int fieldIndex = 0;
int comCount = 0;
List<string> AllComponents = MessageHelper.SplitString(strValueFormat, new char[] { '.' });
comCount = AllComponents.Count;
isValid = validateValueFormat(AllComponents);
if (isValid)
{
segmentName = AllComponents[0];
if (comCount >= 2)
{
try
{
Int32.TryParse(AllComponents[1], out fieldIndex);
isComponentized = SegmentList[segmentName].First().FieldList[fieldIndex - 1].IsComponentized;
}
catch (Exception ex)
{
throw new HL7Exception("Field not available - " + strValueFormat + " Error: " + ex.Message);
}
}
else
throw new HL7Exception("Field not identified in request");
}
else
throw new HL7Exception("Request format is not valid");
return isComponentized;
}
/// <summary>
/// check if specified fields has repeatitions
/// </summary>
/// <param name="strValueFormat">Field/Component position in format SEGMENTNAME.FieldIndex.ComponentIndex.SubComponentIndex example PID.5.2</param>
/// <returns>boolean</returns>
public bool HasRepetitions(string strValueFormat)
{
bool hasRepetitions = false;
bool isValid = false;
string segmentName = string.Empty;
int fieldIndex = 0;
int comCount = 0;
List<string> AllComponents = MessageHelper.SplitString(strValueFormat, new char[] { '.' });
comCount = AllComponents.Count;
isValid = validateValueFormat(AllComponents);
if (isValid)
{
segmentName = AllComponents[0];
if (comCount >= 2)
{
try
{
Int32.TryParse(AllComponents[1], out fieldIndex);
hasRepetitions = SegmentList[segmentName].First().FieldList[fieldIndex - 1].HasRepetitions;
}
catch (Exception ex)
{
throw new HL7Exception("Field not available - " + strValueFormat + " Error: " + ex.Message);
}
}
else
throw new HL7Exception("Field not identified in request");
}
else
throw new HL7Exception("Request format is not valid");
return hasRepetitions;
}
/// <summary>
/// check if specified component has sub components
/// </summary>
/// <param name="strValueFormat">Field/Component position in format SEGMENTNAME.FieldIndex.ComponentIndex.SubComponentIndex example PID.5.2</param>
/// <returns>boolean</returns>
public bool IsSubComponentized(string strValueFormat)
{
bool isSubComponentized = false;
bool isValid = false;
string segmentName = string.Empty;
int fieldIndex = 0;
int componentIndex = 0;
int comCount = 0;
List<string> AllComponents = MessageHelper.SplitString(strValueFormat, new char[] { '.' });
comCount = AllComponents.Count;
isValid = validateValueFormat(AllComponents);
if (isValid)
{
segmentName = AllComponents[0];
if (comCount >= 3)
{
try
{
Int32.TryParse(AllComponents[1], out fieldIndex);
Int32.TryParse(AllComponents[2], out componentIndex);
isSubComponentized = SegmentList[segmentName].First().FieldList[fieldIndex - 1].ComponentList[componentIndex - 1].IsSubComponentized;
}
catch (Exception ex)
{
throw new HL7Exception("Component not available - " + strValueFormat + " Error: " + ex.Message);
}
}
else
throw new HL7Exception("Component not identified in request");
}
else
throw new HL7Exception("Request format is not valid");
return isSubComponentized;
}
/// <summary>
/// Builds the acknowledgement message for this message
/// </summary>
/// <returns>An ACK message if success, otherwise null</returns>
public Message GetACK()
{
return this.createAckMessage("AA", false, null);
}
/// <summary>
/// Builds a negative ack for this message
/// </summary>
/// <param name="code">ack code like AR, AE</param>
/// <param name="errMsg">Error message to be sent with NACK</param>
/// <returns>A NACK message if success, otherwise null</returns>
public Message GetNACK(string code, string errMsg)
{
return this.createAckMessage(code, true, errMsg);
}
/// <summary>
/// Adds a segemnt to the message
/// </summary>
/// <param name="newSegment">Segment to be appended to the end of the message</param>
/// <returns>True if added sucessfully, otherwise false</returns>
public bool AddNewSegment(Segment newSegment)
{
try
{
newSegment.SequenceNo = SegmentCount++;
if (!SegmentList.ContainsKey(newSegment.Name))
SegmentList[newSegment.Name] = new List<Segment>();
SegmentList[newSegment.Name].Add(newSegment);
return true;
}
catch (Exception ex)
{
SegmentCount--;
throw new HL7Exception("Unable to add new segment. Error - " + ex.Message);
}
}
/// <summary>
/// Removes a segment from the message
/// </summary>
/// <param name="segmentName">Segment to be removed/param>
/// <param name="index">Zero-based index of the sement to be removed, in case of multiple. Default is 0.</param>
/// <returns>True if found and removed sucessfully, otherwise false</returns>
public bool RemoveSegment(string segmentName, int index = 0)
{
try
{
if (!SegmentList.ContainsKey(segmentName))
return false;
var list = SegmentList[segmentName];
if (list.Count <= index)
return false;
list.RemoveAt(index);
return true;
}
catch (Exception ex)
{
throw new HL7Exception("Unable to add remove segment. Error - " + ex.Message);
}
}
public List<Segment> Segments()
{
return getAllSegmentsInOrder();
}
public List<Segment> Segments(string segmentName)
{
return getAllSegmentsInOrder().FindAll(o=> o.Name.Equals(segmentName));
}
public Segment DefaultSegment(string segmentName)
{
return getAllSegmentsInOrder().First(o => o.Name.Equals(segmentName));
}
/// <summary>
/// Addsthe header segment to a new message
/// </summary>
/// <param name="sendingApplication">Sending application name</param>
/// <param name="sendingFacility">Sending facility name</param>
/// <param name="receivingApplication">Receiving application name</param>
/// <param name="receivingFacility">Receiving facility name</param>
/// <param name="security">Security features. Can be null.</param>
/// <param name="messageType">Message type ^ trigger event</param>
/// <param name="messageControlID">Message control unique ID</param>
/// <param name="processingID">Processing ID ^ processing mode</param>
/// <param name="version">HL7 message version (2.x)</param>
public void AddSegmentMSH(string sendingApplication, string sendingFacility, string receivingApplication, string receivingFacility,
string security, string messageType, string messageControlID, string processingID, string version)
{
var dateString = MessageHelper.LongDateWithFractionOfSecond(DateTime.Now);
var delim = this.Encoding.FieldDelimiter;
string response = "MSH" + this.Encoding.AllDelimiters + delim + sendingApplication + delim + sendingFacility + delim
+ receivingApplication + delim + receivingFacility + delim
+ dateString + delim + (security ?? string.Empty) + delim + messageType + delim + messageControlID + delim
+ processingID + delim + version + this.Encoding.SegmentDelimiter;
var message = new Message(response);
message.ParseMessage();
this.AddNewSegment(message.DefaultSegment("MSH"));
}
/// <summary>
/// Serialize to MLLP escaped byte array
/// </summary>
/// <param name="validate">Optional. Validate the message before serializing</param>
/// <returns>MLLP escaped byte array</returns>
public byte[] GetMLLP(bool validate = false)
{
string hl7 = this.SerializeMessage(validate);
return MessageHelper.GetMLLP(hl7);
}
/// <summary>
/// Builds an ACK or NACK message for this message
/// </summary>
/// <param name="code">ack code like AA, AR, AE</param>
/// <param name="isNack">true for generating a NACK message, otherwise false</param>
/// <param name="errMsg">error message to be sent with NACK</param>
/// <returns>An ACK or NACK message if success, otherwise null</returns>
private Message createAckMessage(string code, bool isNack, string errMsg)
{
var response = new StringBuilder();
if (this.MessageStructure != "ACK")
{
var dateString = MessageHelper.LongDateWithFractionOfSecond(DateTime.Now);
var msh = this.SegmentList["MSH"].First();
var delim = this.Encoding.FieldDelimiter;
response.Append("MSH").Append(this.Encoding.AllDelimiters).Append(delim).Append(msh.FieldList[4].Value).Append(delim).Append(msh.FieldList[5].Value).Append(delim)
.Append(msh.FieldList[2].Value).Append(delim).Append(msh.FieldList[3].Value).Append(delim)
.Append(dateString).Append(delim).Append(delim).Append("ACK").Append(delim).Append(this.MessageControlID).Append(delim)
.Append(this.ProcessingID).Append(delim).Append(this.Version).Append(this.Encoding.SegmentDelimiter);
response.Append("MSA").Append(delim).Append(code).Append(delim).Append(this.MessageControlID).Append((isNack ? delim + errMsg : string.Empty)).Append(this.Encoding.SegmentDelimiter);
}
else
{
return null;
}
try
{
var message = new Message(response.ToString());
message.ParseMessage();
return message;
}
catch
{
return null;
}
}
/// <summary>
/// Validates the HL7 message for basic syntax
/// </summary>
/// <returns>A boolean indicating whether the whole message is valid or not</returns>
private bool validateMessage()
{
try
{
if (!string.IsNullOrEmpty(HL7Message))
{
//check message length - MSH+Delimeters+12Fields in MSH
if (HL7Message.Length < 20)
{
throw new HL7Exception("Message Length too short: " + HL7Message.Length + " chars.", HL7Exception.BAD_MESSAGE);
}
//check if message starts with header segment
if (!HL7Message.StartsWith("MSH"))
{
throw new HL7Exception("MSH segment not found at the beggining of the message", HL7Exception.BAD_MESSAGE);
}
this.Encoding.EvaluateSegmentDelimiter(this.HL7Message);
this.HL7Message = string.Join(this.Encoding.SegmentDelimiter, MessageHelper.SplitMessage(this.HL7Message)) + this.Encoding.SegmentDelimiter;
//check Segment Name & 4th character of each segment
char fourthCharMSH = HL7Message[3];
this.allSegments = MessageHelper.SplitMessage(HL7Message);
foreach (string strSegment in this.allSegments)
{
if (string.IsNullOrWhiteSpace(strSegment))
continue;
bool isValidSegName = false;
string segmentName = strSegment.Substring(0, 3);
string segNameRegEx = "[A-Z][A-Z][A-Z1-9]";
isValidSegName = System.Text.RegularExpressions.Regex.IsMatch(segmentName, segNameRegEx);
if (!isValidSegName)
{
throw new HL7Exception("Invalid segment name found: " + strSegment, HL7Exception.BAD_MESSAGE);
}
char fourthCharSEG = strSegment[3];
if (fourthCharMSH != fourthCharSEG)
{
throw new HL7Exception("Invalid segment found: " + strSegment, HL7Exception.BAD_MESSAGE);
}
}
string _fieldDelimiters_Message = this.allSegments[0].Substring(3, 8 - 3);
this.Encoding.EvaluateDelimiters(_fieldDelimiters_Message);
// Count field separators, MSH.12 is required so there should be at least 11 field separators in MSH
int countFieldSepInMSH = this.allSegments[0].Count(f => f == Encoding.FieldDelimiter);
if (countFieldSepInMSH < 11)
{
throw new HL7Exception("MSH segment doesn't contain all the required fields", HL7Exception.BAD_MESSAGE);
}
// Find Message Version
var MSHFields = MessageHelper.SplitString(this.allSegments[0], Encoding.FieldDelimiter);
if (MSHFields.Count >= 12)
{
this.Version = MessageHelper.SplitString(MSHFields[11], Encoding.ComponentDelimiter)[0];
}
else
{
throw new HL7Exception("HL7 version not found in the MSH segment", HL7Exception.REQUIRED_FIELD_MISSING);
}
//Find Message Type & Trigger Event
try
{
string MSH_9 = MSHFields[8];
if (!string.IsNullOrEmpty(MSH_9))
{
var MSH_9_comps = MessageHelper.SplitString(MSH_9, this.Encoding.ComponentDelimiter);
if (MSH_9_comps.Count >= 3)
{
this.MessageStructure = MSH_9_comps[2];
}
else if (MSH_9_comps.Count > 0 && MSH_9_comps[0] != null && MSH_9_comps[0].Equals("ACK"))
{
this.MessageStructure = "ACK";
}
else if (MSH_9_comps.Count == 2)
{
this.MessageStructure = MSH_9_comps[0] + "_" + MSH_9_comps[1];
}
else
{
throw new HL7Exception("Message Type & Trigger Event value not found in message", HL7Exception.UNSUPPORTED_MESSAGE_TYPE);
}
}
else
throw new HL7Exception("MSH.10 not available", HL7Exception.UNSUPPORTED_MESSAGE_TYPE);
}
catch (System.IndexOutOfRangeException e)
{
throw new HL7Exception("Can't find message structure (MSH.9.3) - " + e.Message, HL7Exception.UNSUPPORTED_MESSAGE_TYPE);
}
try
{
this.MessageControlID = MSHFields[9];
if (string.IsNullOrEmpty(this.MessageControlID))
throw new HL7Exception("MSH.10 - Message Control ID not found", HL7Exception.REQUIRED_FIELD_MISSING);
}
catch (Exception ex)
{
throw new HL7Exception("Error occured while accessing MSH.10 - " + ex.Message, HL7Exception.REQUIRED_FIELD_MISSING);
}
try
{
this.ProcessingID = MSHFields[10];
if (string.IsNullOrEmpty(this.ProcessingID))
throw new HL7Exception("MSH.11 - Processing ID not found", HL7Exception.REQUIRED_FIELD_MISSING);
}
catch (Exception ex)
{
throw new HL7Exception("Error occured while accessing MSH.11 - " + ex.Message, HL7Exception.REQUIRED_FIELD_MISSING);
}
}
else
throw new HL7Exception("No Message Found", HL7Exception.BAD_MESSAGE);
}
catch (Exception ex)
{
throw new HL7Exception("Failed to validate the message with error - " + ex.Message, HL7Exception.BAD_MESSAGE);
}
return true;
}
/// <summary>
/// Serializes a field into a string with proper encoding
/// </summary>
/// <returns>A serialized string</returns>
private void serializeField(Field field, StringBuilder strMessage)
{
if (field.ComponentList.Count > 0)
{
int indexCom = 0;
foreach (Component com in field.ComponentList)
{
indexCom++;
if (com.SubComponentList.Count > 0)
strMessage.Append(string.Join(Encoding.SubComponentDelimiter.ToString(), com.SubComponentList.Select(sc => Encoding.Encode(sc.Value))));
else
strMessage.Append(Encoding.Encode(com.Value));
if (indexCom < field.ComponentList.Count)
strMessage.Append(Encoding.ComponentDelimiter);
}
}
else
strMessage.Append(Encoding.Encode(field.Value));
}
/// <summary>
/// Get all segments in order as they appear in original message. This the usual order: IN1|1 IN2|1 IN1|2 IN2|2
/// </summary>
/// <returns>A list of segments in the proper order</returns>
private List<Segment> getAllSegmentsInOrder()
{
List<Segment> _list = new List<Segment>();
foreach (string segName in SegmentList.Keys)
{
foreach (Segment seg in SegmentList[segName])
{
_list.Add(seg);
}
}
return _list.OrderBy(o => o.SequenceNo).ToList();
}
/// <summary>
/// Validates the components of a value's position descriptor
/// </summary>
/// <returns>A boolean indicating whether all the components are valid or not</returns>
private bool validateValueFormat(List<string> allComponents)
{
string segNameRegEx = "[A-Z][A-Z][A-Z1-9]";
string otherRegEx = @"^[1-9]([0-9]{1,2})?$";
bool isValid = false;
if (allComponents.Count > 0)
{
if (Regex.IsMatch(allComponents[0], segNameRegEx))
{
for (int i = 1; i < allComponents.Count; i++)
{
if (Regex.IsMatch(allComponents[i], otherRegEx))
isValid = true;
else
return false;
}
}
else
{
isValid = false;
}
}
return isValid;
}
}
}
MessageElement.cs
/// <summary>
/// https://github.com/Efferent-Health/HL7-dotnetcore
/// </summary>
namespace HL7.Dotnetcore
{
public abstract class MessageElement
{
protected string _value = string.Empty;
public string Value
{
get
{
return _value == Encoding.PresentButNull ? null : _value;
}
set
{
_value = value;
ProcessValue();
}
}
public HL7Encoding Encoding { get; protected set; }
protected abstract void ProcessValue();
}
}
MessageHelper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
/// <summary>
/// https://github.com/Efferent-Health/HL7-dotnetcore
/// </summary>
namespace HL7.Dotnetcore
{
public static class MessageHelper
{
private static string[] lineSeparators = { "\r\n", "\n\r", "\r", "\n" };
public static List<string> SplitString(string strStringToSplit, string splitBy, StringSplitOptions splitOptions = StringSplitOptions.None)
{
return strStringToSplit.Split(new string[] { splitBy }, splitOptions).ToList();
}
public static List<string> SplitString(string strStringToSplit, char chSplitBy, StringSplitOptions splitOptions = StringSplitOptions.None)
{
return strStringToSplit.Split(new char[] { chSplitBy }, splitOptions).ToList();
}
public static List<string> SplitString(string strStringToSplit, char[] chSplitBy, StringSplitOptions splitOptions = StringSplitOptions.None)
{
return strStringToSplit.Split(chSplitBy, splitOptions).ToList();
}
public static List<string> SplitMessage(string message)
{
return message.Split(lineSeparators, StringSplitOptions.None).Where(m => !string.IsNullOrWhiteSpace(m)).ToList();
}
public static string LongDateWithFractionOfSecond(DateTime dt)
{
return dt.ToString("yyyyMMddHHmmss.FFFF");
}
public static string[] ExtractMessages(string messages)
{
var expr = "\x0B(.*?)\x1C\x0D";
var matches = Regex.Matches(messages, expr, RegexOptions.Singleline);
var list = new List<string>();
foreach (Match m in matches)
list.Add(m.Groups[1].Value);
return list.ToArray();
}
public static DateTime? ParseDateTime(string dateTimeString, bool throwExeption = false)
{
return ParseDateTime(dateTimeString, out TimeSpan offset, throwExeption);
}
public static DateTime? ParseDateTime(string dateTimeString, out TimeSpan offset, bool throwExeption = false)
{
var expr = @"^\s*((?:19|20)[0-9]{2})(?:(1[0-2]|0[1-9])(?:(3[0-1]|[1-2][0-9]|0[1-9])(?:([0-1][0-9]|2[0-3])(?:([0-5][0-9])(?:([0-5][0-9](?:\.[0-9]{1,4})?)?)?)?)?)?)?(?:([+-][0-1][0-9]|[+-]2[0-3])([0-5][0-9]))?\s*$";
var matches = Regex.Matches(dateTimeString, expr, RegexOptions.Singleline);
try
{
if (matches.Count != 1)
throw new FormatException("Invalid date format");
var groups = matches[0].Groups;
int year = int.Parse(groups[1].Value);
int month = groups[2].Success ? int.Parse(groups[2].Value) : 1;
int day = groups[3].Success ? int.Parse(groups[3].Value) : 1;
int hours = groups[4].Success ? int.Parse(groups[4].Value) : 0;
int mins = groups[5].Success ? int.Parse(groups[5].Value) : 0;
float fsecs = groups[6].Success ? float.Parse(groups[6].Value) : 0;
int secs = (int)Math.Truncate(fsecs);
int msecs = (int)Math.Truncate(fsecs * 1000) % 1000;
int tzh = groups[7].Success ? int.Parse(groups[7].Value) : 0;
int tzm = groups[8].Success ? int.Parse(groups[8].Value) : 0;
offset = new TimeSpan(tzh, tzm, 0);
return new DateTime(year, month, day, hours, mins, secs, msecs);
}
catch
{
if (throwExeption)
throw;
offset = new TimeSpan();
return null;
}
}
/// <summary>
/// Serialize string to MLLP escaped byte array
/// </summary>
/// <param name="message">String to serialize</param>
/// <param name="encoding">Text encoder (optional)</param>
/// <returns>MLLP escaped byte array</returns>
public static byte[] GetMLLP(string message, Encoding encoding = null)
{
if (encoding == null)
encoding = Encoding.UTF8;
byte[] data = encoding.GetBytes(message);
byte[] buffer = new byte[data.Length + 3];
buffer[0] = 11;//VT
Array.Copy(data, 0, buffer, 1, data.Length);
buffer[buffer.Length - 2] = 28;//FS
buffer[buffer.Length - 1] = 13;//CR
return buffer;
}
}
}
Segment.cs
using System;
using System.Collections.Generic;
/// <summary>
/// https://github.com/Efferent-Health/HL7-dotnetcore
/// </summary>
namespace HL7.Dotnetcore
{
public class Segment : MessageElement
{
internal FieldCollection FieldList { get; set; }
internal short SequenceNo { get; set; }
public string Name { get; set; }
public Segment(HL7Encoding encoding)
{
this.FieldList = new FieldCollection();
this.Encoding = encoding;
}
public Segment(string name, HL7Encoding encoding)
{
this.FieldList = new FieldCollection();
this.Name = name;
this.Encoding = encoding;
}
protected override void ProcessValue()
{
List<string> allFields = MessageHelper.SplitString(_value, this.Encoding.FieldDelimiter);
if (allFields.Count > 1)
{
allFields.RemoveAt(0);
}
for (int i = 0; i < allFields.Count; i++)
{
string strField = allFields[i];
Field field = new Field(this.Encoding);
if (Name == "MSH" && i == 0)
field.IsDelimiters = true; // special case
field.Value = strField;
this.FieldList.Add(field);
}
if (this.Name == "MSH")
{
var field1 = new Field(this.Encoding);
field1.IsDelimiters = true;
field1.Value = this.Encoding.FieldDelimiter.ToString();
this.FieldList.Insert(0,field1);
}
}
public Segment DeepCopy()
{
var newSegment = new Segment(this.Name, this.Encoding);
newSegment.Value = this.Value;
return newSegment;
}
public void AddEmptyField()
{
this.AddNewField(string.Empty);
}
public void AddNewField(string content, int position = -1)
{
this.AddNewField(new Field(content, this.Encoding), position);
}
public void AddNewField(string content, bool isDelimiters)
{
var newField = new Field(this.Encoding);
if (isDelimiters)
newField.IsDelimiters = true; // Prevent decoding
newField.Value = content;
this.AddNewField(newField, -1);
}
public bool AddNewField(Field field, int position = -1)
{
try
{
if (position < 0)
{
this.FieldList.Add(field);
}
else
{
position = position - 1;
this.FieldList.Add(field, position);
}
return true;
}
catch (Exception ex)
{
throw new HL7Exception("Unable to add new field in segment " + this.Name + " Error - " + ex.Message);
}
}
public Field Fields(int position)
{
position = position - 1;
Field field = null;
try
{
field = this.FieldList[position];
}
catch (Exception ex)
{
throw new HL7Exception("Field not available Error - " + ex.Message);
}
return field;
}
public List<Field> GetAllFields()
{
return this.FieldList;
}
}
}
SubComponent.cs
/// <summary>
/// https://github.com/Efferent-Health/HL7-dotnetcore
/// </summary>
namespace HL7.Dotnetcore
{
public class SubComponent : MessageElement
{
public SubComponent(string val, HL7Encoding encoding)
{
this.Encoding = encoding;
this.Value = val;
}
protected override void ProcessValue()
{
}
}
}