Coder Social home page Coder Social logo

efferent-health / hl7-dotnetcore Goto Github PK

View Code? Open in Web Editor NEW

This project forked from j4jayant/hl7-csharp-parser

226.0 24.0 114.0 419 KB

Lightweight HL7 C# parser and composer compatible with .Net Core and .Net Standard

License: MIT License

C# 100.00%
hl7 hl7-message netcoreapp netstandard hl7v2 hl7-parsing hl7-generating

hl7-dotnetcore's Introduction

HL7-dotnetcore

NuGet github Build Status

This is a lightweight library for building and parsing HL7 2.x messages, for .Net Standard and .Net Core. It is not tied to any particular version of HL7 nor validates against one.

Object construction

Create a Message object and pass a raw HL7 message in text format

Message message = new Message(strMsg);

// Parse this message

bool isParsed = false;

try
{
    isParsed = message.ParseMessage();
}
catch(Exception ex)
{
    // Handle the exception
}

Adding a message header

For adding a header segment to a new message object, use the AddSegmentMSH() method, after constructing an empty message:

message.AddSegmentMSH(sendingApplication, sendingFacility, 
    receivingApplication, receivingFacility,
    security, messageType, 
    messageControlId, processingId, version);

⚠️ Notice that every HL7 message needs a header segment to be considered valid

Message extraction

If the HL7 message is coming from a MLLP connection (see the official documentation), the message needs to be cleared from the MLLP prefixes and suffixes. Also, consider there can be more than one message in a single MLLP frame.

For this purpose, there is an ExtractMessages() method, to be used as follows:

// extract the messages from a buffer containing a MLLP frame
var messages = MessageHelper.ExtractMessages(buffer);

// construct and process each message
foreach (var strMsg in messages)
{
    Message message = new Message(strMsg);
    message.ParseMessage(); // Required by most operations
    // do something with the message object
}

Bypass Validation

Message  message = new Message(strMsg)
message.ParseMessage(true);

Accessing Segments

Get list of all segments

List<Segment> segList = message.Segments();

Get List of list of repeated segments by name

For example if there are multiple IN1 segments

List<Segment> IN1List = message.Segments("IN1");

Access a particular occurrence from multiple IN1s providing the index

Note index 1 will return the 2nd element from list

Segment IN1_2 = message.Segments("IN1")[1];

Get count of IN1s

int countIN1 = message.Segments("IN1").Count;

Access first occurrence of any segment

Segment IN1 = message.DefaultSegment("IN1");

// OR

Segment IN1 = message.Segments("IN1")[0];

Accessing Fields

Access field values

string SendingFacility = message.GetValue("MSH.4");

// OR

string SendingFacility = message.DefaultSegment("MSH").Fields(4).Value;

// OR

string SendingFacility = message.Segments("MSH")[0].Fields(4).Value;

Segment ocurrences

string ContactPhone = message.GetValue("NK1(2).5"); // Second occurrence of NK1

Check if field is componentized

bool isComponentized = message.Segments("PID")[0].Fields(5).IsComponentized;

// OR

bool isComponentized = message.IsComponentized("PID.5");

Check if field has repetitions

bool isRepeated = message.Segments("PID")[0].Fields(3).HasRepetitions;

// OR

bool isRepeated = message.HasRepetitions("PID.3");

Adding repeating field

var enc = new HL7Encoding();
Segment PID = new Segment("PID", enc);
Field f = new Field(enc);
f.HasRepetitions = true;

// Adding field f1 to f
Field f1 = new Field("A", enc);
f.AddRepeatingField(f1);

// Adding field f2 to f
Field f2 = new Field("B", enc);
f.AddRepeatingField(f2);

Get list of repeated fields

List<Field> repList = message.Segments("PID")[0].Fields(3).Repetitions();

Get particular repetition i.e. 2nd repetition of PID.3

Field PID3_R2 = message.GetValue("PID.3[2]");

// OR

Field PID3_R2 = message.GetValue("PID.3(2)");

Update value of any field i.e. to update PV1.2 – patient class

message.SetValue("PV1.2", "I");

// OR

message.Segments("PV1")[0].Fields(2).Value = "I";

Access some of the required MSH fields with properties

string version = message.Version;
string msgControlID = message.MessageControlID;
string messageStructure = message.MessageStructure;

Generate ACKs

To generate an ACK message

Message ack = message.GetACK();

To generate negative ACK (NACK) message with error message

Message nack = message.GetNACK("AR", "Invalid Processing ID");

It may be required to change the application and facility fields

Message ack = message.GetACK();
ack.SetValue("MSH.3", appName);
ack.SetValue("MSH.4", facility);

⚠️ Take into account that a message shall be previously parsed before attempting to generate an ACK or NACK message.

Accessing Components

Access particular component i.e. PID.5.1 – Patient Family Name

string PatName1 = message.GetValue("PID.5.1");

// OR

string PatName1 = message.Segments("PID")[0].Fields(5).Components(1).Value;

Check if component is sub componentized

bool isSubComponentized = message.Segments("PV1")[0].Fields(7).Components(1).IsSubComponentized;

// OR

bool isSubComponentized = message.IsSubComponentized("PV1.7.1");

Update value of any component

message.Segments("PID")[0].Fields(5).Components(1).Value = "Jayant";

// OR

message.SetValue("PID.5.1", "Jayant");

Adding new Segment

// Create a Segment with name ZIB
Segment newSeg = new Segment("ZIB");
 
// Create Field ZIB_1
Field ZIB_1 = new Field("ZIB1");
// Create Field ZIB_5
Field ZIB_5 = new Field("ZIB5");
 
// Create Component ZIB.5.2
Component com1 = new Component("ZIB.5.2");
 
