Skip to content
15 changes: 15 additions & 0 deletions src/NHapi.Base/Parser/ParserOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public ParserOptions()
InvalidObx2Type = null;
UnexpectedSegmentBehaviour = UnexpectedSegmentBehaviour.AddInline;
NonGreedyMode = false;
IncludeLongNameInEncodedXml = false;
DisableWhitespaceTrimmingOnAllXmlNodes = false;
XmlNodeNamesToDisableWhitespaceTrimming = new HashSet<string>(StringComparer.Ordinal);
PrettyPrintEncodedXml = true;
Expand Down Expand Up @@ -129,6 +130,20 @@ public ParserOptions()
/// </example>
public bool NonGreedyMode { get; set; }

/// <summary>
/// Gets or Sets flag which allows the Description attribute [LongName] to be added in encoded xml with counter position.
/// </summary>
/// <example>
/// <code>
/// <![CDATA[
/// <MSH.1 LongName="Field Separator">1</MSH.1>
/// <MSH.2 LongName="Encoding Characters">2</MSH.2>
/// ]]>
/// </code>
/// </example>
/// <remarks>The default value is <see langword="false"/>.</remarks>
public bool IncludeLongNameInEncodedXml { get; set; }

/// <summary>
/// <para>
/// If set to <see langword="true"/>, the <see cref="XMLParser"/> is configured to treat all whitespace
Expand Down
45 changes: 29 additions & 16 deletions src/NHapi.Base/Parser/XMLParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,11 @@ public virtual bool Encode(ISegment segmentObject, XmlElement segmentElement, Pa
continue;
}

if (parserOptions.IncludeLongNameInEncodedXml && reps[j] is AbstractType rep)
{
newNode.SetAttribute("LongName", rep.Description);
}