// Add Component ZIB.5.2 to Field ZIB_5
// 2nd parameter here specifies the component position, for inserting segment on particular position
// If we don’t provide 2nd parameter, component will be inserted to next position (if field has 2 components this will be 3rd, 
// If field is empty this will be 1st component
ZIB_5.AddNewComponent(com1, 2);
 
// Add Field ZIB_1 to segment ZIB, this will add a new filed to next field location, in this case first field
newSeg.AddNewField(ZIB_1);
 
// Add Field ZIB_5 to segment ZIB, this will add a new filed as 5th field of segment
newSeg.AddNewField(ZIB_5, 5);
 
// Add segment ZIB to message
bool success = message.AddNewSegment(newSeg);

New Segment would look like this:

ZIB|ZIB1||||ZIB5^ZIB.5.2

After evaluated and modified required values, the message can be obtained again in text format

string strUpdatedMsg = message.SerializeMessage();

Remove Trailing Components

var message = new Message();

// create ORC segment
var orcSegment = new Segment("ORC", new HL7Encoding());

// add fields
for (int eachField = 1; eachField <= 12; eachField++)
{
    orcSegment.AddEmptyField();
}

// add components to field 12
for (int eachField = 1; eachField < 8; eachField++)
{
    orcSegment.Fields(12).AddNewComponent(new Component(new HL7Encoding()));
}

// add values to components
orcSegment.Fields(12).Components(1).Value = "should not be removed";
orcSegment.Fields(12).Components(2).Value = "should not be removed";
orcSegment.Fields(12).Components(3).Value = "should not be removed";
orcSegment.Fields(12).Components(4).Value = ""; // should not be removed because in between valid values
orcSegment.Fields(12).Components(5).Value = "should not be removed";
orcSegment.Fields(12).Components(6).Value = ""; // should be removed because trailing
orcSegment.Fields(12).Components(7).Value = ""; // should be removed because trailing
orcSegment.Fields(12).Components(8).Value = ""; // should be removed because trailing

orcSegment.Fields(12).RemoveEmptyTrailingComponents();
message.AddNewSegment(orcSegment);

string serializedMessage = message.SerializeMessage(false);

Remove a Segment

Segments are removed individually, including the case where there are repeated segments with the same name

    // Remove the first segment with name NK1
    bool success = message.RemoveSegment("NK1") 

    // Remove the second segment with name NK1
    bool success = message.RemoveSegment("NK1", 1) 

Encoded segments

Some contents may contain forbidden characters like pipes and ampersands. Whenever there is a possibility of having those characters, the content shall be encoded before calling the 'AddNew' methods, like in the following code:

var obx = new Segment("OBX", new HL7Encoding());

// Not encoded. Will be split into parts.
obx.AddNewField("70030^Radiologic Exam, Eye, Detection, FB^CDIRadCodes");  

// Encoded. Won't be parsed nor split.
obx.AddNewField(obx.Encoding.Encode("domain.com/resource.html?Action=1&ID=2"));  

Copying a segment

The DeepCopy method allows to perform a clone of a segment when building new messages. Countersense, if a segment is referenced directly when adding segments to a message, a change in the segment will affect both the origin and new messages.

Segment pid = ormMessage.DefaultSegment("PID").DeepCopy();
oru.AddNewSegment(pid);

Null elements

Null elements (fields, components or subcomponents), also referred to as Present But Null, are expressed in HL7 messages as double quotes, like (see last field):

EVN|A04|20110613083617||""

Whenever requested individually, those elements are returned as null, rather than double quotes:

var expectEmpty = evn.Fields(3).Value; // Will return an empty string
var expectNull = evn.Fields(4).Value; // Will return null

If this behavior is not desirable, it can be disabled by setting Encoding.PresentButNull to null before parsing:

var message = new Message(msg);
message.Encoding.PresentButNull = null;
message.ParseMessage();

Date Handling

A couple of date handling methods have been added, for parsing elements containing valid date/times, including time zones, as described in the HL7 standard. Examples:

// With time zone
string value1 = "20151231234500.1234+2358";
TimeSpan offset;
DateTime? dt1 = MessageHelper.ParseDateTime(value1, out offset);

// Date/time only
string value2 = "20151231234500";
DateTime? dt2 = MessageHelper.ParseDateTime(value2);

ParseDateTime will catch exceptions by default and return null in case of invalid dates. For preventing this mechanism, add an extra argument as true, like:

try
{
    var dt1 = MessageHelper.ParseDateTime(value1, out TimeSpan offse, true);
    var dt2 = MessageHelper.ParseDateTime(value2, true);
}
catch 
{
    // do something here
}

Credits

This is a fork from Jayant Singh's HL7 parser (2013). Since then, it has been modified fundamentally, with respect to features, code quality, bugs and typos. For more information about the original implementation read:

The field encoding and decoding methods have been based on https://github.com/elomagic/hl7inspector

Breaking changes

Since version 2.9, the MSH segment will have an extra field at the beginning of the segment list, containing the field separator. This is according to the HL7 standard, as mentioned in Issue #26. Every field index in that segment should be increased by one.

Since version 2.9, some previously deprecated methods starting with lowercase have been removed. The replacement methods starting with uppercase shall be used instead.

Since version 2.21, message.GetValue() will decode the returned content. In version 2.33, it was homologated with the Value property.

hl7-dotnetcore's People

Contributors

3v07 avatar bokc avatar chickenpollo avatar chrismidolo avatar domibies avatar eldorturgunov avatar gg-garcia avatar j4jayant avatar jaime-olivares avatar kevintighe avatar kfrancis avatar knucklhead13 avatar kulak avatar lixfeld avatar msdoege avatar pdevito3 avatar ravenheart avatar robepstein avatar serhey-iv avatar stefanod avatar wokket avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hl7-dotnetcore's Issues

Problem with AddSegmentMSH method

Hello! Good joob for de library but i have a problem

When i trie tu use message.AddSegmentMSH(...)
i obtain this error: System.InvalidOperationException: 'Sequence contains no matching element'

i use this method becouse i have an error when i trie "newSeg.AddNewField(encoding.AllDelimiters, 2);"

My best regards!

Can't build MSH Segment

Hi,
I could not find an example how I create a MSH segment.
Here is a minimal example what I tried:

[Fact]
public void Escaping()
{
	Segment mshSeg = new Segment("MSH", new HL7Encoding());
	mshSeg.AddNewField(@"^~\&", 1);

	Message message = new Message();
	message.AddNewSegment(mshSeg);
	var str = message.SerializeMessage(false);

	Assert.Equal("MSH|^~\\&\r", str);
}

Unfortunatelly I get following error:

HL7.Dotnetcore.HL7Exception : Invalid escape sequence in HL7 string

This happens in HL7Encoding.Decode().

Am I creating a MSH segment the wrong way or is this a bug?

Access/mutate a specific component in a repeated field?

How do I access the value 13000722 ?

PID|1||13000722^^^CS^PI^^^~154921123^^^NLMINBIZA^NNNLD^^20120425^~""^^^NLMINBIZA^PPN~4866812344^^^NLRDW^DL~""^^^NLIND^CZ~""^^^NLMINBUZA^CZ ...

This works:
message.Segments("PID")[0].Fields(3).Repetitions(1).Components(1).Value

But what if I want to use a syntax like message.getValue("PID.3.1")

Segment.GetAllFields()-Ordering

I just noticed the following: There seems to be an incorrect order when getting all the fields for a segment. Obviously, the code does not state that it actually does guarantee any order but I obviously expected some by gut feeling.

I am opening this issue as a point of discussion. I have attached two NUnit-Tests to show the problem, one that I would have expected to work and one that actually works.

EXAMPLE.txt

Field IsSubComponentized edge case

Hi.
I believe I have found a bug.
I believe that the boolean property IsComponentized on a field should be TRUE if the field consists only of one component which is SubComponentized and nothing else.
Example of that is OBR segment and field 33, which can have 11 components.
However this is what I get in a message:
|23&xxx&yyy&&&&&&zzz|

This is FIELD 33 => COMPONENT1 => SUBCOMPONENTS [1...9]
The IsComponentized for this field returns FALSE, I believe it should return TRUE and one (1) component with subcomponents. Am I right or am I doing something wrong?

RemoveSegment

Hi, is there any support for removing Segment?

Thanks

HL7 Message object has zero segments although the message string contains segments

I want to parse incoming HL7 ADT messages and create HL7 ACK messages as responses. I created the following code based on the samples from the docs

    string messageString = "\vMSH|^~\\&|KISsystem|ZTM|NIDAklinikserver|HL7Connector|201902271130||ADT^A01|68371142|P|2.3\rEVN|A01|201902271130|201902271130\rPID|1||677789||Aubertin�^Letizia||19740731|F||\rPV1|1|O|||||||||||||||||456456456\rIN1|1||999567890|gematik Musterkasse1GKV||||||||||||Prof. Dr.Aubertin�^Letizia||19740731|||||||||||201902271101|||||||X110173919\u001c\r"
    string[] hl7MessageStrings = MessageHelper.ExtractMessages(messageString);

    foreach (string hl7MessageString in hl7MessageStrings)
    {
        /* 
            hl7MessageString has almost the same 
            content as messageString but without the 
            prefixes and suffixes. The content is:

            "MSH|^~\\&|KISsystem|ZTM|NIDAklinikserver|HL7Connector|201902271130||ADT^A01|68371142|P|2.3\rEVN|A01|201902271130|201902271130\rPID|1||677789||Aubertin�^Letizia||19740731|F||\rPV1|1|O|||||||||||||||||456456456\rIN1|1||999567890|gematik Musterkasse1GKV||||||||||||Prof. Dr.Aubertin�^Letizia||19740731|||||||||||201902271101|||||||X110173919"
        */

        Message hl7Message = new Message(hl7MessageString);
        List<Segment> segments = hl7Message.Segments(); // This has a count of 0
        Message ackMessage = hl7Message.GetACK(); // This throws an exception, because MSH is missing
    }

When generating the ACK message I get the following exception

System.Collections.Generic.KeyNotFoundException: The given key 'MSH'
was not present in the dictionary.

but the MSH segment is present? How can I fix this?

Escape characters

I am having problems with certain escape characters in messages.

Specifically one laboratory uses \X00d as an escape sequence for Carriage Return and \X0A as New Line. The library however uses a weird \X00D and \X00A (1.5 bytes?). The error is in Encoding.Encode line 89.

According to HL& standards both implementations are wrong, it should use 1 byte (two hexadecimal numbers) for sub 32 ascii characters, however I think for maximum compatibility it should somehow ignore whether 1/1.5/2 bytes are used for escaped characters.