try
{
segmentElement.AppendChild(newNode);
Expand Down Expand Up @@ -731,17 +736,17 @@ private bool Encode(IType datatypeObject, XmlElement datatypeElement, ParserOpti
var hasData = false;

// TODO: consider using a switch statement
if (datatypeObject is Varies)
if (datatypeObject is Varies varies)
{
hasData = EncodeVaries((Varies)datatypeObject, datatypeElement, parserOptions);
hasData = EncodeVaries(varies, datatypeElement, parserOptions);
}
else if (datatypeObject is IPrimitive)
else if (datatypeObject is IPrimitive primitive)
{
hasData = EncodePrimitive((IPrimitive)datatypeObject, datatypeElement);
hasData = EncodePrimitive(primitive, datatypeElement);
}
else if (datatypeObject is IComposite)
else if (datatypeObject is IComposite composite)
{
hasData = EncodeComposite((IComposite)datatypeObject, datatypeElement, parserOptions);
hasData = EncodeComposite(composite, datatypeElement, parserOptions);
}

return hasData;
Expand Down Expand Up @@ -859,20 +864,28 @@ private bool EncodeComposite(IComposite datatypeObject, XmlElement datatypeEleme
{
var name = MakeElementName(datatypeObject, i + 1);
var newNode = datatypeElement.OwnerDocument.CreateElement(name, NameSpace);

var componentHasValue = Encode(components[i], newNode, parserOptions);
if (componentHasValue)
if (!componentHasValue)
{
try
{
datatypeElement.AppendChild(newNode);
}
catch (Exception e)
{
throw new DataTypeException("DOMException encoding Composite: ", e);
}
continue;
}

hasValue = true;
if (parserOptions.IncludeLongNameInEncodedXml && components[i] is AbstractType component)
{
newNode.SetAttribute("LongName", component.Description);
}

try
{
datatypeElement.AppendChild(newNode);
}
catch (Exception e)
{
throw new DataTypeException("DOMException encoding Composite: ", e);
}

hasValue = true;
}

return hasValue;
Expand Down
46 changes: 45 additions & 1 deletion tests/NHapi.NUnit/Parser/XMLParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,27 @@ public void Parse_EncodedMessageIsModifiedWithEscapeSequence_IsParsedCorrectly()
Assert.AreEqual($"\\H\\{obx5Value}\\.br\\{obx5Value}\\N\\", parsedObx5Value);
}

[Test]
public void Parse_MessageContainsLongNameAttributes_ParsedAsExpected()
{
// Arrange
var message = "<ADT_A01 xmlns=\"urn:hl7-org:v2xml\"><MSH><MSH.1 LongName=\"Field Separator\">|</MSH.1><MSH.2 LongName=\"Encoding Characters\">^~\\&amp;</MSH.2><MSH.3 LongName=\"Sending Application\"><HD.1 LongName=\"Namespace ID\">MILL</HD.1></MSH.3><MSH.4 LongName=\"Sending Facility\"><HD.1 LongName=\"Namespace ID\">EMRY</HD.1></MSH.4><MSH.5 LongName=\"Receiving Application\"><HD.1 LongName=\"Namespace ID\">MQ</HD.1></MSH.5><MSH.6 LongName=\"Receiving Facility\"><HD.1 LongName=\"Namespace ID\">EMRY</HD.1></MSH.6><MSH.7 LongName=\"Date / Time of Message\"><TS.1 LongName=\"Time of an event\">20150619155451</TS.1></MSH.7><MSH.9 LongName=\"Message Type\"><CM_MSG.1 LongName=\"Message type\">ADT</CM_MSG.1><CM_MSG.2 LongName=\"Trigger event\">A08</CM_MSG.2></MSH.9><MSH.10 LongName=\"Message Control ID\">Q2043855220T2330403781X928163</MSH.10><MSH.11 LongName=\"Processing ID\"><PT.1 LongName=\"Processing ID\">P</PT.1></MSH.11><MSH.12 LongName=\"Version ID\">2.3</MSH.12><MSH.18 LongName=\"Character Set\">8859/1</MSH.18></MSH><EVN><EVN.1 LongName=\"Event Type Code\">A08</EVN.1><EVN.2 LongName=\"Recorded Date/Time\"><TS.1 LongName=\"Time of an event\">20150619155451</TS.1></EVN.2></EVN><PID><PID.1 LongName=\"Set ID - Patient ID\">1</PID.1><PID.2 LongName=\"Patient ID (External ID)\"><CX.1 LongName=\"ID\">935307</CX.1><CX.4 LongName=\"Assigning authority\"><HD.1 LongName=\"Namespace ID\">EUH MRN</HD.1></CX.4><CX.5 LongName=\"Identifier type code\">MRN</CX.5><CX.6 LongName=\"Assigning facility\"><HD.1 LongName=\"Namespace ID\">EH01</HD.1></CX.6></PID.2><PID.3 LongName=\"Patient ID (Internal ID)\"><CX.1 LongName=\"ID\">25106376</CX.1><CX.4 LongName=\"Assigning authority\"><HD.1 LongName=\"Namespace ID\">TEC MRN</HD.1></CX.4></PID.3><PID.3 LongName=\"Patient ID (Internal ID)\"><CX.1 LongName=\"ID\">1781893</CX.1><CX.4 LongName=\"Assigning authority\"><HD.1 LongName=\"Namespace ID\">CLH MRN</HD.1></CX.4></PID.3><PID.3 LongName=\"Patient ID (Internal ID)\"><CX.1 LongName=\"ID\">935307</CX.1><CX.4 LongName=\"Assigning authority\"><HD.1 LongName=\"Namespace ID\">EUH MRN</HD.1></CX.4></PID.3><PID.3 LongName=\"Patient ID (Internal ID)\"><CX.1 LongName=\"ID\">5938067</CX.1><CX.4 LongName=\"Assigning authority\"><HD.1 LongName=\"Namespace ID\">EMPI</HD.1></CX.4></PID.3><PID.4 LongName=\"Alternate Patient ID\"><CX.1 LongName=\"ID\">1167766</CX.1><CX.4 LongName=\"Assigning authority\"><HD.1 LongName=\"Namespace ID\">CPI NBR</HD.1></CX.4><CX.6 LongName=\"Assigning facility\"><HD.1 LongName=\"Namespace ID\">EXTERNAL</HD.1></CX.6></PID.4><PID.4 LongName=\"Alternate Patient ID\"><CX.1 LongName=\"ID\">90509411</CX.1><CX.4 LongName=\"Assigning authority\"><HD.1 LongName=\"Namespace ID\">HNASYSID</HD.1></CX.4></PID.4><PID.4 LongName=\"Alternate Patient ID\"><CX.1 LongName=\"ID\">10341880</CX.1><CX.4 LongName=\"Assigning authority\"><HD.1 LongName=\"Namespace ID\">HNASYSID</HD.1></CX.4></PID.4><PID.4 LongName=\"Alternate Patient ID\"><CX.1 LongName=\"ID\">50627780</CX.1><CX.4 LongName=\"Assigning authority\"><HD.1 LongName=\"Namespace ID\">HNASYSID</HD.1></CX.4></PID.4><PID.4 LongName=\"Alternate Patient ID\"><CX.1 LongName=\"ID\">5938067</CX.1><CX.4 LongName=\"Assigning authority\"><HD.1 LongName=\"Namespace ID\">MSG_CERNPHR</HD.1></CX.4></PID.4><PID.5 LongName=\"Patient Name\"><XPN.1 LongName=\"Family name\">Patient</XPN.1><XPN.2 LongName=\"Given name\">Test</XPN.2><XPN.3 LongName=\"Middle initial or name\">Test</XPN.3><XPN.7 LongName=\"Name type code\">Cur_Name</XPN.7></PID.5><PID.7 LongName=\"Date of Birth\"><TS.1 LongName=\"Time of an event\">19400101</TS.1></PID.7><PID.8 LongName=\"Sex\">F</PID.8><PID.10 LongName=\"Race\">WHI</PID.10><PID.11 LongName=\"Patient Address\"><XAD.1 LongName=\"Street address\">123 ENDOFTHE RD</XAD.1><XAD.2 LongName=\"Other designation\">UNIT 123</XAD.2><XAD.3 LongName=\"City\">ATLANTA</XAD.3><XAD.4 LongName=\"State or province\">GA</XAD.4><XAD.5 LongName=\"Zip or postal code\">40000</XAD.5><XAD.6 LongName=\"Country\">USA</XAD.6><XAD.7 LongName=\"Address type\">HOME</XAD.7></PID.11><PID.13 LongName=\"Phone Number - Home\"><XTN.1 LongName=\"[(999)] 999-9999 [X99999][C any text]\">5555555555</XTN.1><XTN.2 LongName=\"Telecommunication use code\">HOME</XTN.2></PID.13><PID.13 LongName=\"Phone Number - Home\"><XTN.1 LongName=\"[(999)] 999-9999 [X99999][C any text]\">6666666666</XTN.1><XTN.2 LongName=\"Telecommunication use code\">[email protected]</XTN.2><XTN.3 LongName=\"Telecommunication equipment type (ID)\">EMAIL</XTN.3></PID.13><PID.14 LongName=\"Phone Number - Business\"><XTN.1 LongName=\"[(999)] 999-9999 [X99999][C any text]\">6666666666</XTN.1><XTN.2 LongName=\"Telecommunication use code\">BUS</XTN.2></PID.14><PID.15 LongName=\"Primary Language\"><CE.1 LongName=\"Identifier\">ENG</CE.1></PID.15><PID.16 LongName=\"Marital Status\">M</PID.16><PID.17 LongName=\"Religion\">OTH</PID.17><PID.18 LongName=\"Patient Account Number\"><CX.1 LongName=\"ID\">12345665161</CX.1><CX.4 LongName=\"Assigning authority\"><HD.1 LongName=\"Namespace ID\">EUH FIN</HD.1></CX.4><CX.5 LongName=\"Identifier type code\">FIN NBR</CX.5><CX.6 LongName=\"Assigning facility\"><HD.1 LongName=\"Namespace ID\">EH01</HD.1></CX.6></PID.18><PID.19 LongName=\"SSN Number - Patient\">123454103</PID.19><PID.20 LongName=\"Driver's License Number\"><DLN.1 LongName=\"Driver's License Number\">GA123450071</DLN.1></PID.20><PID.22 LongName=\"Ethnic Group\">Non-Hispanic</PID.22><PID.25 LongName=\"Birth Order\">0</PID.25><PID.26 LongName=\"Citizenship\">\"\"</PID.26><PID.27 LongName=\"Veterans Military Status\"><CE.1 LongName=\"Identifier\">\"\"</CE.1></PID.27><PID.28 LongName=\"Nationality Code\"><CE.1 LongName=\"Identifier\">\"\"</CE.1></PID.28><PID.30 LongName=\"Patient Death Indicator\">N</PID.30></PID></ADT_A01>";
var expectedMessageControlId = "Q2043855220T2330403781X928163";
var expectedDob = "19400101";

var parser = new DefaultXMLParser();

// Act
var parsed = parser.Parse(message);

var adtA01 = parsed as ADT_A01; // a08 is mapped to a01

// Assert
Assert.IsNotNull(adtA01);
Assert.AreEqual(expectedMessageControlId, adtA01.MSH.MessageControlID.Value);
Assert.AreEqual(expectedDob, adtA01.PID.DateOfBirth.TimeOfAnEvent.Value);
}

[Test]
public void Encode_OmdO03_CorrectlyHandlesEscaping()
{
Expand Down Expand Up @@ -345,6 +366,7 @@ public void Encode_GenericMessage_WorksAsExpected(string version)
// Arrange
var xmlParser = new DefaultXMLParser();
var type = GenericMessage.GetGenericMessageClass(version);
var expectedElementName = $"GenericMessageV{version.Replace(".", string.Empty)}";

var constructor = type.GetConstructor(new[] { typeof(IModelClassFactory) });
var message = (IMessage)constructor?.Invoke(new object[] { new DefaultModelClassFactory() });
Expand All @@ -354,7 +376,7 @@ public void Encode_GenericMessage_WorksAsExpected(string version)

// Assert
Assert.IsNotNull(document);
Assert.AreEqual($"GenericMessageV{version.Replace(".", string.Empty)}", document.DocumentElement?.LocalName);
Assert.AreEqual(expectedElementName, document.DocumentElement?.LocalName);
Assert.IsNotNull(xmlParser.Encode(message));
}

Expand All @@ -374,6 +396,28 @@ public void Encode_AdtA01_CanBeParsedAgain()
Console.WriteLine(decodedMessage.ToString());
}

[Test]
public void Encode_WithParserOption_IncludeLongNameInEncodedXMLIsTrue_ResultContainsLongNameAttribute()
{
// Arrange
var message = "MSH|^~\\&|KISsystem|ZTM|NIDAklinikserver|HL7Proxy|201902271130||ADT^A01|68371142|P|2.3\r"
+ "EVN|A01|201902271130|201902271130";

var expectedEncodedMessage = @"<?xml version=""1.0"" encoding=""utf-8""?><ADT_A01 xmlns=""urn:hl7-org:v2xml""><MSH><MSH.1 LongName=""Field Separator"">|</MSH.1><MSH.2 LongName=""Encoding Characters"">^~\&amp;</MSH.2><MSH.3 LongName=""Sending Application""><HD.1 LongName=""Namespace ID"">KISsystem</HD.1></MSH.3><MSH.4 LongName=""Sending Facility""><HD.1 LongName=""Namespace ID"">ZTM</HD.1></MSH.4><MSH.5 LongName=""Receiving Application""><HD.1 LongName=""Namespace ID"">NIDAklinikserver</HD.1></MSH.5><MSH.6 LongName=""Receiving Facility""><HD.1 LongName=""Namespace ID"">HL7Proxy</HD.1></MSH.6><MSH.7 LongName=""Date / Time of Message""><TS.1 LongName=""Time of an event"">201902271130</TS.1></MSH.7><MSH.9 LongName=""Message Type""><CM_MSG.1 LongName=""Message type"">ADT</CM_MSG.1><CM_MSG.2 LongName=""Trigger event"">A01</CM_MSG.2></MSH.9><MSH.10 LongName=""Message Control ID"">68371142</MSH.10><MSH.11 LongName=""Processing ID""><PT.1 LongName=""Processing ID"">P</PT.1></MSH.11><MSH.12 LongName=""Version ID"">2.3</MSH.12></MSH><EVN><EVN.1 LongName=""Event Type Code"">A01</EVN.1><EVN.2 LongName=""Recorded Date/Time""><TS.1 LongName=""Time of an event"">201902271130</TS.1></EVN.2><EVN.3 LongName=""Date/Time Planned Event""><TS.1 LongName=""Time of an event"">201902271130</TS.1></EVN.3></EVN></ADT_A01>";

var parser = new PipeParser();
var xmlParser = new DefaultXMLParser();
var options = new ParserOptions { IncludeLongNameInEncodedXml = true, PrettyPrintEncodedXml = false };

var parsed = parser.Parse(message);

// Act
var encodedMessage = xmlParser.Encode(parsed, options);

// Assert
Assert.AreEqual(expectedEncodedMessage, encodedMessage);
}

private static void SetMessageHeader(IMessage msg, string messageCode, string messageTriggerEvent, string processingId)
{
var msh = (ISegment)msg.GetStructure("MSH");
Expand Down