MSH|^~\&|iLab|CibaLab|Joystick|АГ Д-р Щерев|20200304170212||ORU^R01^ORU_R01|XATQXL2UYUP5SJDERAXH|P|2.5.1
SFT|SKYWARE Group|5.0.0.6775|iLabCourier|5.0.0.6775
PID|1||0000000000^^^GRAO^NI~5948556^^^Laboratory^MR~3700023^^^LIS^XX||^^||19921029^D|F
NTE|1||--- Footnotes ---\X000d\\X0A\   * 1. (LH ( Лутеинизиращ Хормон )): \X000d\\X0A\Референтни граници на Лутеинизиращ хормон ( LH ) за жени в репродуктивна възраст по фази в IU/L: \X000d\\X0A\фоликулна 2.4-12.6; овулаторна 14-96; лутеална 1.0-11.4; \X000d\\X0A\Референтни граници за жени в менопауза 7.7-59 IU/L;\X000d\\X0A\Референтни граници за мъже в репродуктивна възраст: 1.7-8.6 IU/L;\X000d\\X0A\   * 2. (Estradiol  (Естрадиол)): \X000d\\X0A\Референтни граници на Естрадиол ( Estradiol ) за жени в репродуктивна възраст по фази в  pmol/L:\X000d\\X0A\фоликулна 45.4-854; овулаторна 151-1461; лутеална 81.9-1251; Референтни граници за жени в менопауза: до 505 pmol/L;\X000d\\X0A\Референтни граници за бременни в  pmol/L по триместри:\X000d\\X0A\1-ви триместър   563 - 11902,\X000d\\X0A\2-ри триместър   5729 - 78098, \X000d\\X0A\3-ти триместър  от 31287  до над 110100;\X000d\\X0A\Референтни граници за мъже в репродуктивна възраст: 94.8-223 pmol/L;\X000d\\X0A\   * 3. (Progesterone ( Прогестерон )): \X000d\\X0A\Референтни граници на Прогестерон ( Progesterone ) за жени в репродуктивна възраст по фази в nmol/L: \X000d\\X0A\фоликулна 0.18-2.84; овулаторна 0.38-38.1; лутеална 5.82-75.9;\X000d\\X0A\За жени в менопауза: до 0.40 nmol/L;\X000d\\X0A\Референтни граници за бременни в  nmol/L: \X000d\\X0A\1-ви трим. 35.0-141 \X000d\\X0A\2-ри трим. 80.8-264 \X000d\\X0A\3-ти трим. 187-681;\X000d\\X0A\Референтни граници за мъже в репродуктивна възраст: до 0.474 nmol/L;|RE^^HL70364
PV1|1|N|||||||||||||||||5948556^^^Laboratory^MR|||||||||||||||||||||||||202003041653^M|||||||V
ORC|RE|250042|5948556^iLab||A||^^^202003041653|||||^Лаборатория^Кл.^^^^^^BLS^^^^DN
OBR|1|250042|5948556^iLab|2579-1^LH ( Лутеинизиращ Хормон )^LN^0-64^^HCPT|||202003041653^M|||||||||||||Хормонален анализ|0
OBX|1|NM|2579-1^LH ( Лутеинизиращ Хормон )^LN^0-64^^HCPT|||IU/l|Виж забележка под черта||||I|||202003041653^M||2300006863^Калъчев^Недялко^Иванов^^Д-р^^^BLS^^^^DN
SPM|1|^S20IMQ||SER^Серум^HL70487|||||||||||||202003041653
ORC|RE|250042|5948556^iLab||A||^^^202003041653|||||^Лаборатория^Кл.^^^^^^BLS^^^^DN
OBR|2|250042|5948556^iLab|14715-7^Estradiol  (Естрадиол)^LN^0-67^^HCPT|||202003041653^M|||||||||||||Хормонален анализ|0
OBX|1|NM|14715-7^Estradiol  (Естрадиол)^LN^0-67^^HCPT|||pmol/l|Виж забележка под черта||||I|||202003041653^M||2300006863^Калъчев^Недялко^Иванов^^Д-р^^^BLS^^^^DN
SPM|1|^S20IMQ||SER^Серум^HL70487|||||||||||||202003041653
ORC|RE|250042|5948556^iLab||A||^^^202003041653|||||^Лаборатория^Кл.^^^^^^BLS^^^^DN
OBR|3|250042|5948556^iLab|14890-8^Progesterone ( Прогестерон )^LN^0-68^^HCPT|||202003041653^M|||||||||||||Хормонален анализ|0
OBX|1|NM|14890-8^Progesterone ( Прогестерон )^LN^0-68^^HCPT|||nmol/l|Виж забележка под черта||||I|||202003041653^M||2300006863^Калъчев^Недялко^Иванов^^Д-р^^^BLS^^^^DN
SPM|1|^S20IMQ||SER^Серум^HL70487|||||||||||||202003041653

.Net Framework question

Hello im trying to use it on 4.5.2 Framework version but im vs is unable to install the nuget, what .netframework is necessary?

Component failing to parse

The PV1 segment "PV1.7.1" for example doesn't parse out. It behaves as if it thinks PV1.7 is not componentized.

Here's a test that shows the issue (should be copiable to VS):

[Test]
public void ParsePV1()
{
string hl7File =
@"MSH|^&|EPIC||||20191107134803|ALEVIB01|ORM^O01|23|T|2.3|||||||||||
PID|1||10034926^^^NYU MRN^MRN||OSTRICH^DODUO||19820605|M||U|360 PARK AVE SOUTH^^NEW YORK^NY^10010^US^^^60|60|(615)613-2113^HOME^PH|||S|||999-99-9999|||U||N||||||||
PV1||O|NWSLED^^^NYULHLI^^^^^LI NW SLEEP DISORDER^^DEPID||||1447312459^WEINSTEIN^MICHAEL^^^^^^EPIC^^^^PNPI
WEINSM06^WEINSTEIN^MICHAEL^^^^^^KID^^^^KID|1447312459^WEINSTEIN^MICHAEL^^^^^^EPIC^^^^PNPIWEINSM06^WEINSTEIN^MICHAEL^^^^^^KID^^^^KID|||||||||||496779945|||||||||||||||||||||||||20191107|||||||V
ORC|NW|162431500^EPC|30011922^EPC||Scheduled||^^^20191107134353^^R||20191107134806|ALEVIB01^ALEVIS^BRIAN^^||1447312459^WEINSTEIN^MICHAEL^^^^^^EPIC^^^^PNPI
WEINSM06^WEINSTEIN^MICHAEL^^^^^^KID^^^^KID|1084022001^^^1084022^^^^^LI NW SLEEP DISORDER&NYU WINTHROP SLEEP CENTER|(516)663-2834|||||||||||||||O|
OBR|1|162431500^EPC|30011922^EPC|IMG16525^SLEEP STUDY UNATTENDED^NAT^^SLEEP STUDY UNATTENDED|R|20191107134744|||||Ancillary Pe|||||1447312459^WEINSTEIN^MICHAEL^^^^^^EPIC^^^^PNPIWEINSM06^WEINSTEIN^MICHAEL^^^^^^KID^^^^KID|(516)663-2834|NWSLED^^^^^^^^LI NW SLEEP DISORDER^^DEPIDLI NW SLEEP HOME STUDY||||||OT| Scheduled ||^^^20191107134355^^R|||||||||20191107134500||||||||IMG16525^SLEEP STUDY UNATTENDED^NAT^^SLEEP STUDY UNATTENDED|
NTE|1||Where will this exam be scheduled?->NYU Winthrop Sleep Disorders Center||
DG1|1|ICD-10-CM|W58.03XA^Crushed by alligator, initial encounter^ICD-10-CM|Crushed by alligator, initial encounter||^180;ORD
ZDS|1.2.840.114350.2.232.3.798268.2.162431500.1^EPIC^APPLICATION^DICOM";

        Message msg = new Message(hl7File);
        msg.ParseMessage();

        //This is OK
        string PID = msg.GetValue("PID.3.1");
        Assert.AreEqual("10034926", PID);

        //This fails, actually throws exception:
        try
        {
            string attendingDrId = msg.GetValue("PV1.7.1");
            Assert.AreEqual("1447312459", attendingDrId);
        }catch(Exception parseEx)
        {
            string err = parseEx.Message;
        }
    }

Quotes missing

When a field contains two double quotes, "field.Value" returns only one double quote. Is this by design or is this a bug? I find this odd behavior.

Feature: access segment repititions via SetValue and GetValue

First off, thanks for nhapi alternative. Nhapi just does too many checks (no one implements the norm anyway - it seems) and throws totally meaningless and confusing errors.

As for multiple segment-repititions, you have an example on how to access it directly using the Segments property.

But.. I mostly use the SetValue and GetValue functions. Could you add the ability to reference segment repitions there too? I tried to google a syntax, I think others (nhapi??) do it with the repetition number in (braces), like DG1(2).2.1, not sure about best syntax though.

Also, sorry to mix 2 things here, for me I found it useful to have a SetValue and GetValue that always succeeds (never throws but just returns null or empty) and created some helpers for that. I don't know whether it would be an idea to add that code here.

Parse message throws exception

The parse throws an exception if it can't parse the message.
The expected behavior based on the method should be that it returns a false if the message is not being able to be parsed.

Thanks

Compilation warnings when resolving nuget packages

When using this package in a .Net Core 2.1 application in Visual Studio 2017, the following warnings are displayed during compilation. This appears to be a result of the way these dependencies are referenced in the .csproj file.

The nuget refs in the .csproj file look like this:
<PackageReference Include="System.Runtime" Version="*" />

I believe that the Version="*" is causing this problem, as the compiler is attempting to reference version 2.9.0 of each nuget package, which is the current version of this package.

Warning NU1603 HL7-dotnetcore 2.9.0 depends on System.Linq (>= 2.9.0) but System.Linq 2.9.0 was not found. An approximate best match of System.Linq 4.0.0 was resolved.

Warning NU1603 HL7-dotnetcore 2.9.0 depends on System.Runtime (>= 2.9.0) but System.Runtime 2.9.0 was not found. An approximate best match of System.Runtime 4.0.0 was resolved.

Warning NU1603 HL7-dotnetcore 2.9.0 depends on System.Runtime.Extensions (>= 2.9.0) but System.Runtime.Extensions 2.9.0 was not found. An approximate best match of System.Runtime.Extensions 4.0.0 was resolved.

Warning NU1603 HL7-dotnetcore 2.9.0 depends on System.Text.RegularExpressions (>= 2.9.0) but System.Text.RegularExpressions 2.9.0 was not found. An approximate best match of System.Text.RegularExpressions 4.0.0 was resolved.

Tab space will cause message to fail

When parsing a message, a tab space will be treated as white space and after the message is parsed, is no longer equal to the original message, causing the message parsed to fail the final check.

Manual

Hello, can you publish manual for using your code? Thank you.

Add .NET 4.5 target

I know this is primarily targeting .NET standard, but there's nothing in here that limits it specifically to NetStandard1.3+. The library I'm building is targeting NetStandard2.0 AND Net45 so I would love if this would get pulled in. I've redone the tests to target everything as well.

Naming convention?

I noticed that Message object has setValue(), getAck() methods which are public, they should be uppercase of first letter in NET naming convention, ex: SetValue() or GetAck().

Hope this nice library can follow it, otherwise it's so weird in using.
BTW, it's a good library, thanks.

MSH segments are misparsed, preventing GetValue() selection

Upon parsing existing HL7 data using HL7-dotnetcore version 2.8.0 from NuGet, MSH segment fields cannot be accessed by their correct index. They suffer from an off-by-1 index issue. Specifically, it appears that MSH.1 is being misparsed. This severely impacts querying MSH data.

Expectation

According to the spec ( https://www.hl7.org/documentcenter/public_temp_CACD15D9-1C23-BA17-0C050D19F5A35765/wg/conf/HL7MSH.htm ), MSH.1 should be have a value of | for conventional messages and MSH.2 should have a value of ^~\&.

Result

Actual parsing of a very simple, faux message results in MSH.1 getting parsed with the MSH.2 value and all subsequent MSH.# queries are 1 index higher than they should be.

Code to reproduce

static void Test()
{
	// HL7 string is intentionally multi-line to ensure segment delimiter is recognized
	const string dummyA08 = @"MSH|^~\&|sendingApplication|sendingFacility|receivingApplication|receivingFacility|20100314063000||ADT^A08|-1|D||||
";
	var message = new Message(dummyA08);
	message.ParseMessage();
	Console.WriteLine($"MSH.1 - expected '|' - actual '{message.GetValue("MSH.1")}'");
	const string msh2Expectation = @"^~\&";
	Console.WriteLine($"MSH.2 - expected '{msh2Expectation}' - actual '{message.GetValue("MSH.2")}'");
	Console.WriteLine($"MSH.3 - expected 'sendingApplication' - actual '{message.GetValue("MSH.3")}'");
}

Value "present but null" vs Empty

In HL7 section 2.5.3.0, Field or Component Status, it specifies that a field value of "" (e.g. |""|) is considered to be a null value and should not be used for any other purpose. In the parser, a field value of "" is returned as a string value of 2 double quotes, not a null string value.

Add model binding

I've created a very simple attribute/model binding addition. Wondering if you would like it included. The problem I was solving with your code was parsing a message into a model, but I was getting tired of writing the same code over and over. What I've come up with is the following:

Let's say you have a Patient object that you need to get data in from the HL7 message. In the end, what you call is like this.

    Message message = new Message(strMsg);
    message.ParseMessage(); // For brevity, don't check.
    Patient patient = message.Bind<Patient>();

Bind is an extension method (for now), but it works because the fields of Patient are decorated with custom attributes like this:

    public class Patient {
        [HL7StringField("ERQ", Field=4)]
        public string HealthNumber { get;set; }

        [HL7DateTimeField("PID", Field=3, Component=2, Repetition=2, Format="yyyyMMddd")]
        public string DateTime BirthDate { get; set; }

        [HL7EnumField("PID", Field=3, Component=3, SubComponent=1, EnumType=typeof(Gender))]
        public Gender Gender { get;set; }

        // etc
    }

It works absolutely brilliantly, I think it would be a good addition to your library as filling a model is most likely one of first things people want to do with a parsed HL7 message.

Let me know if you would like this as a PR.

How do I include MSH.2?

If I include the following code, then I get the exception here:

var hl7Encoding = new HL7Encoding() { };
var msh = new Segment("MSH", hl7Encoding);
var msh_2 = new Field("MSH2", hl7Encoding);
msh_2.Value = @"^~\&"; // problem here
msh.AddNewField(msh_2);

How can I include MSH.2 with the value ^~\& in the serialized message?

2.0 Support

Any chance for .NET Standard 2.0 support?

Nuget Package contains unoptimised build

When referencing the published nuget package (v2.10.0) BenchmarkDotNet fails with the following error:

Assembly ConsoleApp1 which defines benchmarks references non-optimized HL7-dotnetcore
        If you own this dependency, please, build it in RELEASE.
        If you don't, you can create custom config with DontFailOnError to disable our custom policy and allow this benchmark to run.

Given the performance critical nature of most HL7 codebases publishing an optimised release build seems worthwhile 👍

field repetitions

When accessing a field with a specified repetition, the library wrongly returns the value of the first repetition if there are no others.

So

getting from "PV1.8(2).1"

will return value from "PV1.8.1"

if there are no repetitions.

This could be fixed in Message.cs getField e. g. by replacing

        if (field.HasRepetitions)
            field = field.RepeatitionList[repetition];

        return field;

with

        if (field.HasRepetitions)
            return field.RepeatitionList[repetition];
        else if (repetition == 0)
            return field;
        else
            return null;

Not beautiful, maybe you see something better.
Also, there is a typo, "RepeatitionList".

Encode messages for MLP

When sending back ACK messages I'm currently doing

Message ackMessage = hl7Message.GetACK();
string ackMessageString = ackMessage.HL7Message;
byte[] ackMessageBytes = Encoding.UTF8.GetBytes(ackMessageString);
// ... Send message via TCP ...

The problem is that ackMessageString does not contain the pre- and suffixes required for the MLP. This package is able to parse message strings containing those pre- and suffixes as desribed here

https://github.com/Efferent-Health/HL7-dotnetcore#message-extraction

but is it also able to encode messages back to strings? My current dirty solution would be something like this:

string ackMessageString = $"{(char) 11}{ackMessage.HL7Message}{(char) 28}{(char) 13}";

but maybe the package is able to do it for me :)

ParseTest() and HL7_ADT

Running ParseTest with Message based on HL7_ADT which points to Sample-ADT.txt returns isParsed = false.

on or thereabouts line 111, there is the following:

if (this.Equals(strSerializedMessage)) isParsed = true;

this fails because the two messages are different in that the original has a blank segment and therefore 5 segments instead of the parsed version which has 4.

is it true that a Message would not be equivalent if the difference were extra blank segments?

Incorrect fix for the "Present but null"-issue (see #28)

You have added a Unit Test when fixing #28 however this seems incomplete. Modify the test as follows:

[Test]
public void EmptyAndNullFields()
{
      const string sampleMessage = "MSH|^~\\&|SA|SF|RA|RF|20110613083617||ADT^A04|123|P|2.7||||\r\nEVN|A04|20110613083617||\"\"";

      var message = new Message(sampleMessage);

      //the changed part:
      var parseResult = message.ParseMessage();
            Assert.That( parseResult, Is.True, "!!> FAILED TO PARSE A MESSAGE HERE IN THE TEST <!!" );
            Assert.That( message.SegmentCount, Is.GreaterThan(0), "!!> FAILED TO PARSE A MESSAGE HERE IN THE TEST, PRODUCED ZERO SEGMENTS <!!" );

       var evn = message.Segments("EVN")[0];

       var expectEmpty = evn.Fields(3).Value;
       Assert.AreEqual(string.Empty, expectEmpty);
       var expectNull = evn.Fields(4).Value;
       Assert.AreEqual(null, expectNull);
}

Now, the test fails. Aka your test message is not being parsed correctly and the behavior of 2.10 is actually rather undefined.

How to properly create new HL7 messages

I tried to follow the samples from the README and this is how I create a basic message for now:

                Message mdmMessage = new Message();
                HL7Encoding hl7Encoding = new HL7Encoding();

                //################################
                // Add a field e.g. TXA.1

                Segment txaSegment = new Segment("TXA", hl7Encoding);
                txaSegment.AddNewField("1" /* the Id */, 1);
                mdmMessage.AddNewSegment(txaSegment);

                //################################
                // Add a component field e.g. PID.5.1

                Segment pidSegment = new Segment("PID", hl7Encoding);
                Field patientNameField = new Field(hl7Encoding);
                Component pidFamilyNameComponent = new Component("Doe" /* The family name */, hl7Encoding);
                patientNameField.AddNewComponent(pidFamilyNameComponent, 1 /* PID.5.1 */);
                pidSegment.AddNewField(patientNameField, 5 /* PID.5 */);
                mdmMessage.AddNewSegment(pidSegment);

                // This throws an exception on validation
                // "Failed to validate the message with error - No Message Found"
                string messageString = mdmMessage.SerializeMessage(false);
  • When validating the message serialization I get an exception

"Failed to validate the message with error - No Message Found"

do you have any ideas why?

  • The code above looks a little bit messy when creating a huge HL7 message with multiple fields from multiple segments. I tried to shorten it down by simply doing this
                Message mdmMessage = new Message();

                // This will throw an exception because the segment
                // does not exist nor the field nor the component
                mdmMessage.SetValue("PID.5.1", "Doe");

but as described in the comments this doesn't seem to be possible because I have to create the elements first. Any improvements?

Different ACK message than 7Edit

Task: Generate Ack message from:

MSH|^~\&|||||20180504113248||ORU^R01|1525433568114-1|P|2.6
PID|1||PatientID_1||Patient Name
OBR|1||1|^Serotonin Test
OBX|1|ST|Serotonin||100|µg|||||F|||20180504013248
OBX|2|ST|HIV||Negative||||||F|||20180504013248

ACK Message of HL7-dotnetcore:

MSH|^~\&||20180504113248|||20180504133248.1424|ORU^R01|ACK|1525433568114-1|P|2.6
MSA|AA|1525433568114-1

ACK Message of 7Edit:

MSH|^~\&|||||20180504130942||ACK|1525432181978-1|P|2.6
MSA|AA|1525432181978-1

HL7-dotnect library has 20180504113248 as Sending Facility and ORU^R01 in the Security field which doesn't seem correct, but I'm no expert.

I guess, GetNACK() has the same problem.

AddSegmentMSH throws exception 'Failed to validate the message with error - Message Type & Trigger Event value not found in message'

I want to create a MDM_T02 message with a MSH, PID, TXA and OBX segment. Based on

https://github.com/Efferent-Health/HL7-dotnetcore#adding-a-message-header

I started with this

Message mdmMessage = new Message();

mdmMessage.AddSegmentMSH(
    "sendingApplication",
    "sendingFacility",
    "receivingApplication",
    "receivingFacility",
    string.Empty,
    "MDM_T02",
    $"Id{DateTime.Now.Ticks}",
    "P",
    "2.6");

But unfortunately AddSegmentMSH throws the following exception

Failed to validate the message with error - Message Type & Trigger Event value not found in message

What is missing?

Thanks in advance

Request breaking API change: MessageHelper.ParseDateTime

Looking at Microsoft's base types like int, DateTime or DateTimeOffset I can find two different kinds of parsing methods:

  • DataType.Parse(...) that returns DateType or throws an informative exception if parsing somehow fails.
  • DataType.TryParse(...) that returns true + out parameter or false if parsing was not successful.

Returning DateTime? (nullable type) as option type is a well known pattern from FP languages. However, it prevents me from seeing the exact exception message. I'd suggest three signatures:

  • DateTime ParseDateTime(string value) (this one throws exceptions. Cannot be added because it conflicts with the current signature)
  • DateTime? TryParseDateTime(string value) (option type, Try* indicates it could return null)
  • bool TryParseDateTime(string value, out DateTime result) (old fashion Try-Pattern)

(optionall) Automatically add elements on write access

When I set a value on any level, this only works for already existing elements.

But.. sometimes, when I set a value, I don't want to care if the value is existing and want a convenient way of saying something like SetOrAddAndSetThen. That would be a variation of the existing

    public bool SetValue(string strValueFormat, string strValue)

in Message.cs.

I already implemented this in a local copy, though it is not really nice (a little bit forced) and also now have to merge it each time there is a change in the library code.

Segment example doesn't work with latest version

HL7-dotnetcore version: 2.0.5

From your example:

//Create a Segment with name ZIB
Segment newSeg = new Segment("ZIB");

The actual constructor is:

public Segment(HL7Encoding encoding);
public Segment(string name, HL7Encoding encoding);

Multiple version support?

For what version will this work? There are some difference between versions, so I'm asking for what versions will this work.
Good work BTW

When generating NACK

Hi, I am trying to generate negative ack and got some problem, it said "Key not found", see the picture below.

Any idea on how to resolve this?

image

Empty fields at the end of segments are ommitted

Empty fields at the end of segments are ommitted.

E.g. it seems:
ORC|RO|2001290459^CS|||A|F|^^^20171102^^R|||||I12007^Doe, D.|CHI||||E||

becomes:
ORC|RO|2001290459^CS|||A|F|^^^20171102^^R|||||I12007^Doe, D.|CHI||||E

and therefore the entire message is never succesfully parsed!

Index out of Range Exception on Example code

Please add following test in your master branch:

[Fact]
public void AddComponents()
{
	//Create a Segment with name ZIB
	Segment newSeg = new Segment("ZIB", new HL7Encoding());

	// Create Field ZIB_1
	Field ZIB_1 = new Field("ZIB1", new HL7Encoding());
	// Create Field ZIB_5
	Field ZIB_5 = new Field("ZIB5", new HL7Encoding());

	// Create Component ZIB.5.2
	Component com1 = new Component("ZIB.5.2", new HL7Encoding());

	// Add Component ZIB.5.2 to Field ZIB_5
	ZIB_5.AddNewComponent(com1, 2);

	// Add Field ZIB_1 to segment ZIB, this will add a new filed to next field location, in this case first field
	newSeg.AddNewField(ZIB_1);

	// Add Field ZIB_5 to segment ZIB, this will add a new filed as 5th field of segment
	newSeg.AddNewField(ZIB_5, 5);

	// Add segment ZIB to message
	var message = new Message(this.HL7_ADT);
	message.AddNewSegment(newSeg);

	string serializedMessage = message.SerializeMessage(false);
	Assert.Equal("ZIB|ZIB1||||^ZIB.5.2", serializedMessage);
}

And you get following error:

HL7.Dotnetcore.HL7Exception: "Unable to add new component Error - Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index"

The test is based on your example code.

Parsing long message

If I attempt to parse a long message (this one in particular contains large amounts of Base64 encoded data in the form of GIF/PNG/PDF/JPG/TIF, nothing breaks but the parsing is incomplete.

I've tried "cheating" a bit and breaking up the message to circumvent any internal length restriction that's happening during parsing like so:

    public bool ParseAllMessage(HL7.Dotnetcore.Message message)
    {
        try
        {
            var hl7 = message.HL7Message;
            var segments = hl7.Split('\r').ToList();

            List<string> hl7SubBlocks = new List<string>();
            int counter = 0;
            string currentBlock = "";
            string msh = "";
            List<string> waitingSegments = new List<string>() { "MSH", "MSA", "QAK", "ERQ", "PID", "PV1", "ORC", "OBR", "ZBR", "BLG", "OBX", "ZBX", "NTE", "ZNT" };
            foreach (var segment in segments)
            {
                if (segment.Substring(0, 3) == "MSH") msh = segment;
                if (waitingSegments.Contains(segment.Substring(0, 3))) waitingSegments.Remove(segment.Substring(0, 3));
                currentBlock += segment + '\r';
                if (segment.Substring(0, 3) == "BLG" && waitingSegments.Count == 0) counter++;
                if (counter >= 5)
                {
                    hl7SubBlocks.Add(currentBlock);
                    currentBlock = "";
                    counter = 0;
                }
            }

            foreach (var hl7SubBlock in hl7SubBlocks)
            {
                HL7.Dotnetcore.Message tmpMessage = new HL7.Dotnetcore.Message(msh+'\r'+hl7SubBlock);
                tmpMessage.ParseMessage();
                foreach (var innerSegment in tmpMessage.Segments())
                {
                    message.Segments(innerSegment.Name).Add(innerSegment);
                }
            }
            return true;
        }
        catch (Exception ex)
        {
            return false;
        }
    }

But this doesn't have the desired effect. I can't just add the segment data in this way. Is there an internal limit being placed on the parse method? I don't see it in your code, though stepping through it is in the next step.

Message string is too short to get parsed

I'm using the tool 7Edit to send HL7 messages from my application to the 7Edit HL7 listener for testing purposes.

My TCP socket receives the following NACK response message (I converted the bytes to a string)

\vNACK\u001c\r

// This returns an array with 1 element with the content "NACK"
string[] hl7MessageStrings = MessageHelper.ExtractMessages(responseString);
    
// Parse the message strings to HL7 message objects
Message[] hl7Messages = hl7MessageStrings
    .Select(hl7MessageString =>
    {
        Message hl7Message = new Message(hl7MessageString);                        
        hl7Message.ParseMessage();

        return hl7Message;
    })
    .ToArray();

When parsing the HL7 message it throws the following exception

Failed to validate the message with error - Message Length too short:
4 chars.

The code fails because of the validateMessage method. I found it at

https://github.com/Efferent-Health/HL7-dotnetcore/blob/master/src/Message.cs#L699

So does 7Edit send invalid response messages? Is it a little bug in this package? The 7Edit log clearly shows its response here

image

and I'm not sure if they are missing the MSH segment.

Repeating Fields

Good evening,
First of all, thank you for this project, it is very helpful.

I have problem, because in my program I have to add into the segment PID, a repeating field, for the phone and mobile numbers, but unfortunately I do not have access to the internal RepeatitionList property.

Can you tell me how can I implement this, without changing the accessibility operator?
I need this output :

PID||||||||||||||(0033)4521254215^PRN^PH~(0033)62512455^ORN^CP~^NET^Internet^[email protected]

Best regards,
Constantin

Nuget download fail.

thanks for this project which is great for IVD software work with LIS or HIS.
I try to download the project through Nuget but fail.
the environment here is .NETFramework,Version=v4.5.2 in Windows Form project.
Does this library only work in .net core?

--- Lan from China / [email protected]

How to get the next element value e.g OBR have result value written in next NTE element.

Hello there.
First of all I want to thank you about this awesome tool, it really helps me to solve HL7 based problems.
I am stuck in one scenario Like I have OBR elements like below

OBR|42|11474-211913|K7084882|5272^HLA DRB1 HR-DRB3,4,5 INTERMEDIATE RESOLUTION|||202002111020|||||||||1912089277^Hello^world^^^^^^N||||||20200221|||F

And after that there is OBX element

OBX|1|ST|R166215^DBR1^^^LN||Comment||||||F||||LI5

And after this I have NTE element

NTE|1|| DRB1*14:04:01:01

Now the problem is the OBR element have result value in NTE element that is DRB1*14:04:01:01
So any idea that how I reach to the next NTE element and pick the result value. NTE elements are repeated and coming after every OBR elements. I can't hardcode the index of these elements.

I really appreciate if you let me know is that possible with this parser.

Thanks

CX Fields

Hello,
I would like to know if threre is a way to create a repeated field CX when sending an HL7 message?
Now, only the received messages are allowed to create repeated fields.

Best regards.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.