diff --git a/.github/workflows/build-status.yml b/.github/workflows/build-status.yml
index 0134f2771..60868795a 100644
--- a/.github/workflows/build-status.yml
+++ b/.github/workflows/build-status.yml
@@ -3,8 +3,7 @@ on:
push:
branches:
- master
-env:
- DOTNET_VERSION: '6.0.x'
+
jobs:
build-and-test:
name: Build And Test ${{matrix.os}}
@@ -18,7 +17,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3
with:
- dotnet-version: ${{ env.DOTNET_VERSION }}
+ global-json-file: global.json
- name: Restore nHapi (Windows)
if: matrix.os == 'windows-latest'
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 77bf197ef..3870ea71e 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -83,4 +83,4 @@ jobs:
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
- category: "/language:${{matrix.language}}"
+ category: "/language:${{matrix.language}}"
\ No newline at end of file
diff --git a/.github/workflows/receive-pr.yml b/.github/workflows/receive-pr.yml
index 184d92d26..db0187b65 100644
--- a/.github/workflows/receive-pr.yml
+++ b/.github/workflows/receive-pr.yml
@@ -1,8 +1,7 @@
name: Receive Pull Request
on: [pull_request]
-env:
- DOTNET_VERSION: '6.0.x'
+
jobs:
build-and-test:
name: Build And Test ${{matrix.os}}
@@ -16,7 +15,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3
with:
- dotnet-version: ${{ env.DOTNET_VERSION }}
+ global-json-file: global.json
- name: Restore nHapi (Windows)
if: matrix.os == 'windows-latest'
diff --git a/global.json b/global.json
index f7d2c85a5..55083d59a 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "6.0.101",
+ "version": "6.0.404",
"rollForward": "latestFeature"
},
"projects": []
diff --git a/src/NHapi.Base/Model/AbstractGroup.cs b/src/NHapi.Base/Model/AbstractGroup.cs
index 719335acf..aba079eaf 100644
--- a/src/NHapi.Base/Model/AbstractGroup.cs
+++ b/src/NHapi.Base/Model/AbstractGroup.cs
@@ -42,16 +42,11 @@ namespace NHapi.Base.Model
///
public abstract class AbstractGroup : IGroup
{
- private static readonly IHapiLog Log;
+ private static readonly IHapiLog Log = HapiLogFactory.GetHapiLog(typeof(AbstractGroup));
private readonly IModelClassFactory myFactory;
private List items;
- static AbstractGroup()
- {
- Log = HapiLogFactory.GetHapiLog(typeof(AbstractGroup));
- }
-
/// This constructor should be used by implementing classes that do not
/// also implement Message.
///
@@ -527,7 +522,7 @@ private IStructure TryToInstantiateStructure(Type c, string name)
var argClasses = new[] { typeof(IGroup), typeof(IModelClassFactory) };
var argObjects = new object[] { this, myFactory };
var con = c.GetConstructor(argClasses);
- o = con.Invoke(argObjects);
+ o = con!.Invoke(argObjects);
}
catch (MethodAccessException)
{
diff --git a/src/NHapi.Base/Model/AbstractPrimitive.cs b/src/NHapi.Base/Model/AbstractPrimitive.cs
index 41f27c06a..2ecc36c03 100644
--- a/src/NHapi.Base/Model/AbstractPrimitive.cs
+++ b/src/NHapi.Base/Model/AbstractPrimitive.cs
@@ -56,10 +56,10 @@ public AbstractPrimitive(IMessage message, string description)
{
}
- /// Sets the value of this Primitive, first performing validation as specified
- /// by. getMessage().getValidationContext(). No validation is performed
- /// if getMessage() returns null.
- ///
+ ///
+ /// Sets the value of this Primitive, first performing validation as specified
+ /// by. Message.ValidationContext
+ /// No validation is performed if returns null.
///
public virtual string Value
{
diff --git a/src/NHapi.Base/Model/GenericComposite.cs b/src/NHapi.Base/Model/GenericComposite.cs
index d5e4b2e49..387fb5622 100644
--- a/src/NHapi.Base/Model/GenericComposite.cs
+++ b/src/NHapi.Base/Model/GenericComposite.cs
@@ -1,6 +1,7 @@
namespace NHapi.Base.Model
{
using System.Collections;
+ using System.Collections.Generic;
///
/// An unspecified Composite datatype that has an undefined number of components, each
@@ -11,7 +12,7 @@ namespace NHapi.Base.Model
/// Bryan Tripp.
public class GenericComposite : AbstractType, IComposite
{
- private readonly ArrayList components;
+ private readonly List components;
///
/// Creates a new instance of GenericComposite.
@@ -30,25 +31,13 @@ public GenericComposite(IMessage theMessage)
public GenericComposite(IMessage theMessage, string description)
: base(theMessage, description)
{
- components = new ArrayList(20);
+ components = new List(20);
}
///
/// Returns an array containing the components of this field.
///
- public virtual IType[] Components
- {
- get
- {
- var ret = new IType[components.Count];
- for (var i = 0; i < ret.Length; i++)
- {
- ret[i] = (IType)components[i];
- }
-
- return ret;
- }
- }
+ public virtual IType[] Components => components.ToArray();
///
/// Returns the name of the type (used in XML encoding and profile checking).
@@ -68,7 +57,7 @@ public virtual IType this[int index]
components.Add(new Varies(Message));
}
- return (IType)components[index];
+ return components[index];
}
}
}
diff --git a/src/NHapi.Base/Model/Primitive/CommonTM.cs b/src/NHapi.Base/Model/Primitive/CommonTM.cs
index a5c92cd67..5436ba83a 100644
--- a/src/NHapi.Base/Model/Primitive/CommonTM.cs
+++ b/src/NHapi.Base/Model/Primitive/CommonTM.cs
@@ -151,15 +151,11 @@ public virtual string Value
{
// check to see if any of the following characters exist: "." or "+/-"
// this will help us determine the acceptable lengths
- var d = value.IndexOf(".");
- var sp = value.IndexOf("+");
- var sm = value.IndexOf("-");
+ var d = value.IndexOf(".", StringComparison.Ordinal);
+ var sp = value.IndexOf("+", StringComparison.Ordinal);
+ var sm = value.IndexOf("-", StringComparison.Ordinal);
var indexOfSign = -1;
- var offsetExists = false;
- if ((sp != -1) || (sm != -1))
- {
- offsetExists = true;
- }
+ var offsetExists = (sp != -1) || (sm != -1);
if (sp != -1)
{
@@ -189,7 +185,7 @@ public virtual string Value
{
// The length of the GMT offset must be 5 characters (including the sign)
var msg = "The length of the TM datatype value does not conform to an allowable" +
- " format. Format should conform to HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]";
+ " format. Format should conform to HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]";
var e = new DataTypeException(msg);
throw e;
}
@@ -201,7 +197,7 @@ public virtual string Value
if ((timeVal.Length < 8) || (timeVal.Length > 11))
{
var msg = "The length of the TM datatype value does not conform to an allowable" +
- " format. Format should conform to HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]";
+ " format. Format should conform to HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]";
var e = new DataTypeException(msg);
throw e;
}
@@ -214,7 +210,7 @@ public virtual string Value
if ((timeVal.Length != 2) && (timeVal.Length != 4) && (timeVal.Length != 6))
{
var msg = "The length of the TM datatype value does not conform to an allowable" +
- " format. Format should conform to HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]";
+ " format. Format should conform to HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]";
var e = new DataTypeException(msg);
throw e;
}
diff --git a/src/NHapi.Base/Model/Primitive/CommonTS.cs b/src/NHapi.Base/Model/Primitive/CommonTS.cs
index 80b2748a7..81d8bd846 100644
--- a/src/NHapi.Base/Model/Primitive/CommonTS.cs
+++ b/src/NHapi.Base/Model/Primitive/CommonTS.cs
@@ -206,8 +206,8 @@ public virtual string Value
string dateVal = null;
string timeVal = null;
string timeValLessOffset = null;
- var sp = value.IndexOf("+");
- var sm = value.IndexOf("-");
+ var sp = value.IndexOf("+", StringComparison.Ordinal);
+ var sm = value.IndexOf("-", StringComparison.Ordinal);
var indexOfSign = -1;
var offsetExists = false;
var timeValIsOffsetOnly = false;
@@ -310,7 +310,7 @@ public virtual string Value
tm = new CommonTM();
// first extract the + sign from the offset value string if it exists
- if (timeVal.IndexOf("+") == 0)
+ if (timeVal.IndexOf("+", StringComparison.Ordinal) == 0)
{
timeVal = timeVal.Substring(1);
} // end if
diff --git a/src/NHapi.Base/NHapi.Base.csproj b/src/NHapi.Base/NHapi.Base.csproj
index 9d34b349c..455c9c6c0 100644
--- a/src/NHapi.Base/NHapi.Base.csproj
+++ b/src/NHapi.Base/NHapi.Base.csproj
@@ -14,6 +14,7 @@
+
@@ -43,4 +44,4 @@
-
+
\ No newline at end of file
diff --git a/src/NHapi.Base/Parser/DefaultModelClassFactory.cs b/src/NHapi.Base/Parser/DefaultModelClassFactory.cs
index d9fca43f5..529621c1b 100644
--- a/src/NHapi.Base/Parser/DefaultModelClassFactory.cs
+++ b/src/NHapi.Base/Parser/DefaultModelClassFactory.cs
@@ -22,15 +22,10 @@ namespace NHapi.Base.Parser
public class DefaultModelClassFactory : IModelClassFactory
{
private static readonly object LockObject = new object();
- private static readonly IHapiLog Log;
+ private static readonly IHapiLog Log = HapiLogFactory.GetHapiLog(typeof(DefaultModelClassFactory));
private static Hashtable packages = null;
private static bool isLoadingPackages = false;
- static DefaultModelClassFactory()
- {
- Log = HapiLogFactory.GetHapiLog(typeof(DefaultModelClassFactory));
- }
-
///
/// Lists all the packages (user-definable) where classes for standard and custom
/// messages may be found. Each package has sub-packages called "message",
diff --git a/src/NHapi.Base/Parser/DefaultXMLParser.cs b/src/NHapi.Base/Parser/DefaultXMLParser.cs
index 7441fe731..02c2d4169 100644
--- a/src/NHapi.Base/Parser/DefaultXMLParser.cs
+++ b/src/NHapi.Base/Parser/DefaultXMLParser.cs
@@ -1,7 +1,7 @@
namespace NHapi.Base.Parser
{
using System;
- using System.Collections;
+ using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
@@ -29,12 +29,9 @@ namespace NHapi.Base.Parser
/// Bryan Tripp.
public class DefaultXMLParser : XMLParser
{
- private static readonly IHapiLog Log;
+ private static readonly IHapiLog Log = HapiLogFactory.GetHapiLog(typeof(DefaultXMLParser));
- static DefaultXMLParser()
- {
- Log = HapiLogFactory.GetHapiLog(typeof(DefaultXMLParser));
- }
+ private static readonly HashSet ForceGroupNames = new HashSet { "DIET" };
/// Creates a new instance of DefaultXMLParser.
public DefaultXMLParser()
@@ -63,30 +60,35 @@ public DefaultXMLParser(IModelClassFactory modelClassFactory)
var messageFile = new FileInfo(args[0]);
var fileLength = SupportClass.FileLength(messageFile);
var r = new StreamReader(messageFile.FullName, Encoding.Default);
- var cbuf = new char[(int)fileLength];
- Console.Out.WriteLine("Reading message file ... " + r.Read((char[])cbuf, 0, cbuf.Length) + " of " + fileLength +
- " chars");
+ var buffer = new char[fileLength];
+ Console.Out.WriteLine(
+ $"Reading message file ... {r.Read(buffer, 0, buffer.Length)} of {fileLength} chars");
r.Close();
- var messString = Convert.ToString(cbuf);
-
- ParserBase inParser = null;
- ParserBase outParser = null;
- var pp = new PipeParser();
- XMLParser xp = new DefaultXMLParser();
- Console.Out.WriteLine("Encoding: " + pp.GetEncoding(messString));
- if (pp.GetEncoding(messString) != null)
+ var messString = Convert.ToString(buffer);
+
+ ParserBase inParser;
+ ParserBase outParser;
+ var pipeParser = new PipeParser();
+ XMLParser xmlParser = new DefaultXMLParser();
+ Console.Out.WriteLine($"Encoding: {pipeParser.GetEncoding(messString)}");
+
+ if (EncodingDetector.IsEr7Encoded(messString))
+ {
+ inParser = pipeParser;
+ outParser = xmlParser;
+ }
+ else if (EncodingDetector.IsXmlEncoded(messString))
{
- inParser = pp;
- outParser = xp;
+ inParser = xmlParser;
+ outParser = pipeParser;
}
- else if (xp.GetEncoding(messString) != null)
+ else
{
- inParser = xp;
- outParser = pp;
+ throw new HL7Exception("Message encoding is not recognized");
}
var mess = inParser.Parse(messString);
- Console.Out.WriteLine("Got message of type " + mess.GetType().FullName);
+ Console.Out.WriteLine($"Got message of type {mess.GetType().FullName}");
var otherEncoding = outParser.Encode(mess);
Console.Out.WriteLine(otherEncoding);
@@ -97,71 +99,60 @@ public DefaultXMLParser(IModelClassFactory modelClassFactory)
}
}
- /// Creates an XML Document that corresponds to the given Message object.
- /// If you are implementing this method, you should create an XML Document, and insert XML Elements
- /// into it that correspond to the groups and segments that belong to the message type that your subclass
- /// of XMLParser supports. Then, for each segment in the message, call the method
- /// encode(Segment segmentObject, Element segmentElement) using the Element for
- /// that segment and the corresponding Segment object from the given Message.
- ///
- public override XmlDocument EncodeDocument(IMessage source)
+ ///
+ public override XmlDocument EncodeDocument(IMessage source, ParserOptions parserOptions)
{
var messageClassName = source.GetType().FullName;
- var messageName = messageClassName.Substring(messageClassName.LastIndexOf('.') + 1);
- XmlDocument doc = null;
+ var messageName = messageClassName!.Substring(messageClassName.LastIndexOf('.') + 1);
+
+ if (source is GenericMessage)
+ {
+ messageName = messageName.Replace("+", string.Empty);
+ }
+
+ XmlDocument doc;
try
{
doc = new XmlDocument();
- var root = doc.CreateElement(messageName);
+ var root = doc.CreateElement(messageName, NameSpace);
doc.AppendChild(root);
}
catch (Exception e)
{
throw new HL7Exception(
- "Can't create XML document - " + e.GetType().FullName,
+ $"Can't create XML document - {e.GetType().FullName}",
ErrorCode.APPLICATION_INTERNAL_ERROR,
e);
}
- Encode(source, (XmlElement)doc.DocumentElement);
+ Encode(source, doc.DocumentElement, parserOptions);
return doc;
}
- /// Creates and populates a Message object from an XML Document that contains an XML-encoded HL7 message.
- /// The easiest way to implement this method for a particular message structure is as follows:
- ///
- Create an instance of the Message type you are going to handle with your subclass
- /// of XMLParser
- /// - Go through the given Document and find the Elements that represent the top level of
- /// each message segment.
- /// - For each of these segments, call
parse(Segment segmentObject, Element segmentElement),
- /// providing the appropriate Segment from your Message object, and the corresponding Element.
- /// At the end of this process, your Message object should be populated with data from the XML
- /// Document.
- ///
- /// HL7Exception if the message is not correctly formatted.
- /// EncodingNotSupportedException if the message encoded.
- /// is not supported by this parser.
- ///
+ ///
public override IMessage ParseDocument(XmlDocument xmlMessage, string version, ParserOptions parserOptions)
{
- if (parserOptions is null)
+ if (xmlMessage is null)
{
- throw new ArgumentNullException(nameof(parserOptions));
+ throw new ArgumentNullException(nameof(xmlMessage));
}
- var messageName = xmlMessage.DocumentElement.Name;
+ parserOptions ??= DefaultParserOptions;
+
+ AssertNamespaceUri(xmlMessage.DocumentElement!.NamespaceURI);
+
+ var messageName = xmlMessage.DocumentElement!.LocalName;
var message = InstantiateMessage(messageName, version, true);
Parse(message, xmlMessage.DocumentElement, parserOptions);
return message;
}
+ // TODO: should this be public?
+ // https://github.com/nHapiNET/nHapi/issues/399
///
public override void Parse(IMessage message, string @string, ParserOptions parserOptions)
{
- if (parserOptions is null)
- {
- throw new ArgumentNullException(nameof(parserOptions));
- }
+ parserOptions ??= DefaultParserOptions;
try
{
@@ -186,9 +177,9 @@ public override void Parse(IMessage message, string @string, ParserOptions parse
///
protected internal static string MakeGroupElementName(string messageName, string className)
{
- string ret = null;
+ string ret;
- if (className.Length > 4)
+ if (className.Length > 4 || ForceGroupNames.Contains(className))
{
var elementName = new StringBuilder();
elementName.Append(messageName);
@@ -217,74 +208,92 @@ private void Parse(IGroup groupObject, XmlElement groupElement, ParserOptions pa
var messageName = groupObject.Message.GetStructureName();
var allChildNodes = groupElement.ChildNodes;
- var unparsedElementList = new ArrayList();
+ var unparsedElementList = new List();
for (var i = 0; i < allChildNodes.Count; i++)
{
- var node = allChildNodes.Item(i);
- var name = node.Name;
- if (Convert.ToInt16(node.NodeType) == (short)XmlNodeType.Element && !unparsedElementList.Contains(name))
+ var node = allChildNodes[i];
+ var name = node.LocalName;
+ if (node.NodeType == XmlNodeType.Element && !unparsedElementList.Contains(name))
{
+ AssertNamespaceUri(node.NamespaceURI);
unparsedElementList.Add(name);
}
}
// we're not too fussy about order here (all occurrences get parsed as repetitions) ...
- for (var i = 0; i < childNames.Length; i++)
+ foreach (var nextChildName in childNames)
{
- SupportClass.ICollectionSupport.Remove(unparsedElementList, childNames[i]);
- ParseReps(groupElement, groupObject, messageName, childNames[i], childNames[i], parserOptions);
+ var childName = nextChildName;
+ if (groupObject.IsGroup(nextChildName))
+ {
+ childName = MakeGroupElementName(groupObject.Message.GetStructureName(), nextChildName);
+ }
+
+ unparsedElementList.Remove(childName);
+
+ // 4 char segment names are second occurrences of a segment within a single message
+ // structure. e.g. the second PID segment in an A17 patient swap message is known
+ // to nhapi's code representation as PID2
+ if (nextChildName.Length == 4 && char.IsDigit(nextChildName[3]))
+ {
+ Log.Trace($"Skipping rep segment: {nextChildName}");
+ }
+ else
+ {
+ ParseReps(groupElement, groupObject, messageName, nextChildName, nextChildName, parserOptions);
+ }
}
- for (var i = 0; i < unparsedElementList.Count; i++)
+ foreach (var segmentName in unparsedElementList)
{
- var segName = (string)unparsedElementList[i];
- var segIndexName = groupObject.AddNonstandardSegment(segName);
- ParseReps(groupElement, groupObject, messageName, segName, segIndexName, parserOptions);
+ var segmentIndexName = groupObject.AddNonstandardSegment(segmentName);
+ ParseReps(groupElement, groupObject, messageName, segmentName, segmentIndexName, parserOptions);
}
}
- /// Copies data from a group object into the corresponding group element, creating any
- /// necessary child nodes.
+ ///
+ /// Copies data from a object into the corresponding ,
+ /// creating any necessary child nodes.
///
- private void Encode(IGroup groupObject, XmlElement groupElement)
+ /// The to encode.
+ /// The to encode into.
+ /// Contains configuration that will be applied when encoding.
+ /// If unable to encode .
+ private void Encode(IGroup groupObject, XmlElement groupElement, ParserOptions parserOptions)
{
var childNames = groupObject.Names;
var messageName = groupObject.Message.GetStructureName();
try
{
- for (var i = 0; i < childNames.Length; i++)
+ foreach (var name in childNames)
{
- var reps = groupObject.GetAll(childNames[i]);
- for (var j = 0; j < reps.Length; j++)
+ var reps = groupObject.GetAll(name);
+ foreach (var rep in reps)
{
+ var elementName = MakeGroupElementName(messageName, name);
var childElement =
- groupElement.OwnerDocument.CreateElement(MakeGroupElementName(messageName, childNames[i]));
- var hasValue = false;
+ groupElement.OwnerDocument!.CreateElement(elementName, NameSpace);
- if (reps[j] is IGroup)
- {
- hasValue = true;
- Encode((IGroup)reps[j], childElement);
- }
- else if (reps[j] is ISegment)
+ groupElement.AppendChild(childElement);
+
+ if (rep is IGroup group)
{
- hasValue = Encode((ISegment)reps[j], childElement);
+ Encode(group, childElement, parserOptions);
}
-
- if (hasValue)
+ else if (rep is ISegment segment)
{
- groupElement.AppendChild(childElement);
+ Encode(segment, childElement, parserOptions);
}
}
}
}
- catch (Exception e)
+ catch (Exception ex)
{
throw new HL7Exception(
"Can't encode group " + groupObject.GetType().FullName,
ErrorCode.APPLICATION_INTERNAL_ERROR,
- e);
+ ex);
}
}
@@ -297,29 +306,45 @@ private void ParseReps(
string childIndexName,
ParserOptions parserOptions)
{
- var reps = GetChildElementsByTagName(groupElement, MakeGroupElementName(messageName, childName));
- Log.Debug("# of elements matching " + MakeGroupElementName(messageName, childName) + ": " + reps.Count);
+ var groupElementName = MakeGroupElementName(messageName, childName);
+ var reps = GetChildElementsByTagName(groupElement, groupElementName);
+ Log.Debug($"# of elements matching {MakeGroupElementName(messageName, childName)}: {reps.Count}");
if (groupObject.IsRepeating(childIndexName))
{
for (var i = 0; i < reps.Count; i++)
{
- ParseRep((XmlElement)reps[i], groupObject.GetStructure(childIndexName, i), parserOptions);
+ ParseRep(reps[i], groupObject.GetStructure(childIndexName, i), parserOptions);
}
}
else
{
if (reps.Count > 0)
{
- ParseRep((XmlElement)reps[0], groupObject.GetStructure(childIndexName, 0), parserOptions);
+ ParseRep(reps[0], groupObject.GetStructure(childIndexName, 0), parserOptions);
}
if (reps.Count > 1)
{
- var newIndexName = groupObject.AddNonstandardSegment(childName);
- for (var i = 1; i < reps.Count; i++)
+ string newIndexName;
+ var i = 1;
+ try
{
- ParseRep((XmlElement)reps[i], groupObject.GetStructure(newIndexName, i - 1), parserOptions);
+ for (i = 1; i < reps.Count; i++)
+ {
+ newIndexName = childName + (i + 1);
+ var structure = groupObject.GetStructure(newIndexName);
+ ParseRep(reps[i], structure, parserOptions);
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Info("Issue Parsing", ex);
+ newIndexName = groupObject.AddNonstandardSegment(childName);
+ for (var j = i; j < reps.Count; j++)
+ {
+ ParseRep(reps[i], groupObject.GetStructure(newIndexName, j - i), parserOptions);
+ }
}
}
}
@@ -327,31 +352,34 @@ private void ParseReps(
private void ParseRep(XmlElement theElem, IStructure theObj, ParserOptions parserOptions)
{
- if (theObj is IGroup)
+ if (theObj is IGroup group)
{
- Parse((IGroup)theObj, theElem, parserOptions);
+ Parse(group, theElem, parserOptions);
}
- else if (theObj is ISegment)
+ else if (theObj is ISegment segment)
{
- Parse((ISegment)theObj, theElem, parserOptions);
+ Parse(segment, theElem, parserOptions);
}
Log.Debug("Parsed element: " + theElem.Name);
}
// includes direct children only
- private IList GetChildElementsByTagName(XmlElement theElement, string theName)
+ private IList GetChildElementsByTagName(XmlElement theElement, string theName)
{
- IList result = new ArrayList(10);
+ var result = new List(10);
var children = theElement.ChildNodes;
for (var i = 0; i < children.Count; i++)
{
- var child = children.Item(i);
- if (Convert.ToInt16(child.NodeType) == (short)XmlNodeType.Element && child.Name.Equals(theName))
+ var child = children[i];
+ if (child.NodeType != XmlNodeType.Element || !child.LocalName.Equals(theName, StringComparison.Ordinal))
{
- result.Add(child);
+ continue;
}
+
+ AssertNamespaceUri(child.NamespaceURI);
+ result.Add((XmlElement)child);
}
return result;
diff --git a/src/NHapi.Base/Parser/LegacyDefaultXMLParser.cs b/src/NHapi.Base/Parser/LegacyDefaultXMLParser.cs
new file mode 100644
index 000000000..4bc5d3120
--- /dev/null
+++ b/src/NHapi.Base/Parser/LegacyDefaultXMLParser.cs
@@ -0,0 +1,357 @@
+namespace NHapi.Base.Parser
+{
+ using System;
+ using System.Collections;
+ using System.IO;
+ using System.Text;
+ using System.Xml;
+
+ using NHapi.Base.Log;
+ using NHapi.Base.Model;
+
+ ///
+ /// A default XMLParser. This class assigns segment elements (in an XML-encoded message)
+ /// to Segment objects (in a Message object) using the name of a segment and the names
+ /// of any groups in which the segment is nested. The names of group classes must correspond
+ /// to the names of group elements (they must be identical except that a dot in the element
+ /// name, following the message name, is replaced with an underscore, in order to constitute a
+ /// valid class name).
+ ///
+ ///
+ /// At the time of writing, the group names in the XML spec are changing. Many of the group
+ /// names have been automatically generated based on the group contents. However, these automatic
+ /// names are gradually being replaced with manually assigned names. This process is expected to
+ /// be complete by November 2002. As a result, mismatches are likely. Messages could be
+ /// transformed prior to parsing (using XSLT) as a work-around. Alternatively the group class names
+ /// could be changed to reflect updates in the XML spec. Ultimately, HAPI group classes will be
+ /// changed to correspond with the official group names, once these are all assigned.
+ ///
+ /// Bryan Tripp.
+ [Obsolete("Use DefaultXMLParser instead.")]
+ public class LegacyDefaultXMLParser : LegacyXMLParser
+ {
+ private static readonly IHapiLog Log = HapiLogFactory.GetHapiLog(typeof(LegacyDefaultXMLParser));
+
+ /// Creates a new instance of DefaultXMLParser.
+ public LegacyDefaultXMLParser()
+ {
+ }
+
+ /// Creates a new instance of DefaultXMLParser.
+ public LegacyDefaultXMLParser(IModelClassFactory modelClassFactory)
+ : base(modelClassFactory)
+ {
+ }
+
+ /// Test harness.
+ [STAThread]
+ public static void Main(string[] args)
+ {
+ if (args.Length != 1)
+ {
+ Console.Out.WriteLine("Usage: DefaultXMLParser pipe_encoded_file");
+ Environment.Exit(1);
+ }
+
+ // read and parse message from file
+ try
+ {
+ var messageFile = new FileInfo(args[0]);
+ var fileLength = SupportClass.FileLength(messageFile);
+ var r = new StreamReader(messageFile.FullName, Encoding.Default);
+ var buffer = new char[(int)fileLength];
+ Console.Out.WriteLine(
+ $"Reading message file ... {r.Read(buffer, 0, buffer.Length)} of {fileLength} chars");
+ r.Close();
+ var messString = Convert.ToString(buffer);
+
+ ParserBase inParser;
+ ParserBase outParser;
+ var pp = new PipeParser();
+ XMLParser xp = new DefaultXMLParser();
+ Console.Out.WriteLine("Encoding: " + pp.GetEncoding(messString));
+ if (pp.GetEncoding(messString) != null)
+ {
+ inParser = pp;
+ outParser = xp;
+ }
+ else if (xp.GetEncoding(messString) != null)
+ {
+ inParser = xp;
+ outParser = pp;
+ }
+ else
+ {
+ throw new HL7Exception("Message encoding is not recognized");
+ }
+
+ var mess = inParser.Parse(messString);
+ Console.Out.WriteLine("Got message of type " + mess.GetType().FullName);
+
+ var otherEncoding = outParser.Encode(mess);
+ Console.Out.WriteLine(otherEncoding);
+ }
+ catch (Exception e)
+ {
+ SupportClass.WriteStackTrace(e, Console.Error);
+ }
+ }
+
+ /// Creates an XML Document that corresponds to the given Message object.
+ /// If you are implementing this method, you should create an XML Document, and insert XML Elements
+ /// into it that correspond to the groups and segments that belong to the message type that your subclass
+ /// of XMLParser supports. Then, for each segment in the message, call the method
+ /// encode(Segment segmentObject, Element segmentElement) using the Element for
+ /// that segment and the corresponding Segment object from the given Message.
+ ///
+ public override XmlDocument EncodeDocument(IMessage source, ParserOptions parserOptions)
+ {
+ var messageClassName = source.GetType().FullName;
+ var messageName = messageClassName!.Substring(messageClassName.LastIndexOf('.') + 1);
+ XmlDocument doc;
+ try
+ {
+ doc = new XmlDocument();
+ var root = doc.CreateElement(messageName);
+ doc.AppendChild(root);
+ }
+ catch (Exception e)
+ {
+ throw new HL7Exception(
+ "Can't create XML document - " + e.GetType().FullName,
+ ErrorCode.APPLICATION_INTERNAL_ERROR,
+ e);
+ }
+
+ Encode(source, doc.DocumentElement, parserOptions);
+ return doc;
+ }
+
+ /// Creates and populates a Message object from an XML Document that contains an XML-encoded HL7 message.
+ /// The easiest way to implement this method for a particular message structure is as follows:
+ ///
- Create an instance of the Message type you are going to handle with your subclass
+ /// of XMLParser
+ /// - Go through the given Document and find the Elements that represent the top level of
+ /// each message segment.
+ /// - For each of these segments, call
parse(Segment segmentObject, Element segmentElement),
+ /// providing the appropriate Segment from your Message object, and the corresponding Element.
+ /// At the end of this process, your Message object should be populated with data from the XML
+ /// Document.
+ ///
+ /// HL7Exception if the message is not correctly formatted.
+ /// EncodingNotSupportedException if the message encoded.
+ /// is not supported by this parser.
+ ///
+ public override IMessage ParseDocument(XmlDocument xmlMessage, string version, ParserOptions parserOptions)
+ {
+ if (parserOptions is null)
+ {
+ throw new ArgumentNullException(nameof(parserOptions));
+ }
+
+ var messageName = xmlMessage.DocumentElement.Name;
+ var message = InstantiateMessage(messageName, version, true);
+ Parse(message, xmlMessage.DocumentElement, parserOptions);
+ return message;
+ }
+
+ ///
+ public override void Parse(IMessage message, string @string, ParserOptions parserOptions)
+ {
+ parserOptions ??= DefaultParserOptions;
+
+ try
+ {
+ var xmlDocument = new XmlDocument();
+ xmlDocument.Load(new StringReader(@string));
+
+ Parse(message, xmlDocument.DocumentElement, parserOptions);
+ }
+ catch (XmlException e)
+ {
+ throw new HL7Exception("XmlException parsing XML", ErrorCode.APPLICATION_INTERNAL_ERROR, e);
+ }
+ }
+
+ ///
+ /// Given the name of a message and a Group class, returns the corresponding group element name in an
+ /// XML-encoded message. This is the message name and group name separated by a dot. For example,
+ /// ADT_A01.INSURANCE.
+ ///
+ /// If it looks like a segment name (ie: has 3 characters), no change is made.
+ ///
+ ///
+ protected internal static string MakeGroupElementName(string messageName, string className)
+ {
+ string ret;
+
+ if (className.Length > 4)
+ {
+ var elementName = new StringBuilder();
+ elementName.Append(messageName);
+ elementName.Append('.');
+ elementName.Append(className);
+ ret = elementName.ToString();
+ }
+ else if (className.Length == 4)
+ {
+ ret = className.Substring(0, 3 - 0);
+ }
+ else
+ {
+ ret = className;
+ }
+
+ return ret;
+ }
+
+ /// Populates the given group object with data from the given group element, ignoring
+ /// any unrecognized nodes.
+ ///
+ private void Parse(IGroup groupObject, XmlElement groupElement, ParserOptions parserOptions)
+ {
+ var childNames = groupObject.Names;
+ var messageName = groupObject.Message.GetStructureName();
+
+ var allChildNodes = groupElement.ChildNodes;
+ var unparsedElementList = new ArrayList();
+ for (var i = 0; i < allChildNodes.Count; i++)
+ {
+ var node = allChildNodes.Item(i);
+ var name = node.Name;
+ if (Convert.ToInt16(node.NodeType) == (short)XmlNodeType.Element && !unparsedElementList.Contains(name))
+ {
+ unparsedElementList.Add(name);
+ }
+ }
+
+ // we're not too fussy about order here (all occurrences get parsed as repetitions) ...
+ for (var i = 0; i < childNames.Length; i++)
+ {
+ SupportClass.ICollectionSupport.Remove(unparsedElementList, childNames[i]);
+ ParseReps(groupElement, groupObject, messageName, childNames[i], childNames[i], parserOptions);
+ }
+
+ for (var i = 0; i < unparsedElementList.Count; i++)
+ {
+ var segName = (string)unparsedElementList[i];
+ var segIndexName = groupObject.AddNonstandardSegment(segName);
+ ParseReps(groupElement, groupObject, messageName, segName, segIndexName, parserOptions);
+ }
+ }
+
+ /// Copies data from a group object into the corresponding group element, creating any
+ /// necessary child nodes.
+ ///
+ private void Encode(IGroup groupObject, XmlElement groupElement, ParserOptions parserOptions)
+ {
+ var childNames = groupObject.Names;
+ var messageName = groupObject.Message.GetStructureName();
+
+ try
+ {
+ for (var i = 0; i < childNames.Length; i++)
+ {
+ var reps = groupObject.GetAll(childNames[i]);
+ for (var j = 0; j < reps.Length; j++)
+ {
+ var childElement =
+ groupElement.OwnerDocument.CreateElement(MakeGroupElementName(messageName, childNames[i]));
+ var hasValue = false;
+
+ if (reps[j] is IGroup)
+ {
+ hasValue = true;
+ Encode((IGroup)reps[j], childElement, parserOptions);
+ }
+ else if (reps[j] is ISegment)
+ {
+ hasValue = Encode((ISegment)reps[j], childElement, parserOptions);
+ }
+
+ if (hasValue)
+ {
+ groupElement.AppendChild(childElement);
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ throw new HL7Exception(
+ "Can't encode group " + groupObject.GetType().FullName,
+ ErrorCode.APPLICATION_INTERNAL_ERROR,
+ e);
+ }
+ }
+
+ // param childIndexName may have an integer on the end if >1 sibling with same name (e.g. NTE2)
+ private void ParseReps(
+ XmlElement groupElement,
+ IGroup groupObject,
+ string messageName,
+ string childName,
+ string childIndexName,
+ ParserOptions parserOptions)
+ {
+ var reps = GetChildElementsByTagName(groupElement, MakeGroupElementName(messageName, childName));
+ Log.Debug("# of elements matching " + MakeGroupElementName(messageName, childName) + ": " + reps.Count);
+
+ if (groupObject.IsRepeating(childIndexName))
+ {
+ for (var i = 0; i < reps.Count; i++)
+ {
+ ParseRep((XmlElement)reps[i], groupObject.GetStructure(childIndexName, i), parserOptions);
+ }
+ }
+ else
+ {
+ if (reps.Count > 0)
+ {
+ ParseRep((XmlElement)reps[0], groupObject.GetStructure(childIndexName, 0), parserOptions);
+ }
+
+ if (reps.Count > 1)
+ {
+ var newIndexName = groupObject.AddNonstandardSegment(childName);
+ for (var i = 1; i < reps.Count; i++)
+ {
+ ParseRep((XmlElement)reps[i], groupObject.GetStructure(newIndexName, i - 1), parserOptions);
+ }
+ }
+ }
+ }
+
+ private void ParseRep(XmlElement theElem, IStructure theObj, ParserOptions parserOptions)
+ {
+ if (theObj is IGroup obj)
+ {
+ Parse(obj, theElem, parserOptions);
+ }
+ else if (theObj is ISegment segment)
+ {
+ Parse(segment, theElem, parserOptions);
+ }
+
+ Log.Debug("Parsed element: " + theElem.Name);
+ }
+
+ // includes direct children only
+ private IList GetChildElementsByTagName(XmlElement theElement, string theName)
+ {
+ IList result = new ArrayList(10);
+ var children = theElement.ChildNodes;
+
+ for (var i = 0; i < children.Count; i++)
+ {
+ var child = children.Item(i);
+ if (Convert.ToInt16(child.NodeType) == (short)XmlNodeType.Element && child.Name.Equals(theName))
+ {
+ result.Add(child);
+ }
+ }
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NHapi.Base/Parser/LegacyPipeParser.cs b/src/NHapi.Base/Parser/LegacyPipeParser.cs
index e84820528..3e8b2f198 100644
--- a/src/NHapi.Base/Parser/LegacyPipeParser.cs
+++ b/src/NHapi.Base/Parser/LegacyPipeParser.cs
@@ -50,12 +50,7 @@ public class LegacyPipeParser : ParserBase
{
private const string SegDelim = "\r"; // see section 2.8 of spec
- private static readonly IHapiLog Log;
-
- static LegacyPipeParser()
- {
- Log = HapiLogFactory.GetHapiLog(typeof(LegacyPipeParser));
- }
+ private static readonly IHapiLog Log = HapiLogFactory.GetHapiLog(typeof(LegacyPipeParser));
/// Creates a new LegacyPipeParser.
public LegacyPipeParser()
@@ -257,12 +252,11 @@ public static string StripLeadingWhitespace(string in_Renamed)
return out_Renamed.ToString();
}
+ // TODO: should this be public?
+ // https://github.com/nHapiNET/nHapi/issues/399
public override void Parse(IMessage message, string @string, ParserOptions parserOptions)
{
- if (parserOptions is null)
- {
- throw new ArgumentNullException(nameof(parserOptions));
- }
+ parserOptions ??= DefaultParserOptions;
var messageIter = new Util.MessageIterator(message, "MSH", true);
FilterIterator.IPredicate segmentsOnly = new IsSegmentPredicate(this);
@@ -361,7 +355,7 @@ public override string GetEncoding(string message)
var nextFieldDelimLoc = 0;
for (var i = 0; i < 11; i++)
{
- nextFieldDelimLoc = message.IndexOf((char)fourthChar, nextFieldDelimLoc + 1);
+ nextFieldDelimLoc = message.IndexOf(fourthChar, nextFieldDelimLoc + 1);
if (nextFieldDelimLoc < 0)
{
return null;
@@ -378,8 +372,7 @@ public override string GetEncoding(string message)
/// this method should not be public.
///
- ///
- ///
+ ///
///
///
/// HL7Exception.
@@ -389,28 +382,32 @@ public virtual string GetMessageStructure(string message)
return GetStructure(message).Structure;
}
+ // TODO: should this be public?
+ // https://github.com/nHapiNET/nHapi/issues/399
///
/// Parses a segment string and populates the given Segment object. Unexpected fields are
/// added as Varies' at the end of the segment.
///
- /// HL7Exception if the given string does not contain the.
- /// given segment or if the string is not encoded properly.
- ///
+ ///
+ /// If the given string does not contain the given segment or if the string is not encoded properly.
+ ///
public virtual void Parse(ISegment destination, string segment, EncodingCharacters encodingChars)
{
Parse(destination, segment, encodingChars, DefaultParserOptions);
}
+ // TODO: should this be public?
+ // https://github.com/nHapiNET/nHapi/issues/399
///
/// Parses a segment string and populates the given Segment object. Unexpected fields are
/// added as Varies' at the end of the segment.
///
- /// HL7Exception if the given string does not contain the.
- /// given segment or if the string is not encoded properly.
- ///
+ ///
+ /// If the given string does not contain the given segment or if the string is not encoded properly.
+ ///
public virtual void Parse(ISegment destination, string segment, EncodingCharacters encodingChars, ParserOptions parserOptions)
{
- parserOptions = parserOptions ?? DefaultParserOptions;
+ parserOptions ??= DefaultParserOptions;
var fieldOffset = 0;
if (IsDelimDefSegment(destination.GetStructureName()))
@@ -468,7 +465,7 @@ public virtual void Parse(ISegment destination, string segment, EncodingCharacte
}
// set data type of OBX-5
- if (destination.GetType().FullName.IndexOf("OBX") >= 0)
+ if (destination.GetType().FullName.IndexOf("OBX", StringComparison.Ordinal) >= 0)
{
Varies.FixOBX5(destination, Factory, parserOptions);
}
@@ -493,7 +490,7 @@ public virtual void Parse(ISegment destination, string segment, EncodingCharacte
public override ISegment GetCriticalResponseData(string message)
{
// try to get MSH segment
- var locStartMSH = message.IndexOf("MSH");
+ var locStartMSH = message.IndexOf("MSH", StringComparison.Ordinal);
if (locStartMSH < 0)
{
throw new HL7Exception("Couldn't find MSH segment in message: " + message, ErrorCode.SEGMENT_SEQUENCE_ERROR);
@@ -561,14 +558,14 @@ public override ISegment GetCriticalResponseData(string message)
public override string GetAckID(string message)
{
string ackID = null;
- var startMSA = message.IndexOf("\rMSA");
+ var startMSA = message.IndexOf("\rMSA", StringComparison.Ordinal);
if (startMSA >= 0)
{
var startFieldOne = startMSA + 5;
var fieldDelim = message[startFieldOne - 1];
- var start = message.IndexOf((char)fieldDelim, startFieldOne) + 1;
- var end = message.IndexOf((char)fieldDelim, start);
- var segEnd = message.IndexOf(Convert.ToString(SegDelim), start);
+ var start = message.IndexOf(fieldDelim, startFieldOne) + 1;
+ var end = message.IndexOf(fieldDelim, start);
+ var segEnd = message.IndexOf(Convert.ToString(SegDelim), start, StringComparison.Ordinal);
if (segEnd > start && segEnd < end)
{
end = segEnd;
@@ -597,20 +594,16 @@ public override string GetAckID(string message)
return ackID;
}
- /// Returns the version ID (MSH-12) from the given message, without fully parsing the message.
- /// The version is needed prior to parsing in order to determine the message class
- /// into which the text of the message should be parsed.
- ///
- /// HL7Exception if the version field can not be found.
- public override string GetVersion(string message)
+ ///
+ public override string GetVersion(string message, ParserOptions parserOptions)
{
- var startMSH = message.IndexOf("MSH");
+ var startMSH = message.IndexOf("MSH", StringComparison.Ordinal);
if (startMSH < 0)
{
throw new HL7Exception("No MSH header segment found.", ErrorCode.REQUIRED_FIELD_MISSING);
}
- var endMSH = message.IndexOf(SegDelim, startMSH);
+ var endMSH = message.IndexOf(SegDelim, startMSH, StringComparison.Ordinal);
if (endMSH < 0)
{
endMSH = message.Length;
@@ -672,14 +665,14 @@ public override string GetVersion(string message)
/// EncodingNotSupportedException if the requested encoding is not.
/// supported by this parser.
///
- protected internal override string DoEncode(IMessage source, string encoding)
+ protected internal override string DoEncode(IMessage source, string encoding, ParserOptions parserOptions)
{
if (!SupportsEncoding(encoding))
{
throw new EncodingNotSupportedException("This parser does not support the " + encoding + " encoding");
}
- return Encode(source);
+ return Encode(source, parserOptions);
}
/// Formats a Message object into an HL7 message string using this parser's
@@ -688,7 +681,7 @@ protected internal override string DoEncode(IMessage source, string encoding)
/// HL7Exception if the data fields in the message do not permit encoding.
/// (e.g. required fields are null).
///
- protected internal override string DoEncode(IMessage source)
+ protected internal override string DoEncode(IMessage source, ParserOptions parserOptions)
{
// get encoding characters ...
var msh = (ISegment)source.GetStructure("MSH");
@@ -729,7 +722,7 @@ protected internal override string DoEncode(IMessage source)
{
// Create the MsgType and Trigger Event if not there
var messageTypeFullname = source.GetStructureName();
- var i = messageTypeFullname.IndexOf("_");
+ var i = messageTypeFullname.IndexOf("_", StringComparison.Ordinal);
if (i > 0)
{
var type = messageTypeFullname.Substring(0, i);
@@ -753,7 +746,7 @@ protected internal override string DoEncode(IMessage source)
var en = new EncodingCharacters(fieldSep, encCharString);
// pass down to group encoding method which will operate recursively on children ...
- return Encode((IGroup)source, en);
+ return Encode(source, en);
}
/// Parses a message string and returns the corresponding Message
@@ -785,7 +778,7 @@ private static EncodingCharacters GetEncodingChars(string message)
private static string EncodePrimitive(IPrimitive p, EncodingCharacters encodingChars)
{
- var val = ((IPrimitive)p).Value;
+ var val = p.Value;
if (val == null)
{
val = string.Empty;
@@ -834,11 +827,7 @@ private static string StripExtraDelimiters(string in_Renamed, char delim)
///
private static bool IsDelimDefSegment(string theSegmentName)
{
- var is_Renamed = false;
- if (theSegmentName.Equals("MSH") || theSegmentName.Equals("FHS") || theSegmentName.Equals("BHS"))
- {
- is_Renamed = true;
- }
+ bool is_Renamed = theSegmentName.Equals("MSH") || theSegmentName.Equals("FHS") || theSegmentName.Equals("BHS");
return is_Renamed;
}
@@ -880,7 +869,7 @@ private MessageStructure GetStructure(string message)
try
{
var fields = Split(
- message.Substring(0, Math.Max(message.IndexOf(SegDelim), message.Length) - 0),
+ message.Substring(0, Math.Max(message.IndexOf(SegDelim, StringComparison.Ordinal), message.Length) - 0),
Convert.ToString(ec.FieldSeparator));
wholeFieldNine = fields[8];
diff --git a/src/NHapi.Base/Parser/LegacyXMLParser.cs b/src/NHapi.Base/Parser/LegacyXMLParser.cs
new file mode 100644
index 000000000..f98fa5472
--- /dev/null
+++ b/src/NHapi.Base/Parser/LegacyXMLParser.cs
@@ -0,0 +1,763 @@
+/*
+ The contents of this file are subject to the Mozilla Public License Version 1.1
+ (the "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at http://www.mozilla.org/MPL/
+ Software distributed under the License is distributed on an "AS IS" basis,
+ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
+ specific language governing rights and limitations under the License.
+ The Original Code is "XMLParser.java". Description:
+ "Parses and encodes HL7 messages in XML form, according to HL7's normative XML encoding
+ specification."
+ The Initial Developer of the Original Code is University Health Network. Copyright (C)
+ 2002. All Rights Reserved.
+ Contributor(s): ______________________________________.
+ Alternatively, the contents of this file may be used under the terms of the
+ GNU General Public License (the "GPL"), in which case the provisions of the GPL are
+ applicable instead of those above. If you wish to allow use of your version of this
+ file only under the terms of the GPL and not to allow others to use your version
+ of this file under the MPL, indicate your decision by deleting the provisions above
+ and replace them with the notice and other provisions required by the GPL License.
+ If you do not delete the provisions above, a recipient may use your version of
+ this file under either the MPL or the GPL.
+*/
+
+namespace NHapi.Base.Parser
+{
+ using System;
+ using System.IO;
+ using System.Text;
+ using System.Xml;
+
+ using NHapi.Base.Log;
+ using NHapi.Base.Model;
+ using NHapi.Base.Util;
+
+ ///
+ /// Parses and encodes HL7 messages in XML form, according to HL7's normative XML encoding
+ /// specification. This is an abstract class that handles datatype and segment parsing/encoding,
+ /// but not the parsing/encoding of entire messages. To use the XML parser, you should create a
+ /// subclass for a certain message structure. This subclass must be able to identify the Segment
+ /// objects that correspond to various Segment nodes in an XML document, and call the methods.
+ /// parse(Segment segment, ElementNode segmentNode) and. encode(Segment segment, ElementNode segmentNode)
+ /// as appropriate. XMLParser uses the Xerces parser, which must be installed in your class path.
+ ///
+ /// Bryan Tripp, Shawn Bellina.
+ [Obsolete("Use XMLParser instead.")]
+ public abstract class LegacyXMLParser : ParserBase
+ {
+ private static readonly IHapiLog Log = HapiLogFactory.GetHapiLog(typeof(LegacyXMLParser));
+
+ ///
+ /// The nodes whose names match these strings will be kept as original,
+ /// meaning that no white space trimming will occur on them.
+ ///
+ private string[] keepAsOriginalNodes;
+
+ /// All keepAsOriginalNodes names, concatenated by a pipe (|).
+ private string concatKeepAsOriginalNodes = string.Empty;
+
+ protected LegacyXMLParser()
+ {
+ }
+
+ protected LegacyXMLParser(IModelClassFactory factory)
+ : base(factory)
+ {
+ }
+
+ ///
+ /// Gets the preferred encoding of this Parser.
+ ///
+ public override string DefaultEncoding => "XML";
+
+ ///
+ /// Sets the keepAsOriginalNodes.
+ ///
+ /// The nodes whose names match the keepAsOriginalNodes will be kept as original,
+ /// meaning that no white space trimming will occur on them.
+ ///
+ ///
+ public virtual string[] KeepAsOriginalNodes
+ {
+ get
+ {
+ return keepAsOriginalNodes;
+ }
+
+ set
+ {
+ keepAsOriginalNodes = value;
+
+ if (value.Length != 0)
+ {
+ // initializes the
+ var strBuf = new StringBuilder(value[0]);
+ for (var i = 1; i < value.Length; i++)
+ {
+ strBuf.Append("|");
+ strBuf.Append(value[i]);
+ }
+
+ concatKeepAsOriginalNodes = strBuf.ToString();
+ }
+ else
+ {
+ concatKeepAsOriginalNodes = string.Empty;
+ }
+ }
+ }
+
+ ///
+ /// Returns a String representing the encoding of the given message, if
+ /// the encoding is recognized. For example if the given message appears
+ /// to be encoded using HL7 2.x XML rules then "XML" would be returned.
+ /// If the encoding is not recognized then null is returned. That this
+ /// method returns a specific encoding does not guarantee that the
+ /// message is correctly encoded (e.g. well formed XML) - just that
+ /// it is not encoded using any other encoding than the one returned.
+ /// Returns null if the encoding is not recognized.
+ ///
+ public override string GetEncoding(string message)
+ {
+ string encoding = null;
+
+ // check for a number of expected strings
+ var expected = new[] { "" };
+ var isXML = true;
+ for (var i = 0; i < expected.Length; i++)
+ {
+ if (message.IndexOf(expected[i], StringComparison.Ordinal) < 0)
+ {
+ isXML = false;
+ }
+ }
+
+ if (isXML)
+ {
+ encoding = "XML";
+ }
+
+ return encoding;
+ }
+
+ /// Creates and populates a Message object from an XML Document that contains an XML-encoded HL7 message.
+ /// The easiest way to implement this method for a particular message structure is as follows:
+ ///
- Create an instance of the Message type you are going to handle with your subclass
+ /// of XMLParser
+ /// - Go through the given Document and find the Elements that represent the top level of
+ /// each message segment.
+ /// - For each of these segments, call
parse(Segment segmentObject, Element segmentElement),
+ /// providing the appropriate Segment from your Message object, and the corresponding Element.
+ /// At the end of this process, your Message object should be populated with data from the XML
+ /// Document.
+ ///
+ /// HL7Exception if the message is not correctly formatted.
+ /// EncodingNotSupportedException if the message encoded.
+ /// is not supported by this parser.
+ ///
+ public IMessage ParseDocument(XmlDocument xmlMessage, string version)
+ {
+ return ParseDocument(xmlMessage, version, DefaultParserOptions);
+ }
+
+ /// Creates and populates a Message object from an XML Document that contains an XML-encoded HL7 message.
+ /// The easiest way to implement this method for a particular message structure is as follows:
+ ///
- Create an instance of the Message type you are going to handle with your subclass
+ /// of XMLParser
+ /// - Go through the given Document and find the Elements that represent the top level of
+ /// each message segment.
+ /// - For each of these segments, call
parse(Segment segmentObject, Element segmentElement),
+ /// providing the appropriate Segment from your Message object, and the corresponding Element.
+ /// At the end of this process, your Message object should be populated with data from the XML
+ /// Document.
+ ///
+ /// HL7Exception if the message is not correctly formatted.
+ /// EncodingNotSupportedException if the message encoded.
+ /// is not supported by this parser.
+ ///
+ public abstract IMessage ParseDocument(XmlDocument xmlMessage, string version, ParserOptions parserOptions);
+
+ /// Creates an XML Document that corresponds to the given Message object.
+ /// If you are implementing this method, you should create an XML Document, and insert XML Elements
+ /// into it that correspond to the groups and segments that belong to the message type that your subclass
+ /// of XMLParser supports. Then, for each segment in the message, call the method
+ /// encode(Segment segmentObject, Element segmentElement) using the Element for
+ /// that segment and the corresponding Segment object from the given Message.
+ ///
+ public abstract XmlDocument EncodeDocument(IMessage source, ParserOptions parserOptions);
+
+ public XmlDocument EncodeDocument(IMessage source)
+ {
+ return EncodeDocument(source, DefaultParserOptions);
+ }
+
+ /// Populates the given Segment object with data from the given XML Element.
+ /// HL7Exception if the XML Element does not have the correct name and structure.
+ /// for the given Segment, or if there is an error while setting individual field values.
+ ///
+ public virtual void Parse(ISegment segmentObject, XmlElement segmentElement)
+ {
+ Parse(segmentObject, segmentElement, DefaultParserOptions);
+ }
+
+ /// Populates the given Segment object with data from the given XML Element.
+ /// HL7Exception if the XML Element does not have the correct name and structure.
+ /// for the given Segment, or if there is an error while setting individual field values.
+ ///
+ public virtual void Parse(ISegment segmentObject, XmlElement segmentElement, ParserOptions parserOptions)
+ {
+ parserOptions ??= DefaultParserOptions;
+
+ var done = new SupportClass.HashSetSupport();
+ var all = segmentElement.ChildNodes;
+ for (var i = 0; i < all.Count; i++)
+ {
+ var elementName = all.Item(i).Name;
+ if (Convert.ToInt16(all.Item(i).NodeType) == (short)XmlNodeType.Element && !done.Contains(elementName))
+ {
+ done.Add(elementName);
+
+ var index = elementName.IndexOf('.');
+ if (index >= 0 && elementName.Length > index)
+ {
+ // properly formatted element
+ var fieldNumString = elementName.Substring(index + 1);
+ var fieldNum = int.Parse(fieldNumString);
+ ParseReps(segmentObject, segmentElement, elementName, fieldNum);
+ }
+ else
+ {
+ Log.Debug("Child of segment " + segmentObject.GetStructureName() + " doesn't look like a field: " + elementName);
+ }
+ }
+ }
+
+ // set data type of OBX-5
+ if (segmentObject.GetType().FullName.IndexOf("OBX", StringComparison.Ordinal) >= 0)
+ {
+ Varies.FixOBX5(segmentObject, Factory, parserOptions);
+ }
+ }
+
+ /// Populates the given Element with data from the given Segment, by inserting
+ /// Elements corresponding to the Segment's fields, their components, etc. Returns
+ /// true if there is at least one data value in the segment.
+ ///
+ public virtual bool Encode(ISegment segmentObject, XmlElement segmentElement)
+ {
+ return Encode(segmentObject, segmentElement, DefaultParserOptions);
+ }
+
+ /// Populates the given Element with data from the given Segment, by inserting
+ /// Elements corresponding to the Segment's fields, their components, etc. Returns
+ /// true if there is at least one data value in the segment.
+ ///
+ public virtual bool Encode(ISegment segmentObject, XmlElement segmentElement, ParserOptions parserOptions)
+ {
+ var hasValue = false;
+ var n = segmentObject.NumFields();
+ for (var i = 1; i <= n; i++)
+ {
+ var name = MakeElementName(segmentObject, i);
+ var reps = segmentObject.GetField(i);
+ for (var j = 0; j < reps.Length; j++)
+ {
+ var newNode = segmentElement.OwnerDocument.CreateElement(name);
+
+ var componentHasValue = Encode(reps[j], newNode, parserOptions);
+ if (componentHasValue)
+ {
+ try
+ {
+ segmentElement.AppendChild(newNode);
+ }
+ catch (Exception e)
+ {
+ throw new HL7Exception("DOMException encoding Segment: ", ErrorCode.APPLICATION_INTERNAL_ERROR, e);
+ }
+
+ hasValue = true;
+ }
+ }
+ }
+
+ return hasValue;
+ }
+
+ /// Populates the given Type object with data from the given XML Element.
+ public virtual void Parse(IType datatypeObject, XmlElement datatypeElement)
+ {
+ // TODO: consider replacing with a switch statement
+ if (datatypeObject is Varies)
+ {
+ ParseVaries((Varies)datatypeObject, datatypeElement);
+ }
+ else if (datatypeObject is IPrimitive)
+ {
+ ParsePrimitive((IPrimitive)datatypeObject, datatypeElement);
+ }
+ else if (datatypeObject is IComposite)
+ {
+ ParseComposite((IComposite)datatypeObject, datatypeElement);
+ }
+ }
+
+ /// Returns a minimal amount of data from a message string, including only the
+ /// data needed to send a response to the remote system. This includes the
+ /// following fields:
+ ///
+ /// - field separator
+ /// - encoding characters
+ /// - processing ID
+ /// - message control ID
+ ///
+ /// This method is intended for use when there is an error parsing a message,
+ /// (so the Message object is unavailable) but an error message must be sent
+ /// back to the remote system including some of the information in the inbound
+ /// message. This method parses only that required information, hopefully
+ /// avoiding the condition that caused the original error.
+ ///
+ public override ISegment GetCriticalResponseData(string message)
+ {
+ var version = GetVersion(message);
+ var criticalData = MakeControlMSH(version, Factory);
+
+ Terser.Set(criticalData, 1, 0, 1, 1, ParseLeaf(message, "MSH.1", 0));
+ Terser.Set(criticalData, 2, 0, 1, 1, ParseLeaf(message, "MSH.2", 0));
+ Terser.Set(criticalData, 10, 0, 1, 1, ParseLeaf(message, "MSH.10", 0));
+ var procID = ParseLeaf(message, "MSH.11", 0);
+ if (procID == null || procID.Length == 0)
+ {
+ procID = ParseLeaf(message, "PT.1", message.IndexOf("MSH.11", StringComparison.Ordinal));
+
+ // this field is a composite in later versions
+ }
+
+ Terser.Set(criticalData, 11, 0, 1, 1, procID);
+
+ return criticalData;
+ }
+
+ ///
+ /// For response messages, returns the value of MSA-2 (the message ID of the message
+ /// sent by the sending system). This value may be needed prior to main message parsing,
+ /// so that (particularly in a multi-threaded scenario) the message can be routed to
+ /// the thread that sent the request. We need this information first so that any
+ /// parse exceptions are thrown to the correct thread. Implementers of Parsers should
+ /// take care to make the implementation of this method very fast and robust.
+ /// Returns null if MSA-2 can not be found (e.g. if the message is not a
+ /// response message). Trims whitespace from around the MSA-2 field.
+ ///
+ public override string GetAckID(string message)
+ {
+ try
+ {
+ return ParseLeaf(message, "msa.2", 0).Trim();
+ }
+ catch (HL7Exception)
+ {
+ // OK ... assume it isn't a response message
+ return null;
+ }
+ }
+
+ ///
+ public override string GetVersion(string message, ParserOptions parserOptions)
+ {
+ var version = ParseLeaf(message, "MSH.12", 0);
+ if (version == null || version.Trim().Length == 0)
+ {
+ version = ParseLeaf(message, "VID.1", message.IndexOf("MSH.12", StringComparison.Ordinal));
+ }
+
+ return version;
+ }
+
+ ///
+ /// Checks if a node content should be kept as original (ie.: whitespaces won't be removed).
+ ///
+ /// The target node.
+ ///
+ /// true if whitespaces should not be removed from node content; otherwise, false.
+ ///
+ protected internal virtual bool KeepAsOriginal(XmlNode node)
+ {
+ return
+ node.Name != null
+ && concatKeepAsOriginalNodes.IndexOf(node.Name, StringComparison.Ordinal) != -1;
+ }
+
+ ///
+ /// Removes all unnecessary whitespace from the given String (intended to be used with Primitive values).
+ /// This includes leading and trailing whitespace, and repeated space characters. Carriage returns,
+ /// line feeds, and tabs are replaced with spaces.
+ ///
+ protected internal virtual string RemoveWhitespace(string s)
+ {
+ s = s.Replace('\r', ' ');
+ s = s.Replace('\n', ' ');
+ s = s.Replace('\t', ' ');
+
+ var repeatedSpacesExist = true;
+ while (repeatedSpacesExist)
+ {
+ var loc = s.IndexOf(" ", StringComparison.Ordinal);
+ if (loc < 0)
+ {
+ repeatedSpacesExist = false;
+ }
+ else
+ {
+ var buf = new StringBuilder();
+ buf.Append(s.Substring(0, loc - 0));
+ buf.Append(" ");
+ buf.Append(s.Substring(loc + 2));
+ s = buf.ToString();
+ }
+ }
+
+ return s.Trim();
+ }
+
+ ///
+ /// Parses a message string and returns the corresponding Message
+ /// object. This method checks that the given message string is XML encoded, creates an
+ /// XML Document object (using Xerces) from the given String, and calls the abstract
+ /// method .
+ ///
+ protected internal override IMessage DoParse(string message, string version, ParserOptions parserOptions)
+ {
+ IMessage m;
+
+ // parse message string into a DOM document
+ try
+ {
+ var doc = new XmlDocument();
+ doc.Load(new StringReader(message));
+
+ m = ParseDocument(doc, version, parserOptions);
+ }
+ catch (XmlException e)
+ {
+ throw new HL7Exception("XmlException parsing XML", ErrorCode.APPLICATION_INTERNAL_ERROR, e);
+ }
+ catch (IOException e)
+ {
+ throw new HL7Exception("IOException parsing XML", ErrorCode.APPLICATION_INTERNAL_ERROR, e);
+ }
+
+ return m;
+ }
+
+ ///
+ /// Formats a Message object into an HL7 message string using the given encoding.
+ ///
+ /// Thrown if the data fields in the message do not permit encoding (e.g. required fields are null).
+ /// Thrown if the requested encoding is not supported by this parser.
+ protected internal override string DoEncode(IMessage source, string encoding, ParserOptions parserOptions)
+ {
+ if (!SupportsEncoding("XML"))
+ {
+ throw new EncodingNotSupportedException("XMLParser supports only XML encoding");
+ }
+
+ return Encode(source, parserOptions);
+ }
+
+ ///
+ /// Formats a Message object into an HL7 message string using this parser's
+ /// default encoding (XML encoding). This method calls the abstract method.
+ /// encodeDocument(...) in order to obtain XML Document object
+ /// representation of the Message, then serializes it to a String.
+ ///
+ /// Thrown if the data fields in the message do not permit encoding (e.g. required fields are null).
+ protected internal override string DoEncode(IMessage source, ParserOptions parserOptions)
+ {
+ if (source is GenericMessage)
+ {
+ throw new HL7Exception(
+ "Can't XML-encode a GenericMessage. Message must have a recognized structure.");
+ }
+
+ var doc = EncodeDocument(source, parserOptions);
+ doc.DocumentElement.SetAttribute("xmlns", "urn:hl7-org:v2xml");
+
+ return doc.OuterXml;
+ }
+
+ ///
+ /// Attempts to retrieve the value of a leaf tag without using DOM or SAX.
+ /// This method searches the given message string for the given tag name, and returns
+ /// everything after the given tag and before the start of the next tag. Whitespace
+ /// is stripped. This is intended only for lead nodes, as the value is considered to
+ /// end at the start of the next tag, regardless of whether it is the matching end
+ /// tag or some other nested tag.
+ ///
+ /// a string message in XML form.
+ /// the name of the XML tag, e.g. "MSA.2".
+ /// the character location at which to start searching.
+ /// Thrown if the tag can not be found.
+ protected internal virtual string ParseLeaf(string message, string tagName, int startAt)
+ {
+ var tagStart = message.IndexOf("<" + tagName, startAt, StringComparison.Ordinal);
+ if (tagStart < 0)
+ {
+ tagStart = message.IndexOf("<" + tagName.ToUpperInvariant(), startAt, StringComparison.Ordinal);
+ }
+
+ var valStart = message.IndexOf(">", tagStart, StringComparison.Ordinal) + 1;
+ var valEnd = message.IndexOf("<", valStart, StringComparison.Ordinal);
+
+ string value;
+ if (tagStart >= 0 && valEnd >= valStart)
+ {
+ value = message.Substring(valStart, valEnd - valStart);
+ }
+ else
+ {
+ throw new HL7Exception(
+ $"Couldn't find {tagName} in message beginning: {message.Substring(0, Math.Min(150, message.Length) - 0)}",
+ ErrorCode.REQUIRED_FIELD_MISSING);
+ }
+
+ return value;
+ }
+
+ /// Populates a Composite type by looping through it's children, finding corresponding
+ /// Elements among the children of the given Element, and calling parse(Type, Element) for
+ /// each.
+ ///
+ private void ParseComposite(IComposite datatypeObject, XmlElement datatypeElement)
+ {
+ if (datatypeObject is GenericComposite)
+ {
+ // elements won't be named GenericComposite.x
+ var children = datatypeElement.ChildNodes;
+ var compNum = 0;
+ for (var i = 0; i < children.Count; i++)
+ {
+ if (Convert.ToInt16(children.Item(i).NodeType) == (short)XmlNodeType.Element)
+ {
+ Parse(datatypeObject[compNum], (XmlElement)children.Item(i));
+ compNum++;
+ }
+ }
+ }
+ else
+ {
+ var children = datatypeObject.Components;
+ for (var i = 0; i < children.Length; i++)
+ {
+ var matchingElements = datatypeElement.GetElementsByTagName(MakeElementName(datatypeObject, i + 1));
+ if (matchingElements.Count > 0)
+ {
+ Parse(children[i], (XmlElement)matchingElements.Item(0)); // components don't repeat - use 1st
+ }
+ }
+ }
+ }
+
+ ///
+ /// Returns the expected XML element name for the given child of the given Segment.
+ ///
+ private string MakeElementName(ISegment s, int child)
+ {
+ return $"{s.GetStructureName()}.{child}";
+ }
+
+ ///
+ /// Returns the expected XML element name for the given child of the given Composite.
+ ///
+ private string MakeElementName(IComposite composite, int child)
+ {
+ return $"{composite.TypeName}.{child}";
+ }
+
+ ///
+ /// Populates the given with data from the given , by inserting
+ /// XmlElements corresponding to the Type's components and values.
+ ///
+ ///
+ /// if the given type contains a value (i.e. for Primitives, if
+ /// doesn't return null, and for Composites, if at least one underlying
+ /// Primitive doesn't return null).
+ ///
+ private bool Encode(IType datatypeObject, XmlElement datatypeElement, ParserOptions parserOptions)
+ {
+ var hasData = false;
+
+ // TODO: consider using a switch statement
+ if (datatypeObject is Varies)
+ {
+ hasData = EncodeVaries((Varies)datatypeObject, datatypeElement, parserOptions);
+ }
+ else if (datatypeObject is IPrimitive)
+ {
+ hasData = EncodePrimitive((IPrimitive)datatypeObject, datatypeElement);
+ }
+ else if (datatypeObject is IComposite)
+ {
+ hasData = EncodeComposite((IComposite)datatypeObject, datatypeElement, parserOptions);
+ }
+
+ return hasData;
+ }
+
+ ///
+ /// Encodes a Varies type by extracting it's data field and encoding that.
+ ///
+ /// if the data field (or one of its components) contains a value.
+ private bool EncodeVaries(Varies datatypeObject, XmlElement datatypeElement, ParserOptions parserOptions)
+ {
+ var hasData = false;
+ if (datatypeObject.Data != null)
+ {
+ hasData = Encode(datatypeObject.Data, datatypeElement, parserOptions);
+ }
+
+ return hasData;
+ }
+
+ ///
+ /// Encodes a Primitive in XML by adding it's value as a child of the given Element.
+ ///
+ /// if the given Primitive contains a value.
+ private bool EncodePrimitive(IPrimitive datatypeObject, XmlElement datatypeElement)
+ {
+ var hasValue = datatypeObject.Value != null && !datatypeObject.Value.Equals(string.Empty);
+
+ var t = datatypeElement.OwnerDocument.CreateTextNode(datatypeObject.Value);
+ if (hasValue)
+ {
+ try
+ {
+ datatypeElement.AppendChild(t);
+ }
+ catch (Exception e)
+ {
+ throw new DataTypeException("DOMException encoding Primitive: ", e);
+ }
+ }
+
+ return hasValue;
+ }
+
+ ///
+ /// Encodes a Composite in XML by looping through it's components, creating new
+ /// children for each of them (with the appropriate names) and populating them by
+ /// calling using these children.
+ ///
+ /// if at least one component contains a value.
+ private bool EncodeComposite(IComposite datatypeObject, XmlElement datatypeElement, ParserOptions parserOptions)
+ {
+ var components = datatypeObject.Components;
+ var hasValue = false;
+ for (var i = 0; i < components.Length; i++)
+ {
+ var name = MakeElementName(datatypeObject, i + 1);
+ var newNode = datatypeElement.OwnerDocument.CreateElement(name);
+ var componentHasValue = Encode(components[i], newNode, parserOptions);
+ if (componentHasValue)
+ {
+ try
+ {
+ datatypeElement.AppendChild(newNode);
+ }
+ catch (Exception e)
+ {
+ throw new DataTypeException("DOMException encoding Composite: ", e);
+ }
+
+ hasValue = true;
+ }
+ }
+
+ return hasValue;
+ }
+
+ private void ParseReps(ISegment segmentObject, XmlElement segmentElement, string fieldName, int fieldNum)
+ {
+ var reps = segmentElement.GetElementsByTagName(fieldName);
+ for (var i = 0; i < reps.Count; i++)
+ {
+ Parse(segmentObject.GetField(fieldNum, i), (XmlElement)reps.Item(i));
+ }
+ }
+
+ ///
+ /// Parses an XML element into a Varies by determining whether the element is primitive or
+ /// composite, calling setData() on the Varies with a new generic primitive or composite as appropriate,
+ /// and then calling parse again with the new Type object.
+ ///
+ private void ParseVaries(Varies datatypeObject, XmlElement datatypeElement)
+ {
+ // figure out what data type it holds
+ if (!HasChildElement(datatypeElement))
+ {
+ // it's a primitive
+ datatypeObject.Data = new GenericPrimitive(datatypeObject.Message);
+ }
+ else
+ {
+ // it's a composite ... almost know what type, except that we don't have the version here
+ datatypeObject.Data = new GenericComposite(datatypeObject.Message);
+ }
+
+ Parse(datatypeObject.Data, datatypeElement);
+ }
+
+ /// Returns true if any of the given element's children are elements.
+ private bool HasChildElement(XmlElement e)
+ {
+ var children = e.ChildNodes;
+ var hasElement = false;
+ var c = 0;
+ while (c < children.Count && !hasElement)
+ {
+ if (Convert.ToInt16(children.Item(c).NodeType) == (short)XmlNodeType.Element)
+ {
+ hasElement = true;
+ }
+
+ c++;
+ }
+
+ return hasElement;
+ }
+
+ /// Parses a primitive type by filling it with text child, if any.
+ private void ParsePrimitive(IPrimitive datatypeObject, XmlElement datatypeElement)
+ {
+ var children = datatypeElement.ChildNodes;
+ var c = 0;
+ var full = false;
+ while (c < children.Count && !full)
+ {
+ var child = children.Item(c++);
+ if (Convert.ToInt16(child.NodeType) == (short)XmlNodeType.Text)
+ {
+ try
+ {
+ if (child.Value != null && !child.Value.Equals(string.Empty))
+ {
+ if (KeepAsOriginal(child.ParentNode))
+ {
+ datatypeObject.Value = child.Value;
+ }
+ else
+ {
+ datatypeObject.Value = RemoveWhitespace(child.Value);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Error("Error parsing primitive value from TEXT_NODE", e);
+ }
+
+ full = true;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NHapi.Base/Parser/ParserBase.cs b/src/NHapi.Base/Parser/ParserBase.cs
index e7644044c..4b09258d3 100644
--- a/src/NHapi.Base/Parser/ParserBase.cs
+++ b/src/NHapi.Base/Parser/ParserBase.cs
@@ -45,16 +45,10 @@ namespace NHapi.Base.Parser
public abstract class ParserBase
{
protected static readonly ParserOptions DefaultParserOptions = new ParserOptions();
-
- private static readonly IHapiLog Log;
+ private static readonly IHapiLog Log = HapiLogFactory.GetHapiLog(typeof(ParserBase));
private IValidationContext validationContext;
private MessageValidator messageValidator;
- static ParserBase()
- {
- Log = HapiLogFactory.GetHapiLog(typeof(ParserBase));
- }
-
///
/// Uses DefaultModelClassFactory for model class lookup.
///
@@ -82,10 +76,7 @@ protected ParserBase(IModelClassFactory theFactory)
///
public IValidationContext ValidationContext
{
- get
- {
- return validationContext;
- }
+ get => validationContext;
set
{
@@ -121,13 +112,13 @@ public static ISegment MakeControlMSH(string version, IModelClassFactory factory
(IMessage)GenericMessage
.GetGenericMessageClass(version)
.GetConstructor(new[] { typeof(IModelClassFactory) })
- .Invoke(new object[] { factory });
+ !.Invoke(new object[] { factory });
- var c = factory.GetSegmentClass("MSH", version);
+ var @class = factory.GetSegmentClass("MSH", version);
var constructorParamTypes = new[] { typeof(IGroup), typeof(IModelClassFactory) };
var constructorParamArgs = new object[] { dummy, factory };
- var constructor = c.GetConstructor(constructorParamTypes);
- msh = (ISegment)constructor.Invoke(constructorParamArgs);
+ var constructor = @class.GetConstructor(constructorParamTypes);
+ msh = (ISegment)constructor!.Invoke(constructorParamArgs);
}
catch (Exception e)
{
@@ -210,6 +201,8 @@ public virtual IMessage Parse(string message)
/// If is null.
public virtual IMessage Parse(string message, ParserOptions parserOptions)
{
+ parserOptions ??= DefaultParserOptions;
+
var encoding = GetEncoding(message);
if (!SupportsEncoding(encoding))
@@ -224,17 +217,14 @@ public virtual IMessage Parse(string message, ParserOptions parserOptions)
}
}
- if (startOfMessage == null)
- {
- startOfMessage = message.Substring(0, Math.Min(message.Length, 50));
- }
+ startOfMessage ??= message.Substring(0, Math.Min(message.Length, 50));
throw new EncodingNotSupportedException(
$"Determine encoding for message. The following is the first 50 chars of the message for reference, although this may not be where the issue is: {startOfMessage}");
}
var version = GetVersion(message);
- if (!ValidVersion(version))
+ if (!parserOptions.AllowUnknownVersions && !ValidVersion(version))
{
throw new HL7Exception(
$"Can't process message of version '{version}' - version not recognized",
@@ -284,6 +274,8 @@ public virtual IMessage Parse(string message, string version, ParserOptions pars
return result;
}
+ // TODO: should this be public?
+ // https://github.com/nHapiNET/nHapi/issues/399
///
/// Parses a particular message and returns the encoded structure. Uses the default
/// .
@@ -296,6 +288,8 @@ public void Parse(IMessage message, string @string)
Parse(message, @string, DefaultParserOptions);
}
+ // TODO: should this be public?
+ // https://github.com/nHapiNET/nHapi/issues/399
///
/// Parses a particular message and returns the encoded structure.
///
@@ -303,7 +297,6 @@ public void Parse(IMessage message, string @string)
/// The string to parse.
/// Contains configuration that will be applied when parsing.
/// If there is a problem encoding.
- /// If is null.
public abstract void Parse(IMessage message, string @string, ParserOptions parserOptions);
///
@@ -317,9 +310,25 @@ public void Parse(IMessage message, string @string)
/// Thrown if the data fields in the message do not permit encoding (e.g. required fields are null).
/// Thrown if the requested encoding is not supported by this parser.
public virtual string Encode(IMessage source, string encoding)
+ {
+ return Encode(source, encoding, DefaultParserOptions);
+ }
+
+ ///
+ /// Formats a object into an HL7 message string using the given encoding.
+ ///
+ /// An object from which to construct an encoded message string.
+ /// the name of the HL7 encoding to use (eg "XML"; most implementations support only
+ /// one encoding).
+ ///
+ /// Contains configuration that will be applied when encoding.
+ /// The encoded message.
+ /// Thrown if the data fields in the message do not permit encoding (e.g. required fields are null).
+ /// Thrown if the requested encoding is not supported by this parser.
+ public virtual string Encode(IMessage source, string encoding, ParserOptions parserOptions)
{
messageValidator.Validate(source);
- var result = DoEncode(source, encoding);
+ var result = DoEncode(source, encoding, parserOptions);
messageValidator.Validate(result, encoding.Equals("XML"), source.Version);
return result;
@@ -334,11 +343,25 @@ public virtual string Encode(IMessage source, string encoding)
///
/// The encoded message.
public virtual string Encode(IMessage source)
+ {
+ return Encode(source, DefaultParserOptions);
+ }
+
+ ///
+ /// Formats a Message object into an HL7 message string using this parsers
+ /// default encoding.
+ ///
+ ///
+ /// A Message object from which to construct an encoded message string.
+ ///
+ /// Contains configuration that will be applied when encoding.
+ /// The encoded message.
+ public virtual string Encode(IMessage source, ParserOptions parserOptions)
{
var encoding = DefaultEncoding;
messageValidator.Validate(source);
- var result = DoEncode(source);
+ var result = DoEncode(source, parserOptions);
messageValidator.Validate(result, encoding.Equals("XML"), source.Version);
return result;
@@ -422,27 +445,43 @@ public bool SupportsEncoding(string encoding)
/// The version is needed prior to parsing in order to determine the message class
/// into which the text of the message should be parsed.
///
+ /// The message to inspect.
+ /// Thrown if the version field can not be found.
+ public virtual string GetVersion(string message)
+ {
+ return GetVersion(message, DefaultParserOptions);
+ }
+
+ ///
+ /// Returns the version ID (MSH-12) from the given message, without fully parsing the message.
+ /// The version is needed prior to parsing in order to determine the message class
+ /// into which the text of the message should be parsed.
+ ///
+ /// The message to inspect.
+ /// Contains configuration that will be applied when parsing.
/// Thrown if the version field can not be found.
- public abstract string GetVersion(string message);
+ public abstract string GetVersion(string message, ParserOptions parserOptions);
///
- /// Called by to perform implementation-specific encoding work.
+ /// Called by to perform implementation-specific encoding work.
///
/// a Message object from which to construct an encoded message string.
/// the name of the HL7 encoding to use (eg "XML"; most implementations support only one encoding).
+ /// Contains configuration that will be applied when encoding.
/// The encoded message.
/// Thrown if the data fields in the message do not permit encoding (e.g. required fields are null).
/// Thrown if the requested encoding is not supported by this parser.
- protected internal abstract string DoEncode(IMessage source, string encoding);
+ protected internal abstract string DoEncode(IMessage source, string encoding, ParserOptions parserOptions);
///
- /// Called by to perform implementation-specific encoding work.
+ /// Called by to perform implementation-specific encoding work.
///
/// a Message object from which to construct an encoded message string.
+ /// Contains configuration that will be applied when encoding.
/// The encoded message.
/// Thrown if the data fields in the message do not permit encoding (e.g. required fields are null).
/// Thrown if the requested encoding is not supported by this parser.
- protected internal abstract string DoEncode(IMessage source);
+ protected internal abstract string DoEncode(IMessage source, ParserOptions parserOptions);
///
/// Called by to perform implementation-specific parsing work.
@@ -492,9 +531,10 @@ protected internal virtual IMessage InstantiateMessage(string theName, string th
}
Log.Info($"Instantiating msg of class {messageClass.FullName}");
- var constructor = messageClass.GetConstructor(new Type[] { typeof(IModelClassFactory) });
- var result = (IMessage)constructor.Invoke(new object[] { Factory });
+ var constructor = messageClass.GetConstructor(new[] { typeof(IModelClassFactory) });
+ var result = (IMessage)constructor!.Invoke(new object[] { Factory });
result.ValidationContext = validationContext;
+
return result;
}
}
diff --git a/src/NHapi.Base/Parser/ParserOptions.cs b/src/NHapi.Base/Parser/ParserOptions.cs
index 0162273ce..57ea1e756 100644
--- a/src/NHapi.Base/Parser/ParserOptions.cs
+++ b/src/NHapi.Base/Parser/ParserOptions.cs
@@ -1,15 +1,36 @@
namespace NHapi.Base.Parser
{
+ using System;
+ using System.Collections.Generic;
+
public class ParserOptions
{
public ParserOptions()
{
+ AllowUnknownVersions = false;
DefaultObx2Type = null;
InvalidObx2Type = null;
UnexpectedSegmentBehaviour = UnexpectedSegmentBehaviour.AddInline;
NonGreedyMode = false;
+ DisableWhitespaceTrimmingOnAllXmlNodes = false;
+ XmlNodeNamesToDisableWhitespaceTrimming = new HashSet(StringComparer.Ordinal);
+ PrettyPrintEncodedXml = true;
}
+ ///
+ ///
+ /// If set to , the parser will allow messages to parse, even if they
+ /// contain a version which is not known to the parser.
+ ///
+ ///
+ /// When operating in this mode, if a message arrives which an unknown version string, the
+ /// parser will attempt to parse it using a class
+ /// instead of a specific nhapi structure class.
+ ///
+ ///
+ /// The default value is .
+ public bool AllowUnknownVersions { get; set; }
+
///
/// If this property is set, the value provides a default datatype ("ST",
/// "NM", etc) for an OBX segment with a missing OBX-2 value. This is useful
@@ -57,11 +78,12 @@ public ParserOptions()
public UnexpectedSegmentBehaviour UnexpectedSegmentBehaviour { get; set; }
///
- /// If set to true (default is false), pipe parser will be put in non-greedy mode. This setting
+ /// If set to , pipe parser will be put in non-greedy mode. This setting
/// applies only to and will have no effect on .
///
///
///
+ /// The default value is .
///
/// In non-greedy mode, if the message structure being parsed has an ambiguous choice of where to put a segment
/// because there is a segment matching the current segment name in both a later position in the message, and
@@ -106,5 +128,39 @@ public ParserOptions()
///
///
public bool NonGreedyMode { get; set; }
+
+ ///
+ ///
+ /// If set to , the is configured to treat all whitespace
+ /// text nodes as literal, meaning that line breaks, tabs, multiple spaces, etc. will be preserved.
+ ///
+ ///
+ /// If set to , any values passed to
+ /// will be superseded since all whitespace will treated as literal.
+ ///
+ ///
+ /// The default value is .
+ public bool DisableWhitespaceTrimmingOnAllXmlNodes { get; set; }
+
+ ///
+ ///
+ /// Configures the to treat all whitespace within the given
+ /// as literal, meaning that line breaks, tabs, multiple spaces, etc. will be preserved.
+ ///
+ ///
+ /// The default value is an Empty .
+ public HashSet XmlNodeNamesToDisableWhitespaceTrimming { get; set; }
+
+ ///
+ ///
+ /// If set to , the will attempt to pretty-print the XML
+ /// they generate.
+ ///
+ ///
+ /// This means the messages will look nicer to humans, but may take up slightly more space/bandwidth.
+ ///
+ ///
+ /// The default value is .
+ public bool PrettyPrintEncodedXml { get; set; }
}
-}
+}
\ No newline at end of file
diff --git a/src/NHapi.Base/Parser/PipeParser.cs b/src/NHapi.Base/Parser/PipeParser.cs
index 8c2f4baf3..43edb2ce0 100644
--- a/src/NHapi.Base/Parser/PipeParser.cs
+++ b/src/NHapi.Base/Parser/PipeParser.cs
@@ -148,9 +148,8 @@ public static string[] Split(string composite, string delimiter)
/// Thrown if the requested encoding is not supported by this parser.
public static string Encode(IType source, EncodingCharacters encodingChars)
{
- if (source is Varies)
+ if (source is Varies varies)
{
- var varies = (Varies)source;
if (varies.Data != null)
{
source = varies.Data;
@@ -313,6 +312,8 @@ public virtual string GetMessageStructure(string message)
return GetStructure(message).Structure;
}
+ // TODO: should this be public?
+ // https://github.com/nHapiNET/nHapi/issues/399
///
/// Parses a segment string and populates the given Segment object.
///
@@ -330,6 +331,8 @@ public virtual void Parse(ISegment destination, string segment, EncodingCharacte
Parse(destination, segment, encodingChars, DefaultParserOptions);
}
+ // TODO: should this be public?
+ // https://github.com/nHapiNET/nHapi/issues/399
///
/// Parses a segment string and populates the given Segment object.
///
@@ -345,10 +348,12 @@ public virtual void Parse(ISegment destination, string segment, EncodingCharacte
///
public virtual void Parse(ISegment destination, string segment, EncodingCharacters encodingChars, ParserOptions parserOptions)
{
- parserOptions = parserOptions ?? DefaultParserOptions;
+ parserOptions ??= DefaultParserOptions;
Parse(destination, segment, encodingChars, 0, parserOptions);
}
+ // TODO: should this be public?
+ // https://github.com/nHapiNET/nHapi/issues/399
///
/// Parses a segment string and populates the given Segment object.
///
@@ -367,6 +372,8 @@ public virtual void Parse(ISegment destination, string segment, EncodingCharacte
Parse(destination, segment, encodingChars, repetition, DefaultParserOptions);
}
+ // TODO: should this be public?
+ // https://github.com/nHapiNET/nHapi/issues/399
///
/// Parses a segment string and populates the given Segment object.
///
@@ -383,7 +390,7 @@ public virtual void Parse(ISegment destination, string segment, EncodingCharacte
///
public virtual void Parse(ISegment destination, string segment, EncodingCharacters encodingChars, int repetition, ParserOptions parserOptions)
{
- parserOptions = parserOptions ?? DefaultParserOptions;
+ parserOptions ??= DefaultParserOptions;
var fieldOffset = 0;
if (IsDelimDefSegment(destination.GetStructureName()))
@@ -445,15 +452,19 @@ public virtual void Parse(ISegment destination, string segment, EncodingCharacte
}
// set data type of OBX-5
- if (destination.GetType().FullName.IndexOf("OBX") >= 0)
+ if (destination.GetType().FullName!.IndexOf("OBX", StringComparison.Ordinal) >= 0)
{
Varies.FixOBX5(destination, Factory, parserOptions);
}
}
+ // TODO: should this be public?
+ // https://github.com/nHapiNET/nHapi/issues/399
///
public override void Parse(IMessage message, string @string, ParserOptions parserOptions)
{
+ parserOptions ??= DefaultParserOptions;
+
if (parserOptions is null)
{
throw new ArgumentNullException(nameof(parserOptions));
@@ -539,7 +550,7 @@ public override void Parse(IMessage message, string @string, ParserOptions parse
public override ISegment GetCriticalResponseData(string message)
{
// try to get MSH segment
- var mshStart = message.IndexOf("MSH");
+ var mshStart = message.IndexOf("MSH", StringComparison.Ordinal);
if (mshStart < 0)
{
throw new HL7Exception("Couldn't find MSH segment in message: " + message, ErrorCode.SEGMENT_SEQUENCE_ERROR);
@@ -600,7 +611,7 @@ public override ISegment GetCriticalResponseData(string message)
public override string GetAckID(string message)
{
string ackID = null;
- var msaStart = message.IndexOf("\rMSA");
+ var msaStart = message.IndexOf("\rMSA", StringComparison.Ordinal);
if (msaStart >= 0)
{
@@ -608,7 +619,7 @@ public override string GetAckID(string message)
var fieldDelimiter = message[startFieldOne - 1];
var start = message.IndexOf(fieldDelimiter, startFieldOne) + 1;
var end = message.IndexOf(fieldDelimiter, start);
- var segEnd = message.IndexOf(SegmentDelimiter, start);
+ var segEnd = message.IndexOf(SegmentDelimiter, start, StringComparison.Ordinal);
if (segEnd > start && segEnd < end)
{
@@ -639,10 +650,10 @@ public override string GetAckID(string message)
}
///
- public override string GetVersion(string message)
+ public override string GetVersion(string message, ParserOptions parserOptions)
{
- var startMsh = message.IndexOf("MSH");
- var endMsh = message.IndexOf(SegmentDelimiter, startMsh);
+ var startMsh = message.IndexOf("MSH", StringComparison.Ordinal);
+ var endMsh = message.IndexOf(SegmentDelimiter, startMsh, StringComparison.Ordinal);
if (endMsh < 0)
{
@@ -670,7 +681,7 @@ public override string GetVersion(string message)
else
{
throw new HL7Exception(
- $"Can't find encoding characters - MSH has only {fields.Length} fields",
+ $"Can't find encoding characters - MSH has only {fields.Length} fields - MSH-2 is {fields[1]}",
ErrorCode.REQUIRED_FIELD_MISSING);
}
@@ -689,22 +700,28 @@ public override string GetVersion(string message)
ErrorCode.REQUIRED_FIELD_MISSING);
}
+ if (parserOptions.AllowUnknownVersions)
+ {
+ // TODO: Version.HighestAvailableVersionOrDefault.Version
+ // https://github.com/nHapiNET/nHapi/issues/400
+ }
+
return comp[0];
}
///
- protected internal override string DoEncode(IMessage source, string encoding)
+ protected internal override string DoEncode(IMessage source, string encoding, ParserOptions parserOptions)
{
if (!SupportsEncoding(encoding))
{
throw new EncodingNotSupportedException("This parser does not support the " + encoding + " encoding");
}
- return Encode(source);
+ return Encode(source, parserOptions);
}
///
- protected internal override string DoEncode(IMessage source)
+ protected internal override string DoEncode(IMessage source, ParserOptions parserOptions)
{
// get encoding characters ...
var msh = (ISegment)source.GetStructure("MSH");
@@ -736,7 +753,7 @@ protected internal override string DoEncode(IMessage source)
{
// Create the MsgType and Trigger Event if not there
var messageTypeFullname = source.GetStructureName();
- var i = messageTypeFullname.IndexOf("_");
+ var i = messageTypeFullname.IndexOf("_", StringComparison.Ordinal);
if (i > 0)
{
var type = messageTypeFullname.Substring(0, i);
@@ -874,9 +891,10 @@ private EncodingCharacters GetValidEncodingCharacters(char fieldSep, ISegment ms
Terser.Set(msh, 2, 0, 1, 1, encCharString);
}
- var version27 = "2.7";
+ var version27 = new Version("2.7");
+ var messageVersion = new Version(msh.Message.Version);
- if (string.CompareOrdinal(version27, msh.Message.Version) > 0 && encCharString.Length != 4)
+ if (version27 > messageVersion && encCharString.Length != 4)
{
throw new HL7Exception(
$"Encoding characters (MSH-2) value '{encCharString}' invalid -- must be 4 characters", ErrorCode.DATA_TYPE_ERROR);
@@ -904,7 +922,7 @@ private MessageStructure GetStructure(string message)
try
{
var fields = Split(
- message.Substring(0, Math.Max(message.IndexOf(SegmentDelimiter), message.Length) - 0),
+ message.Substring(0, Math.Max(message.IndexOf(SegmentDelimiter, StringComparison.Ordinal), message.Length) - 0),
Convert.ToString(encodingCharacters.FieldSeparator));
wholeFieldNine = fields[8];
@@ -972,7 +990,7 @@ private IStructureDefinition GetStructureDefinition(IMessage theMessage)
Log.Info($"Instantiating msg of class {messageType.FullName}");
var constructor = messageType.GetConstructor(new[] { typeof(IModelClassFactory) });
- var message = (IMessage)constructor.Invoke(new object[] { Factory });
+ var message = (IMessage)constructor!.Invoke(new object[] { Factory });
StructureDefinition previousLeaf = null;
retVal = CreateStructureDefinition(message, ref previousLeaf);
diff --git a/src/NHapi.Base/Parser/XMLParser.cs b/src/NHapi.Base/Parser/XMLParser.cs
index 4aec12f12..795500864 100644
--- a/src/NHapi.Base/Parser/XMLParser.cs
+++ b/src/NHapi.Base/Parser/XMLParser.cs
@@ -28,8 +28,11 @@ this file under either the MPL or the GPL.
namespace NHapi.Base.Parser
{
using System;
+ using System.Collections.Generic;
using System.IO;
+ using System.Linq;
using System.Text;
+ using System.Text.RegularExpressions;
using System.Xml;
using NHapi.Base.Log;
@@ -48,39 +51,23 @@ namespace NHapi.Base.Parser
/// Bryan Tripp, Shawn Bellina.
public abstract class XMLParser : ParserBase
{
- private static readonly IHapiLog Log;
+ protected static readonly string NameSpace = "urn:hl7-org:v2xml";
- private readonly XmlDocument parser;
+ private static readonly IHapiLog Log = HapiLogFactory.GetHapiLog(typeof(XMLParser));
- ///
- /// The nodes whose names match these strings will be kept as original,
- /// meaning that no white space trimming will occur on them.
- ///
- private string[] keepAsOriginalNodes;
+ private static readonly string EscapeAttrName = "V";
- /// All keepAsOriginalNodes names, concatenated by a pipe (|).
- private string concatKeepAsOriginalNodes = string.Empty;
+ private static readonly string EscapeNodeName = "escape";
- static XMLParser()
- {
- Log = HapiLogFactory.GetHapiLog(typeof(XMLParser));
- }
+ private static readonly Regex NameSpaceRegex = new Regex(@$"xmlns(.*)=""{NameSpace}""", RegexOptions.Compiled);
protected XMLParser()
{
- parser = new XmlDocument
- {
- PreserveWhitespace = false,
- };
}
protected XMLParser(IModelClassFactory factory)
: base(factory)
{
- parser = new XmlDocument
- {
- PreserveWhitespace = false,
- };
}
///
@@ -89,41 +76,13 @@ protected XMLParser(IModelClassFactory factory)
public override string DefaultEncoding => "XML";
///
- /// Sets the keepAsOriginalNodes.
///
- /// The nodes whose names match the keepAsOriginalNodes will be kept as original,
+ /// The nodes whose names match these strings will be kept as original,
/// meaning that no white space trimming will occur on them.
///
///
- public virtual string[] KeepAsOriginalNodes
- {
- get
- {
- return keepAsOriginalNodes;
- }
-
- set
- {
- keepAsOriginalNodes = value;
-
- if (value.Length != 0)
- {
- // initializes the
- var strBuf = new StringBuilder(value[0]);
- for (var i = 1; i < value.Length; i++)
- {
- strBuf.Append("|");
- strBuf.Append(value[i]);
- }
-
- concatKeepAsOriginalNodes = strBuf.ToString();
- }
- else
- {
- concatKeepAsOriginalNodes = string.Empty;
- }
- }
- }
+ [Obsolete("This method has been replaced by 'ParserOptions.XmlNodeNamesToDisableWhitespaceTrimming'.")]
+ public virtual IEnumerable KeepAsOriginalNodes { get; set; } = new List();
/// Test harness.
[STAThread]
@@ -142,11 +101,11 @@ public static void Main(string[] args)
var messageFile = new FileInfo(args[0]);
var fileLength = SupportClass.FileLength(messageFile);
var r = new StreamReader(messageFile.FullName, Encoding.Default);
- var cbuf = new char[(int)fileLength];
+ var buffer = new char[(int)fileLength];
Console.Out.WriteLine(
- $"Reading message file ... {r.Read((char[])cbuf, 0, cbuf.Length)} of {fileLength} chars");
+ $"Reading message file ... {r.Read(buffer, 0, buffer.Length)} of {fileLength} chars");
r.Close();
- var messString = Convert.ToString(cbuf);
+ var messString = Convert.ToString(buffer);
var mess = parser.Parse(messString);
Console.Out.WriteLine("Got message of type " + mess.GetType().FullName);
@@ -162,26 +121,25 @@ public static void Main(string[] args)
if (reps[j] is ISegment)
{
// ignore groups
- var docBuilder = new XmlDocument();
var doc = new XmlDocument(); // new doc for each segment
- var root = doc.CreateElement(reps[j].GetType().FullName);
+ var root = doc.CreateElement(reps[j].GetType().FullName, NameSpace);
doc.AppendChild(root);
xp.Encode((ISegment)reps[j], root);
- var out_Renamed = new StringWriter();
+ var outRenamed = new StringWriter();
Console.Out.WriteLine("Segment " + reps[j].GetType().FullName + ": \r\n" + doc.OuterXml);
- var segmentConstructTypes = new Type[] { typeof(IMessage) };
+ var segmentConstructTypes = new[] { typeof(IMessage) };
var segmentConstructArgs = new object[] { null };
var s = (ISegment)reps[j].GetType().GetConstructor(segmentConstructTypes).Invoke(segmentConstructArgs);
xp.Parse(s, root);
var doc2 = new XmlDocument();
- var root2 = doc2.CreateElement(s.GetType().FullName);
+ var root2 = doc2.CreateElement(s.GetType().FullName, NameSpace);
doc2.AppendChild(root2);
xp.Encode(s, root2);
var out2 = new StringWriter();
var ser = XmlWriter.Create(out2);
doc.WriteTo(ser);
- if (out2.ToString().Equals(out_Renamed.ToString()))
+ if (out2.ToString().Equals(outRenamed.ToString()))
{
Console.Out.WriteLine("Re-encode OK");
}
@@ -212,103 +170,138 @@ public static void Main(string[] args)
///
public override string GetEncoding(string message)
{
- string encoding = null;
-
- // check for a number of expected strings
- var expected = new string[] { "" };
- var isXML = true;
- for (var i = 0; i < expected.Length; i++)
- {
- if (message.IndexOf(expected[i]) < 0)
- {
- isXML = false;
- }
- }
-
- if (isXML)
- {
- encoding = "XML";
- }
-
- return encoding;
+ return EncodingDetector.IsXmlEncoded(message) ? DefaultEncoding : null;
}
- /// Creates and populates a Message object from an XML Document that contains an XML-encoded HL7 message.
- /// The easiest way to implement this method for a particular message structure is as follows:
- ///
- Create an instance of the Message type you are going to handle with your subclass
- /// of XMLParser
- /// - Go through the given Document and find the Elements that represent the top level of
- /// each message segment.
- /// - For each of these segments, call
parse(Segment segmentObject, Element segmentElement),
- /// providing the appropriate Segment from your Message object, and the corresponding Element.
- /// At the end of this process, your Message object should be populated with data from the XML
- /// Document.
- ///
- /// HL7Exception if the message is not correctly formatted.
- /// EncodingNotSupportedException if the message encoded.
- /// is not supported by this parser.
+ ///
+ /// Creates and populates a Message object from an XML Document that contains an XML-encoded HL7 message.
+ ///
+ /// The easiest way to implement this method for a particular message structure is as follows:
+ ///
+ /// - Create an instance of the Message type you are going to handle with your subclass of
+ ///
+ /// - Go through the given and find the XmlElements
+ /// that represent the top level of each message segment.
+ /// - For each of these segments, call ,
+ /// providing the appropriate from your object, and the
+ /// corresponding .
+ ///
+ /// At the end of this process, your object should be populated with data from the
+ /// .
+ ///
///
+ /// Xml encoded HL7 parsed into .
+ /// The name of the HL7 version to which the message belongs (eg "2.5").
+ /// A parsed HL7 message.
+ /// If the message is not correctly formatted.
+ ///
+ /// If the message encoded is not supported by this parser.
+ ///
public IMessage ParseDocument(XmlDocument xmlMessage, string version)
{
return ParseDocument(xmlMessage, version, DefaultParserOptions);
}
- /// Creates and populates a Message object from an XML Document that contains an XML-encoded HL7 message.
- /// The easiest way to implement this method for a particular message structure is as follows:
- ///
- Create an instance of the Message type you are going to handle with your subclass
- /// of XMLParser
- /// - Go through the given Document and find the Elements that represent the top level of
- /// each message segment.
- /// - For each of these segments, call
parse(Segment segmentObject, Element segmentElement),
- /// providing the appropriate Segment from your Message object, and the corresponding Element.
- /// At the end of this process, your Message object should be populated with data from the XML
- /// Document.
- ///
- /// HL7Exception if the message is not correctly formatted.
- /// EncodingNotSupportedException if the message encoded.
- /// is not supported by this parser.
+ ///
+ /// Creates and populates a Message object from an XML Document that contains an XML-encoded HL7 message.
+ ///
+ /// The easiest way to implement this method for a particular message structure is as follows:
+ ///
+ /// - Create an instance of the Message type you are going to handle with your subclass of
+ ///
+ /// - Go through the given and find the XmlElements
+ /// that represent the top level of each message segment.
+ /// - For each of these segments, call ,
+ /// providing the appropriate from your object, and the
+ /// corresponding .
+ ///
+ /// At the end of this process, your object should be populated with data from the
+ /// .
+ ///
///
+ /// Xml encoded HL7 parsed into .
+ /// The name of the HL7 version to which the message belongs (eg "2.5").
+ /// Contains configuration that will be applied when parsing.
+ /// A parsed HL7 message.
+ /// If the message is not correctly formatted.
+ ///
+ /// If the message encoded is not supported by this parser.
+ ///
public abstract IMessage ParseDocument(XmlDocument xmlMessage, string version, ParserOptions parserOptions);
- /// Creates an XML Document that corresponds to the given Message object.
- /// If you are implementing this method, you should create an XML Document, and insert XML Elements
- /// into it that correspond to the groups and segments that belong to the message type that your subclass
- /// of XMLParser supports. Then, for each segment in the message, call the method
- /// encode(Segment segmentObject, Element segmentElement) using the Element for
- /// that segment and the corresponding Segment object from the given Message.
+ ///
+ /// Creates an that corresponds to the given object.
+ /// If you are implementing this method, you should create an , and insert
+ /// XmlElements into it that correspond to the IGroups and
+ /// ISegments that belong to the type that your subclass
+ /// of supports. Then, for each segment in the message, call the method
+ /// using the for
+ /// that segment and the corresponding object from the given .
+ ///
+ /// An object from which to construct an encoded message string.
+ /// An representation of the HL7 message.
+ /// When unable to create/populate the .
+ public XmlDocument EncodeDocument(IMessage source)
+ {
+ return EncodeDocument(source, DefaultParserOptions);
+ }
+
+ ///
+ /// Creates an that corresponds to the given object.
+ /// If you are implementing this method, you should create an , and insert
+ /// XmlElements into it that correspond to the IGroups and
+ /// ISegments that belong to the type that your subclass
+ /// of supports. Then, for each segment in the message, call the method
+ /// using the for
+ /// that segment and the corresponding object from the given .
///
- public abstract XmlDocument EncodeDocument(IMessage source);
+ /// An object from which to construct an encoded message string.
+ /// Contains configuration that will be applied when encoding.
+ /// An representation of the HL7 message.
+ /// When unable to create/populate the .
+ public abstract XmlDocument EncodeDocument(IMessage source, ParserOptions parserOptions);
- /// Populates the given Segment object with data from the given XML Element.
- /// HL7Exception if the XML Element does not have the correct name and structure.
- /// for the given Segment, or if there is an error while setting individual field values.
+ ///
+ /// Populates the given object with data from the given .
///
+ /// The to parse into.
+ /// The to be parse.
+ ///
+ /// If the does not have the correct name and structure for the given
+ /// , or if there is an error while setting individual field values.
+ ///
public virtual void Parse(ISegment segmentObject, XmlElement segmentElement)
{
Parse(segmentObject, segmentElement, DefaultParserOptions);
}
- /// Populates the given Segment object with data from the given XML Element.
- /// HL7Exception if the XML Element does not have the correct name and structure.
- /// for the given Segment, or if there is an error while setting individual field values.
+ ///
+ /// Populates the given object with data from the given .
///
+ /// The to parse into.
+ /// The to be parse.
+ /// Contains configuration that will be applied when parsing.
+ ///
+ /// If the does not have the correct name and structure for the given
+ /// , or if there is an error while setting individual field values.
+ ///
+ ///
+ /// If any of the of have an invalid namespace.
+ ///
public virtual void Parse(ISegment segmentObject, XmlElement segmentElement, ParserOptions parserOptions)
{
- parserOptions = parserOptions ?? DefaultParserOptions;
+ parserOptions ??= DefaultParserOptions;
- var done = new SupportClass.HashSetSupport();
+ var done = new HashSet(StringComparer.Ordinal);
+ var children = segmentElement.ChildNodes;
- // for (int i = 1; i <= segmentObject.NumFields(); i++) {
- // String elementName = makeElementName(segmentObject, i);
- // done.add(elementName);
- // parseReps(segmentObject, segmentElement, elementName, i);
- // }
- var all = segmentElement.ChildNodes;
- for (var i = 0; i < all.Count; i++)
+ for (var i = 0; i < children.Count; i++)
{
- var elementName = all.Item(i).Name;
- if (Convert.ToInt16(all.Item(i).NodeType) == (short)XmlNodeType.Element && !done.Contains(elementName))
+ var elementName = children[i].LocalName;
+ if (children[i].NodeType == XmlNodeType.Element && !done.Contains(elementName))
{
+ AssertNamespaceUri(children[i].NamespaceURI);
+
done.Add(elementName);
var index = elementName.IndexOf('.');
@@ -317,27 +310,50 @@ public virtual void Parse(ISegment segmentObject, XmlElement segmentElement, Par
// properly formatted element
var fieldNumString = elementName.Substring(index + 1);
var fieldNum = int.Parse(fieldNumString);
- ParseReps(segmentObject, segmentElement, elementName, fieldNum);
+ ParseReps(segmentObject, segmentElement, elementName, fieldNum, parserOptions);
}
else
{
- Log.Debug("Child of segment " + segmentObject.GetStructureName() + " doesn't look like a field: " + elementName);
+ Log.Debug(
+ $"Child of segment {segmentObject.GetStructureName()} doesn't look like a field: {elementName}");
}
}
}
// set data type of OBX-5
- if (segmentObject.GetType().FullName.IndexOf("OBX") >= 0)
+ if (segmentObject.GetType().FullName.IndexOf("OBX", StringComparison.Ordinal) >= 0)
{
Varies.FixOBX5(segmentObject, Factory, parserOptions);
}
+
+ // TODO set data type of MFE-4
}
- /// Populates the given Element with data from the given Segment, by inserting
- /// Elements corresponding to the Segment's fields, their components, etc. Returns
- /// true if there is at least one data value in the segment.
+ ///
+ /// Populates the given with data from the given , by inserting
+ /// XmlElements corresponding to the Segment's fields,
+ /// their components, etc.
///
+ /// The to be encoded.
+ /// The to encode into.
+ /// if there is at least one data value in the .
+ /// If an error occurred while encoding.
public virtual bool Encode(ISegment segmentObject, XmlElement segmentElement)
+ {
+ return Encode(segmentObject, segmentElement, DefaultParserOptions);
+ }
+
+ ///
+ /// Populates the given with data from the given , by inserting
+ /// XmlElements corresponding to the Segment's fields,
+ /// their components, etc.
+ ///
+ /// The to be encoded.
+ /// The to encode into.
+ /// Contains configuration that will be applied when encoding.
+ /// if there is at least one data value in the .
+ /// If an error occurred while encoding.
+ public virtual bool Encode(ISegment segmentObject, XmlElement segmentElement, ParserOptions parserOptions)
{
var hasValue = false;
var n = segmentObject.NumFields();
@@ -347,46 +363,66 @@ public virtual bool Encode(ISegment segmentObject, XmlElement segmentElement)
var reps = segmentObject.GetField(i);
for (var j = 0; j < reps.Length; j++)
{
- var newNode = segmentElement.OwnerDocument.CreateElement(name);
- var componentHasValue = Encode(reps[j], newNode);
- if (componentHasValue)
+ var newNode = segmentElement.OwnerDocument.CreateElement(name, NameSpace);
+
+ var componentHasValue = Encode(reps[j], newNode, parserOptions);
+ if (!componentHasValue)
{
- try
- {
- segmentElement.AppendChild(newNode);
- }
- catch (Exception e)
- {
- throw new HL7Exception("DOMException encoding Segment: ", ErrorCode.APPLICATION_INTERNAL_ERROR, e);
- }
+ continue;
+ }
- hasValue = true;
+ try
+ {
+ segmentElement.AppendChild(newNode);
}
+ catch (Exception e)
+ {
+ throw new HL7Exception($"DOMException encoding Segment: ", ErrorCode.APPLICATION_INTERNAL_ERROR, e);
+ }
+
+ hasValue = true;
}
}
return hasValue;
}
- /// Populates the given Type object with data from the given XML Element.
+ ///
+ /// Populates the given object with data from the given .
+ ///
+ /// The to parse into.
+ /// The to be parsed.
+ /// if the data did not match the expected type rules.
public virtual void Parse(IType datatypeObject, XmlElement datatypeElement)
+ {
+ Parse(datatypeObject, datatypeElement, DefaultParserOptions);
+ }
+
+ ///
+ /// Populates the given object with data from the given .
+ ///
+ /// The to parse into.
+ /// The to be parsed.
+ /// Contains configuration that will be applied when parsing.
+ /// if the data did not match the expected type rules.
+ public virtual void Parse(IType datatypeObject, XmlElement datatypeElement, ParserOptions parserOptions)
{
// TODO: consider replacing with a switch statement
- if (datatypeObject is Varies)
+ if (datatypeObject is Varies varies)
{
- ParseVaries((Varies)datatypeObject, datatypeElement);
+ ParseVaries(varies, datatypeElement, parserOptions);
}
- else if (datatypeObject is IPrimitive)
+ else if (datatypeObject is IPrimitive primitive)
{
- ParsePrimitive((IPrimitive)datatypeObject, datatypeElement);
+ ParsePrimitive(primitive, datatypeElement, parserOptions);
}
- else if (datatypeObject is IComposite)
+ else if (datatypeObject is IComposite composite)
{
- ParseComposite((IComposite)datatypeObject, datatypeElement);
+ ParseComposite(composite, datatypeElement, parserOptions);
}
}
- /// Returns a minimal amount of data from a message string, including only the
+ /// Returns a minimal amount of data from a message string, including only the
/// data needed to send a response to the remote system. This includes the
/// following fields:
///
@@ -399,7 +435,7 @@ public virtual void Parse(IType datatypeObject, XmlElement datatypeElement)
/// (so the Message object is unavailable) but an error message must be sent
/// back to the remote system including some of the information in the inbound
/// message. This method parses only that required information, hopefully
- /// avoiding the condition that caused the original error.
+ /// avoiding the condition that caused the original error.
///
public override ISegment GetCriticalResponseData(string message)
{
@@ -410,9 +446,9 @@ public override ISegment GetCriticalResponseData(string message)
Terser.Set(criticalData, 2, 0, 1, 1, ParseLeaf(message, "MSH.2", 0));
Terser.Set(criticalData, 10, 0, 1, 1, ParseLeaf(message, "MSH.10", 0));
var procID = ParseLeaf(message, "MSH.11", 0);
- if (procID == null || procID.Length == 0)
+ if (string.IsNullOrEmpty(procID))
{
- procID = ParseLeaf(message, "PT.1", message.IndexOf("MSH.11"));
+ procID = ParseLeaf(message, "PT.1", message.IndexOf("MSH.11", StringComparison.Ordinal));
// this field is a composite in later versions
}
@@ -445,12 +481,13 @@ public override string GetAckID(string message)
}
}
- public override string GetVersion(string message)
+ ///
+ public override string GetVersion(string message, ParserOptions parserOptions)
{
var version = ParseLeaf(message, "MSH.12", 0);
if (version == null || version.Trim().Length == 0)
{
- version = ParseLeaf(message, "VID.1", message.IndexOf("MSH.12"));
+ version = ParseLeaf(message, "VID.1", message.IndexOf("MSH.12", StringComparison.Ordinal));
}
return version;
@@ -461,13 +498,39 @@ public override string GetVersion(string message)
///
/// The target node.
///
- /// true if whitespaces should not be removed from node content; otherwise, false.
+ /// if whitespaces should not be removed from node content; otherwise, .
///
protected internal virtual bool KeepAsOriginal(XmlNode node)
{
- return
- node.Name != null
- && concatKeepAsOriginalNodes.IndexOf(node.Name) != -1;
+ return KeepAsOriginal(node, DefaultParserOptions);
+ }
+
+ ///
+ /// Checks if a node content should be kept as original (ie.: whitespaces won't be removed).
+ ///
+ /// The target node.
+ /// Contains configuration that will be applied when parsing.
+ ///
+ /// if whitespaces should not be removed from node content; otherwise, .
+ ///
+ protected internal virtual bool KeepAsOriginal(XmlNode node, ParserOptions parserOptions)
+ {
+ return parserOptions.DisableWhitespaceTrimmingOnAllXmlNodes
+ || parserOptions.XmlNodeNamesToDisableWhitespaceTrimming.Contains(node.LocalName)
+ || KeepAsOriginalNodes.Contains(node.LocalName, StringComparer.Ordinal);
+ }
+
+ ///
+ /// Validates the namespace.
+ ///
+ /// Namespace to assert.
+ /// If provided namespace is not valid.
+ protected internal virtual void AssertNamespaceUri(string @namespace)
+ {
+ if (!NameSpace.Equals(@namespace, StringComparison.Ordinal))
+ {
+ throw new HL7Exception($"Namespace URI must be {NameSpace}");
+ }
}
///
@@ -475,16 +538,16 @@ protected internal virtual bool KeepAsOriginal(XmlNode node)
/// This includes leading and trailing whitespace, and repeated space characters. Carriage returns,
/// line feeds, and tabs are replaced with spaces.
///
- protected internal virtual string RemoveWhitespace(string s)
+ protected internal virtual string RemoveWhitespace(string input)
{
- s = s.Replace('\r', ' ');
- s = s.Replace('\n', ' ');
- s = s.Replace('\t', ' ');
+ input = input.Replace('\r', ' ')
+ .Replace('\n', ' ')
+ .Replace('\t', ' ');
var repeatedSpacesExist = true;
while (repeatedSpacesExist)
{
- var loc = s.IndexOf(" ");
+ var loc = input.IndexOf(" ", StringComparison.Ordinal);
if (loc < 0)
{
repeatedSpacesExist = false;
@@ -492,14 +555,14 @@ protected internal virtual string RemoveWhitespace(string s)
else
{
var buf = new StringBuilder();
- buf.Append(s.Substring(0, loc - 0));
+ buf.Append(input.Substring(0, loc - 0));
buf.Append(" ");
- buf.Append(s.Substring(loc + 2));
- s = buf.ToString();
+ buf.Append(input.Substring(loc + 2));
+ input = buf.ToString();
}
}
- return s.Trim();
+ return input.Trim();
}
///
@@ -510,13 +573,13 @@ protected internal virtual string RemoveWhitespace(string s)
///
protected internal override IMessage DoParse(string message, string version, ParserOptions parserOptions)
{
- IMessage m = null;
+ IMessage m;
// parse message string into a DOM document
try
{
var doc = new XmlDocument();
- doc.Load(new StringReader(message));
+ doc.LoadXml(message);
m = ParseDocument(doc, version, parserOptions);
}
@@ -537,59 +600,84 @@ protected internal override IMessage DoParse(string message, string version, Par
///
/// Thrown if the data fields in the message do not permit encoding (e.g. required fields are null).
/// Thrown if the requested encoding is not supported by this parser.
- protected internal override string DoEncode(IMessage source, string encoding)
+ protected internal override string DoEncode(IMessage source, string encoding, ParserOptions parserOptions)
{
if (!SupportsEncoding("XML"))
{
throw new EncodingNotSupportedException("XMLParser supports only XML encoding");
}
- return Encode(source);
+ return Encode(source, parserOptions);
}
///
/// Formats a Message object into an HL7 message string using this parser's
/// default encoding (XML encoding). This method calls the abstract method.
- /// encodeDocument(...) in order to obtain XML Document object
+ /// in order to obtain object
/// representation of the Message, then serializes it to a String.
///
/// Thrown if the data fields in the message do not permit encoding (e.g. required fields are null).
- protected internal override string DoEncode(IMessage source)
+ protected internal override string DoEncode(IMessage source, ParserOptions parserOptions)
{
- if (source is GenericMessage)
+ Log.Info("XML-Encoding a GenericMessage is not covered by the specification.");
+
+ var doc = EncodeDocument(source, parserOptions);
+
+ var stringBuilder = new StringBuilder();
+ var utf8StringWriter = new StringWriterWithEncoding(stringBuilder, Encoding.UTF8);
+ var xmlWriterSettings = new XmlWriterSettings { Indent = parserOptions.PrettyPrintEncodedXml, CloseOutput = true };
+
+ using (var writer = XmlWriter.Create(utf8StringWriter, xmlWriterSettings))
{
- throw new HL7Exception(
- "Can't XML-encode a GenericMessage. Message must have a recognized structure.");
+ doc.WriteTo(writer);
}
- var doc = EncodeDocument(source);
- doc.DocumentElement.SetAttribute("xmlns", "urn:hl7-org:v2xml");
-
- return doc.OuterXml;
+ return stringBuilder.ToString();
}
///
- /// Attempts to retrieve the value of a leaf tag without using DOM or SAX.
- /// This method searches the given message string for the given tag name, and returns
- /// everything after the given tag and before the start of the next tag. Whitespace
- /// is stripped. This is intended only for lead nodes, as the value is considered to
+ /// Attempts to retrieve the value of a leaf tag without using DOM or SAX.
+ /// This method searches the given message string for the given tag name, and returns
+ /// everything after the given tag and before the start of the next tag.
+ /// Whitespace is stripped.
+ ///
+ ///
+ /// This is intended only for lead nodes, as the value is considered to
/// end at the start of the next tag, regardless of whether it is the matching end
/// tag or some other nested tag.
- ///
+ ///
/// a string message in XML form.
/// the name of the XML tag, e.g. "MSA.2".
/// the character location at which to start searching.
/// Thrown if the tag can not be found.
protected internal virtual string ParseLeaf(string message, string tagName, int startAt)
{
- var tagStart = message.IndexOf("<" + tagName, startAt);
+ var prefix = string.Empty;
+ var matches = NameSpaceRegex.Match(message);
+ if (matches.Success)
+ {
+ var nameSpace = matches.Groups[1].Value;
+ if (!string.IsNullOrEmpty(nameSpace))
+ {
+ prefix = nameSpace.Substring(1) + ":";
+ }
+ }
+
+ var tagStart = message.IndexOf($"<{prefix}{tagName}", startAt, StringComparison.Ordinal);
if (tagStart < 0)
{
- tagStart = message.IndexOf("<" + tagName.ToUpper(), startAt);
+ tagStart = message.IndexOf($"<{prefix}{tagName.ToUpperInvariant()}", startAt, StringComparison.Ordinal);
}
- var valStart = message.IndexOf(">", tagStart) + 1;
- var valEnd = message.IndexOf("<", valStart);
+ if (tagStart < 0)
+ {
+ throw new HL7Exception(
+ $"Couldn't find {tagName} in message beginning: {message.Substring(0, Math.Min(150, message.Length) - 0)}",
+ ErrorCode.REQUIRED_FIELD_MISSING);
+ }
+
+ var valStart = message.IndexOf(">", tagStart, StringComparison.Ordinal) + 1;
+ var valEnd = message.IndexOf("<", valStart, StringComparison.Ordinal);
string value;
if (tagStart >= 0 && valEnd >= valStart)
@@ -603,47 +691,20 @@ protected internal virtual string ParseLeaf(string message, string tagName, int
ErrorCode.REQUIRED_FIELD_MISSING);
}
- return value;
- }
+ // Escape codes, as defined at http://hdf.ncsa.uiuc.edu/HDF5/XML/xml_escape_chars.htm
+ value = Regex.Replace(value, """, "\"");
+ value = Regex.Replace(value, "'", "'");
+ value = Regex.Replace(value, "&", "&");
+ value = Regex.Replace(value, "<", "<");
+ value = Regex.Replace(value, ">", ">");
- /// Populates a Composite type by looping through it's children, finding corresponding
- /// Elements among the children of the given Element, and calling parse(Type, Element) for
- /// each.
- ///
- private void ParseComposite(IComposite datatypeObject, XmlElement datatypeElement)
- {
- if (datatypeObject is GenericComposite)
- {
- // elements won't be named GenericComposite.x
- var children = datatypeElement.ChildNodes;
- var compNum = 0;
- for (var i = 0; i < children.Count; i++)
- {
- if (Convert.ToInt16(children.Item(i).NodeType) == (short)XmlNodeType.Element)
- {
- Parse(datatypeObject[compNum], (XmlElement)children.Item(i));
- compNum++;
- }
- }
- }
- else
- {
- var children = datatypeObject.Components;
- for (var i = 0; i < children.Length; i++)
- {
- var matchingElements = datatypeElement.GetElementsByTagName(MakeElementName(datatypeObject, i + 1));
- if (matchingElements.Count > 0)
- {
- Parse(children[i], (XmlElement)matchingElements.Item(0)); // components don't repeat - use 1st
- }
- }
- }
+ return value;
}
///
/// Returns the expected XML element name for the given child of the given Segment.
///
- private string MakeElementName(ISegment s, int child)
+ private static string MakeElementName(ISegment s, int child)
{
return $"{s.GetStructureName()}.{child}";
}
@@ -651,26 +712,28 @@ private string MakeElementName(ISegment s, int child)
///
/// Returns the expected XML element name for the given child of the given Composite.
///
- private string MakeElementName(IComposite composite, int child)
+ private static string MakeElementName(IComposite composite, int child)
{
return $"{composite.TypeName}.{child}";
}
///
- /// Populates the given Element with data from the given Type, by inserting
- /// Elements corresponding to the Type's components and values. Returns true if
- /// the given type contains a value (i.e. for Primitives, if getValue() doesn't
- /// return null, and for Composites, if at least one underlying Primitive doesn't
- /// return null).
+ /// Populates the given with data from the given , by inserting
+ /// XmlElements corresponding to the Type's components and values.
///
- private bool Encode(IType datatypeObject, XmlElement datatypeElement)
+ ///
+ /// if the given type contains a value (i.e. for Primitives, if
+ /// doesn't return null, and for Composites, if at least one underlying
+ /// Primitive doesn't return null).
+ ///
+ private bool Encode(IType datatypeObject, XmlElement datatypeElement, ParserOptions parserOptions)
{
var hasData = false;
// TODO: consider using a switch statement
if (datatypeObject is Varies)
{
- hasData = EncodeVaries((Varies)datatypeObject, datatypeElement);
+ hasData = EncodeVaries((Varies)datatypeObject, datatypeElement, parserOptions);
}
else if (datatypeObject is IPrimitive)
{
@@ -678,67 +741,125 @@ private bool Encode(IType datatypeObject, XmlElement datatypeElement)
}
else if (datatypeObject is IComposite)
{
- hasData = EncodeComposite((IComposite)datatypeObject, datatypeElement);
+ hasData = EncodeComposite((IComposite)datatypeObject, datatypeElement, parserOptions);
}
return hasData;
}
- /// Encodes a Varies type by extracting it's data field and encoding that. Returns true
- /// if the data field (or one of its components) contains a value.
+ ///
+ /// Encodes a Varies type by extracting it's data field and encoding that.
///
- private bool EncodeVaries(Varies datatypeObject, XmlElement datatypeElement)
+ /// if the data field (or one of its components) contains a value.
+ private bool EncodeVaries(Varies datatypeObject, XmlElement datatypeElement, ParserOptions parserOptions)
{
var hasData = false;
- if (datatypeObject.Data != null)
+ if (datatypeObject.Data is not null)
{
- hasData = Encode(datatypeObject.Data, datatypeElement);
+ hasData = Encode(datatypeObject.Data, datatypeElement, parserOptions);
}
return hasData;
}
- /// Encodes a Primitive in XML by adding it's value as a child of the given Element.
- /// Returns true if the given Primitive contains a value.
+ ///
+ /// Encodes a Primitive in XML by adding it's value as a child of the given Element.
///
+ /// if the given Primitive contains a value.
private bool EncodePrimitive(IPrimitive datatypeObject, XmlElement datatypeElement)
{
- var hasValue = false;
- if (datatypeObject.Value != null && !datatypeObject.Value.Equals(string.Empty))
- {
- hasValue = true;
- }
+ var value = datatypeObject.Value;
+ var hasValue = !string.IsNullOrEmpty(value);
- var t = datatypeElement.OwnerDocument.CreateTextNode(datatypeObject.Value);
if (hasValue)
{
try
{
- datatypeElement.AppendChild(t);
+ var encoding = EncodingCharacters.FromMessage(datatypeObject.Message);
+ int pos;
+ var oldPos = 0;
+ var escaping = false;
+
+ // Find next escape character
+ while ((pos = value.IndexOf(encoding.EscapeCharacter, oldPos)) >= 0)
+ {
+ // string until next escape character
+ var v = value.Substring(oldPos, pos - oldPos);
+ if (!escaping)
+ {
+ // currently in "text mode", so create textnode from it
+ if (v.Length > 0)
+ {
+ datatypeElement.AppendChild(datatypeElement.OwnerDocument.CreateTextNode(v));
+ }
+
+ escaping = true;
+ }
+ else
+ {
+ if (v.StartsWith(".", StringComparison.Ordinal)
+ || "H".Equals(v, StringComparison.Ordinal)
+ || "N".Equals(v, StringComparison.Ordinal))
+ {
+ // currently in "escape mode", so create escape element from it
+ var escape = datatypeElement.OwnerDocument.CreateElement(EscapeNodeName, NameSpace);
+ escape.SetAttribute(EscapeAttrName, v);
+ datatypeElement.AppendChild(escape);
+ escaping = false;
+ }
+ else
+ {
+ // no proper escape sequence, assume text
+ datatypeElement.AppendChild(
+ datatypeElement.OwnerDocument
+ .CreateTextNode(encoding.EscapeCharacter + v));
+ }
+ }
+
+ oldPos = pos + 1;
+ }
+
+ // create text from the remainder
+ if (oldPos <= value.Length)
+ {
+ var stringBuilder = new StringBuilder();
+
+ // If we are in escaping mode, there appears no closing escape character,
+ // so we treat the string as text
+ if (escaping)
+ {
+ stringBuilder.Append(encoding.EscapeCharacter);
+ }
+
+ stringBuilder.Append(value.Substring(oldPos));
+ datatypeElement.AppendChild(
+ datatypeElement.OwnerDocument.CreateTextNode(stringBuilder.ToString()));
+ }
}
- catch (Exception e)
+ catch (Exception ex)
{
- throw new DataTypeException("DOMException encoding Primitive: ", e);
+ throw new DataTypeException("Exception encoding Primitive: ", ex);
}
}
return hasValue;
}
- /// Encodes a Composite in XML by looping through it's components, creating new
+ ///
+ /// Encodes a Composite in XML by looping through it's components, creating new
/// children for each of them (with the appropriate names) and populating them by
- /// calling encode(Type, Element) using these children. Returns true if at least
- /// one component contains a value.
+ /// calling using these children.
///
- private bool EncodeComposite(IComposite datatypeObject, XmlElement datatypeElement)
+ /// if at least one component contains a value.
+ private bool EncodeComposite(IComposite datatypeObject, XmlElement datatypeElement, ParserOptions parserOptions)
{
var components = datatypeObject.Components;
var hasValue = false;
for (var i = 0; i < components.Length; i++)
{
var name = MakeElementName(datatypeObject, i + 1);
- var newNode = datatypeElement.OwnerDocument.CreateElement(name);
- var componentHasValue = Encode(components[i], newNode);
+ var newNode = datatypeElement.OwnerDocument.CreateElement(name, NameSpace);
+ var componentHasValue = Encode(components[i], newNode, parserOptions);
if (componentHasValue)
{
try
@@ -757,21 +878,51 @@ private bool EncodeComposite(IComposite datatypeObject, XmlElement datatypeEleme
return hasValue;
}
- private void ParseReps(ISegment segmentObject, XmlElement segmentElement, string fieldName, int fieldNum)
+ private void ParseReps(ISegment segmentObject, XmlElement segmentElement, string fieldName, int fieldNum, ParserOptions parserOptions)
{
- var reps = segmentElement.GetElementsByTagName(fieldName);
+ var reps = segmentElement.GetElementsByTagName(fieldName, NameSpace);
for (var i = 0; i < reps.Count; i++)
{
- Parse(segmentObject.GetField(fieldNum, i), (XmlElement)reps.Item(i));
+ Parse(segmentObject.GetField(fieldNum, i), (XmlElement)reps[i], parserOptions);
}
}
///
- /// Parses an XML element into a Varies by determining whether the element is primitive or
- /// composite, calling setData() on the Varies with a new generic primitive or composite as appropriate,
- /// and then calling parse again with the new Type object.
+ /// Returns if the provided has any children which are
+ /// are also of type .
///
- private void ParseVaries(Varies datatypeObject, XmlElement datatypeElement)
+ /// Element to test.
+ ///
+ private bool HasChildElement(XmlElement element)
+ {
+ var children = element.ChildNodes;
+ var hasElement = false;
+ var i = 0;
+ while (i < children.Count && !hasElement)
+ {
+ if (children[i].NodeType == XmlNodeType.Element
+ && !EscapeNodeName.Equals(children[i].Name, StringComparison.Ordinal))
+ {
+ hasElement = true;
+ }
+
+ i++;
+ }
+
+ return hasElement;
+ }
+
+ ///
+ /// Parses an into a by determining whether the element is
+ /// or , assigning the Varies.Data
+ /// with a new or as appropriate, and then
+ /// calling parse again with this newly assigned Varies.Data.
+ ///
+ /// The to parse into.
+ /// The to be parsed.
+ /// Contains configuration that will be applied when parsing.
+ /// if the data did not match the expected type rules.
+ private void ParseVaries(Varies datatypeObject, XmlElement datatypeElement, ParserOptions parserOptions)
{
// figure out what data type it holds
if (!HasChildElement(datatypeElement))
@@ -785,71 +936,167 @@ private void ParseVaries(Varies datatypeObject, XmlElement datatypeElement)
datatypeObject.Data = new GenericComposite(datatypeObject.Message);
}
- Parse(datatypeObject.Data, datatypeElement);
+ Parse(datatypeObject.Data, datatypeElement, parserOptions);
}
- /// Returns true if any of the given element's children are elements.
- private bool HasChildElement(XmlElement e)
+ ///
+ /// Populates the given object with data from the given .
+ ///
+ /// The to parse into.
+ /// The to be parsed.
+ /// Contains configuration that will be applied when parsing.
+ ///
+ /// If any of the XmlElement.ChildNodes of type
+ /// which should be escaped from
+ /// have an invalid namespace.
+ ///
+ private void ParsePrimitive(IPrimitive datatypeObject, XmlElement datatypeElement, ParserOptions parserOptions)
{
- var children = e.ChildNodes;
- var hasElement = false;
- var c = 0;
- while (c < children.Count && !hasElement)
+ var children = datatypeElement.ChildNodes;
+ var builder = new StringBuilder();
+
+ for (var childIndex = 0; childIndex < children.Count; childIndex++)
{
- if (Convert.ToInt16(children.Item(c).NodeType) == (short)XmlNodeType.Element)
+ var child = children[childIndex];
+ try
{
- hasElement = true;
- }
+ if (child.NodeType == XmlNodeType.Text)
+ {
+ var value = child.Value;
+ if (!string.IsNullOrEmpty(value))
+ {
+ var valueToAppend = KeepAsOriginal(child.ParentNode, parserOptions)
+ ? value
+ : RemoveWhitespace(value);
- c++;
+ builder.Append(valueToAppend);
+ }
+ }
+ else if (child.NodeType == XmlNodeType.Element && EscapeNodeName.Equals(child.LocalName))
+ {
+ AssertNamespaceUri(child.NamespaceURI);
+
+ var encoding = EncodingCharacters.FromMessage(datatypeObject.Message);
+ var element = (XmlElement)child;
+ var attribute = element.GetAttribute(EscapeAttrName).Trim();
+
+ if (!string.IsNullOrEmpty(attribute))
+ {
+ builder.Append(encoding.EscapeCharacter)
+ .Append(attribute)
+ .Append(encoding.EscapeCharacter);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Error parsing primitive value from TEXT_NODE", ex);
+ }
}
- return hasElement;
+ datatypeObject.Value = builder.ToString();
}
- /// Parses a primitive type by filling it with text child, if any.
- private void ParsePrimitive(IPrimitive datatypeObject, XmlElement datatypeElement)
+ ///
+ /// Populates the provided by looping through it's , finding corresponding
+ /// XmlElements among the children of the given , and
+ /// calling for each.
+ ///
+ /// The to parse into.
+ /// The to be parsed.
+ /// Contains configuration that will be applied when parsing.
+ ///
+ /// If any of the XmlElement.ChildNodes of type
+ /// which should be escaped from
+ /// have an invalid namespace.
+ ///
+ private void ParseComposite(IComposite datatypeObject, XmlElement datatypeElement, ParserOptions parserOptions)
{
- var children = datatypeElement.ChildNodes;
- var c = 0;
- var full = false;
- while (c < children.Count && !full)
+ if (datatypeObject is GenericComposite)
{
- var child = children.Item(c++);
- if (Convert.ToInt16(child.NodeType) == (short)XmlNodeType.Text)
+ // elements won't be named GenericComposite.x
+ var children = datatypeElement.ChildNodes;
+ var componentIndex = 0;
+ for (var i = 0; i < children.Count; i++)
{
- try
+ if (children[i].NodeType != XmlNodeType.Element)
{
- if (child.Value != null && !child.Value.Equals(string.Empty))
- {
- if (KeepAsOriginal(child.ParentNode))
- {
- datatypeObject.Value = child.Value;
- }
- else
- {
- datatypeObject.Value = RemoveWhitespace(child.Value);
- }
- }
+ continue;
}
- catch (Exception e)
+
+ var nextElement = (XmlElement)children[i];
+ AssertNamespaceUri(nextElement.NamespaceURI);
+
+ var localName = nextElement.LocalName;
+ var dotPosition = localName.IndexOf(".", StringComparison.Ordinal);
+ if (dotPosition > -1)
+ {
+ componentIndex = int.Parse(localName.Substring(dotPosition + 1)) - 1;
+ }
+ else
+ {
+ Log.Debug(
+ $"Datatype element {datatypeElement.LocalName} doesn't have a valid numbered name, using default index of {componentIndex}");
+ }
+
+ var nextComponent = datatypeObject[componentIndex];
+
+ Parse(nextComponent, nextElement, parserOptions);
+ componentIndex++;
+ }
+ }
+ else
+ {
+ var children = datatypeObject.Components;
+ for (var i = 0; i < children.Length; i++)
+ {
+ var matchingElements = datatypeElement.GetElementsByTagName(MakeElementName(datatypeObject, i + 1), NameSpace);
+ if (matchingElements.Count > 0)
+ {
+ // components don't repeat - use 1st
+ Parse(children[i], (XmlElement)matchingElements[0], parserOptions);
+ }
+ }
+
+ var nextExtraComponent = 0;
+ bool foundExtraComponent;
+ do
+ {
+ foundExtraComponent = false;
+ var matchingElements =
+ datatypeElement.GetElementsByTagName(
+ MakeElementName(datatypeObject, children.Length + nextExtraComponent + 1), NameSpace);
+
+ if (matchingElements.Count > 0)
{
- Log.Error("Error parsing primitive value from TEXT_NODE", e);
+ var extraComponent = datatypeObject.ExtraComponents.GetComponent(nextExtraComponent);
+ Parse(extraComponent, (XmlElement)matchingElements[0], parserOptions);
+ foundExtraComponent = true;
}
- full = true;
+ nextExtraComponent++;
}
+ while (foundExtraComponent);
}
}
private class AnonymousClassXMLParser : XMLParser
{
+ public AnonymousClassXMLParser()
+ {
+ }
+
+ public AnonymousClassXMLParser(IModelClassFactory factory)
+ : base(factory)
+ {
+ }
+
public override IMessage ParseDocument(XmlDocument xmlMessage, string version, ParserOptions parserOptions)
{
return null;
}
- public override XmlDocument EncodeDocument(IMessage source)
+ public override XmlDocument EncodeDocument(IMessage source, ParserOptions parserOptions)
{
return null;
}
@@ -862,6 +1109,22 @@ public override string GetVersion(string message)
{
return null;
}
+
+ protected internal override string DoEncode(IMessage source, ParserOptions parserOptions)
+ {
+ return null;
+ }
+ }
+
+ private sealed class StringWriterWithEncoding : StringWriter
+ {
+ public StringWriterWithEncoding(StringBuilder builder, Encoding encoding)
+ : base(builder)
+ {
+ this.Encoding = encoding;
+ }
+
+ public override Encoding Encoding { get; }
}
}
}
\ No newline at end of file
diff --git a/src/NHapi.Base/PreParser/Er7.cs b/src/NHapi.Base/PreParser/Er7.cs
index ace4f5347..41a865ef4 100644
--- a/src/NHapi.Base/PreParser/Er7.cs
+++ b/src/NHapi.Base/PreParser/Er7.cs
@@ -234,10 +234,6 @@ private static void ParseSegmentWhole(
private class Er7SegmentHandler
{
- internal int SegmentRepetitionIndex;
- internal string SegmentId;
- internal IList MessageMasks;
-
private readonly EncodingCharacters encodingCharacters;
private readonly IDictionary props;
@@ -249,6 +245,12 @@ public Er7SegmentHandler(EncodingCharacters encodingCharacters, IDictionary 4;
+ internal int SegmentRepetitionIndex { get; set; }
+
+ internal string SegmentId { get; set; }
+
+ internal IList MessageMasks { get; set; }
+
public char Delimiter(int level) => level switch
{
0 => this.encodingCharacters.FieldSeparator,
diff --git a/src/NHapi.Base/SupportClass.cs b/src/NHapi.Base/SupportClass.cs
index f545430b6..735e66612 100644
--- a/src/NHapi.Base/SupportClass.cs
+++ b/src/NHapi.Base/SupportClass.cs
@@ -1460,13 +1460,13 @@ private void DoParsing()
for (int i = 0; i < reader.AttributeCount; i++)
{
reader.MoveToAttribute(i);
- string prefixName = (reader.Name.IndexOf(":") > 0)
- ? reader.Name.Substring(reader.Name.IndexOf(":") + 1, reader.Name.Length - reader.Name.IndexOf(":") - 1)
+ string prefixName = (reader.Name.IndexOf(":", StringComparison.Ordinal) > 0)
+ ? reader.Name.Substring(reader.Name.IndexOf(":", StringComparison.Ordinal) + 1, reader.Name.Length - reader.Name.IndexOf(":") - 1)
: string.Empty;
- string prefix = (reader.Name.IndexOf(":") > 0)
- ? reader.Name.Substring(0, reader.Name.IndexOf(":"))
+ string prefix = (reader.Name.IndexOf(":", StringComparison.Ordinal) > 0)
+ ? reader.Name.Substring(0, reader.Name.IndexOf(":", StringComparison.Ordinal))
: reader.Name;
- bool IsXmlns = prefix.ToLower().Equals("xmlns");
+ bool IsXmlns = prefix.ToLower().Equals("xmlns", StringComparison.Ordinal);
if (namespaceAllowed)
{
if (!IsXmlns)
diff --git a/src/NHapi.Base/Util/Terser.cs b/src/NHapi.Base/Util/Terser.cs
index 6175cab9b..6505df521 100644
--- a/src/NHapi.Base/Util/Terser.cs
+++ b/src/NHapi.Base/Util/Terser.cs
@@ -148,10 +148,10 @@ public static string Get(ISegment segment, int field, int rep, int component, in
}
/// Sets the string value of the Primitive at the given location.
- public static void Set(ISegment segment, int field, int rep, int component, int subcomponent, string value_Renamed)
+ public static void Set(ISegment segment, int field, int rep, int component, int subcomponent, string value)
{
var prim = GetPrimitive(segment, field, rep, component, subcomponent);
- prim.Value = value_Renamed;
+ prim.Value = value;
}
[Obsolete("This method has been replaced by 'GetPrimitive'.")]
@@ -372,7 +372,7 @@ public virtual ISegment GetSegment(string segSpec)
{
ISegment segment = null;
- if (segSpec.Substring(0, 1 - 0).Equals("/"))
+ if (segSpec.StartsWith("/", StringComparison.Ordinal))
{
Finder.Reset();
}
@@ -522,7 +522,7 @@ private static int NumStandardComponents(IType type)
/// Gets path information from a path spec.
private PathSpec ParsePathSpec(string spec)
{
- var ps = new PathSpec(this);
+ var ps = new PathSpec();
if (spec.StartsWith(".", StringComparison.Ordinal))
{
@@ -564,13 +564,6 @@ private PathSpec ParsePathSpec(string spec)
/// Struct for information about a step in a segment path.
private class PathSpec
{
- public PathSpec(Terser enclosingInstance)
- {
- EnclosingInstance = enclosingInstance;
- }
-
- public Terser EnclosingInstance { get; }
-
public string Pattern { get; set; }
public bool IsGroup { get; set; }
diff --git a/src/NHapi.SourceGeneration/Generators/DataTypeGenerator.cs b/src/NHapi.SourceGeneration/Generators/DataTypeGenerator.cs
index ead21d955..a438d62ed 100644
--- a/src/NHapi.SourceGeneration/Generators/DataTypeGenerator.cs
+++ b/src/NHapi.SourceGeneration/Generators/DataTypeGenerator.cs
@@ -333,7 +333,7 @@ private static string MakePrimitive(string datatype, string description, string
source.Append("\t///\r\n");
source.Append("\tpublic string getVersion() {\r\n");
source.Append("\t return \"");
- if (version.IndexOf("UCH") > -1)
+ if (version.IndexOf("UCH", StringComparison.Ordinal) > -1)
{
source.Append("2.3");
}
diff --git a/src/NHapi.SourceGeneration/NHapi.SourceGeneration.csproj b/src/NHapi.SourceGeneration/NHapi.SourceGeneration.csproj
index cc566b0c9..bb46a4174 100644
--- a/src/NHapi.SourceGeneration/NHapi.SourceGeneration.csproj
+++ b/src/NHapi.SourceGeneration/NHapi.SourceGeneration.csproj
@@ -15,8 +15,8 @@
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/tests/NHapi.NUnit.SourceGeneration/NHapi.NUnit.SourceGeneration.csproj b/tests/NHapi.NUnit.SourceGeneration/NHapi.NUnit.SourceGeneration.csproj
index 5977b12df..86c570677 100644
--- a/tests/NHapi.NUnit.SourceGeneration/NHapi.NUnit.SourceGeneration.csproj
+++ b/tests/NHapi.NUnit.SourceGeneration/NHapi.NUnit.SourceGeneration.csproj
@@ -26,7 +26,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/tests/NHapi.NUnit/CustomZSegmentTest.cs b/tests/NHapi.NUnit/CustomZSegmentTest.cs
index 9ce824a27..fbf8b9046 100644
--- a/tests/NHapi.NUnit/CustomZSegmentTest.cs
+++ b/tests/NHapi.NUnit/CustomZSegmentTest.cs
@@ -4,7 +4,6 @@ namespace NHapi.NUnit
using global::NUnit.Framework;
- using NHapi.Base.Model;
using NHapi.Base.Parser;
using NHapi.Model.V22_ZSegments;
using NHapi.Model.V22_ZSegments.Message;
@@ -52,9 +51,9 @@ public void ParseADT_A08()
Assert.IsNotNull(m);
- Console.WriteLine("Type: " + m.GetType());
+ Console.WriteLine($"Type: {m.GetType()}");
- var adtA08 = m as ADT_A08;
+ var adtA08 = (ADT_A08)m;
// verify some Z segment data
Assert.AreEqual("45789", adtA08.ZIN.AccidentData.Id.Value);
diff --git a/tests/NHapi.NUnit/NHapi.NUnit.csproj b/tests/NHapi.NUnit/NHapi.NUnit.csproj
index 843c2156f..cfc6e26ea 100644
--- a/tests/NHapi.NUnit/NHapi.NUnit.csproj
+++ b/tests/NHapi.NUnit/NHapi.NUnit.csproj
@@ -5,27 +5,24 @@
false
-
-
-
-
-
+
+ ..\..\NHapi.snk
+ true
+
-
- PreserveNewest
-
-
- PreserveNewest
-
-
+
+
PreserveNewest
-
-
+
+
+
+
+
PreserveNewest
@@ -63,18 +60,13 @@
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
- PreserveNewest
-
-
-
@@ -84,4 +76,4 @@
-
+
\ No newline at end of file
diff --git a/tests/NHapi.NUnit/Parser/LegacyPipeParserV231Tests.cs b/tests/NHapi.NUnit/Parser/LegacyPipeParserV231Tests.cs
index b66c3367b..32de83407 100644
--- a/tests/NHapi.NUnit/Parser/LegacyPipeParserV231Tests.cs
+++ b/tests/NHapi.NUnit/Parser/LegacyPipeParserV231Tests.cs
@@ -73,7 +73,7 @@ public void ParseORMo01ToXml()
var ormo01 = m as ORM_O01;
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(ormo01);
@@ -100,7 +100,7 @@ public void ParseORRo02ToXml()
var msg = m as ORR_O02;
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(msg);
@@ -145,7 +145,7 @@ public void ParseORUr01LongToXml()
var msg = m as ORU_R01;
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(msg);
@@ -199,7 +199,7 @@ public void ParseORFR04ToXML()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
@@ -231,7 +231,7 @@ public void ParseORMwithOBXToXML()
Assert.IsNotNull(msgObj);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(msgObj);
@@ -264,7 +264,7 @@ public void ParseORMwithCompleteOBXToXML()
Assert.IsNotNull(msgObj);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(msgObj);
@@ -277,7 +277,7 @@ public void ParseXMLToHL7()
{
var message = GetQRYR02XML();
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var m = xmlParser.Parse(message);
var qryR02 = m as QRY_R02;
@@ -312,12 +312,12 @@ public void ParseORFR04ToXmlNoOCR()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
Assert.IsNotNull(recoveredMessage);
- Assert.IsFalse(recoveredMessage.IndexOf("ORC") > -1, "Returned Message added ORC segment.");
+ Assert.IsFalse(recoveredMessage.IndexOf("ORC", StringComparison.Ordinal) > -1, "Returned Message added ORC segment.");
}
[Test]
@@ -340,12 +340,12 @@ public void ParseORFR04ToXmlNoNTE()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
Assert.IsNotNull(recoveredMessage);
- Assert.IsFalse(recoveredMessage.IndexOf("NTE") > -1, "Returned Message added ORC segment.");
+ Assert.IsFalse(recoveredMessage.IndexOf("NTE", StringComparison.Ordinal) > -1, "Returned Message added ORC segment.");
}
[Test]
@@ -436,12 +436,12 @@ public void ParseORFR04FromDHTest()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
Assert.IsNotNull(recoveredMessage);
- Assert.IsFalse(recoveredMessage.IndexOf("NTE") > -1, "Returned Message added ORC segment.");
+ Assert.IsFalse(recoveredMessage.IndexOf("NTE", StringComparison.Ordinal) > -1, "Returned Message added ORC segment.");
}
public void TestDHPatient1111111()
@@ -455,12 +455,12 @@ public void TestDHPatient1111111()
Assert.IsNotNull(orfR04);
object range = orfR04.GetQUERY_RESPONSE().GetORDER().GetOBSERVATION().OBX.GetObservationValue(1);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
Assert.IsNotNull(recoveredMessage);
- Assert.IsFalse(recoveredMessage.IndexOf("NTE") > -1, "Returned Message added ORC segment.");
+ Assert.IsFalse(recoveredMessage.IndexOf("NTE", StringComparison.Ordinal) > -1, "Returned Message added ORC segment.");
}
private static string GetQRYR02XML()
@@ -566,9 +566,12 @@ public void TestOBXDataTypes()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
+
+ Assert.IsNotNull(recoveredMessage);
+ Assert.IsFalse(string.Empty.Equals(recoveredMessage));
}
private static string GetDHPatient1111111()
diff --git a/tests/NHapi.NUnit/Parser/LegacyPipeParserV23Tests.cs b/tests/NHapi.NUnit/Parser/LegacyPipeParserV23Tests.cs
index 5e1d27b05..64a157c54 100644
--- a/tests/NHapi.NUnit/Parser/LegacyPipeParserV23Tests.cs
+++ b/tests/NHapi.NUnit/Parser/LegacyPipeParserV23Tests.cs
@@ -106,7 +106,7 @@ public void ParseORFR04ToXML()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
@@ -119,7 +119,7 @@ public void ParseXMLToHL7()
{
var message = GetQRYR02XML();
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var m = xmlParser.Parse(message);
var qryR02 = m as QRY_R02;
@@ -154,12 +154,12 @@ public void ParseORFR04ToXmlNoOCR()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
Assert.IsNotNull(recoveredMessage);
- Assert.IsFalse(recoveredMessage.IndexOf("ORC") > -1, "Returned message added ORC segment.");
+ Assert.IsFalse(recoveredMessage.IndexOf("ORC", StringComparison.Ordinal) > -1, "Returned message added ORC segment.");
}
[Test]
@@ -194,9 +194,12 @@ public void TestOBXDataTypes()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
+
+ Assert.IsNotNull(recoveredMessage);
+ Assert.IsFalse(string.Empty.Equals(recoveredMessage));
}
[Test]
@@ -219,12 +222,12 @@ public void ParseORFR04ToXmlNoNTE()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
Assert.IsNotNull(recoveredMessage);
- Assert.IsFalse(recoveredMessage.IndexOf("NTE") > -1, "Returned message added ORC segment.");
+ Assert.IsFalse(recoveredMessage.IndexOf("NTE", StringComparison.Ordinal) > -1, "Returned message added ORC segment.");
}
private static string GetQRYR02XML()
diff --git a/tests/NHapi.NUnit/Parser/LegacyPipeParserV24Tests.cs b/tests/NHapi.NUnit/Parser/LegacyPipeParserV24Tests.cs
index cfe9e8bed..55eec100e 100644
--- a/tests/NHapi.NUnit/Parser/LegacyPipeParserV24Tests.cs
+++ b/tests/NHapi.NUnit/Parser/LegacyPipeParserV24Tests.cs
@@ -73,7 +73,7 @@ public void ParseORFR04ToXML()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
@@ -86,7 +86,7 @@ public void ParseXMLToHL7()
{
var message = GetQRYR02XML();
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var m = xmlParser.Parse(message);
var qryR02 = m as QRY_R02;
@@ -121,7 +121,7 @@ public void ParseORFR04ToXmlNoOCR()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
@@ -161,9 +161,12 @@ public void TestOBXDataTypes()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
+
+ Assert.IsNotNull(recoveredMessage);
+ Assert.IsFalse(string.Empty.Equals(recoveredMessage));
}
[Test]
@@ -186,7 +189,7 @@ public void ParseORFR04ToXmlNoNTE()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new LegacyDefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
diff --git a/tests/NHapi.NUnit/Parser/ParserBaseTests.cs b/tests/NHapi.NUnit/Parser/ParserBaseTests.cs
index 7b5fc641c..ef0527bd8 100644
--- a/tests/NHapi.NUnit/Parser/ParserBaseTests.cs
+++ b/tests/NHapi.NUnit/Parser/ParserBaseTests.cs
@@ -176,16 +176,35 @@ public void TestParserDoesntFailWithoutValidationExceptionHandlerFactory()
{
var adt = new ADT_A01();
+ SetMessageHeader(adt, "ADT", "A01", "T");
+
var sut = new PipeParser();
var xmlParser = new DefaultXMLParser();
var msg = sut.Encode(adt);
- sut.Parse(msg);
+ adt = (ADT_A01)sut.Parse(msg);
msg = xmlParser.Encode(adt);
xmlParser.Parse(msg);
}
#endregion
+
+ private static void SetMessageHeader(IMessage msg, string messageCode, string messageTriggerEvent, string processingId)
+ {
+ var msh = (ISegment)msg.GetStructure("MSH");
+
+ var version27 = new Version("2.7");
+ var messageVersion = new Version(msg.Version);
+
+ Terser.Set(msh, 1, 0, 1, 1, "|");
+ Terser.Set(msh, 2, 0, 1, 1, version27 > messageVersion ? "^~\\&" : "^~\\");
+ Terser.Set(msh, 7, 0, 1, 1, DateTime.Now.ToString("yyyyMMddHHmmssK"));
+ Terser.Set(msh, 9, 0, 1, 1, messageCode);
+ Terser.Set(msh, 9, 0, 2, 1, messageTriggerEvent);
+ Terser.Set(msh, 10, 0, 1, 1, Guid.NewGuid().ToString());
+ Terser.Set(msh, 11, 0, 1, 1, processingId);
+ Terser.Set(msh, 12, 0, 1, 1, msg.Version);
+ }
}
-}
+}
\ No newline at end of file
diff --git a/tests/NHapi.NUnit/Parser/PipeParserTests.cs b/tests/NHapi.NUnit/Parser/PipeParserTests.cs
index fa25d287a..f855c7bad 100644
--- a/tests/NHapi.NUnit/Parser/PipeParserTests.cs
+++ b/tests/NHapi.NUnit/Parser/PipeParserTests.cs
@@ -175,24 +175,6 @@ public void UnEscapesData()
Assert.AreEqual(expectedResult, segmentData);
}
- ///
- /// Check that an is thrown when a null is
- /// provided to Parse method calls.
- ///
- [Test]
- public void ParseWithNullConfigThrows()
- {
- var parser = new PipeParser();
- IMessage nullMessage = null;
- const string version = "2.5.1";
- ParserOptions nullConfiguration = null;
-
- Assert.Throws(() => parser.Parse(GetMessage(), nullConfiguration));
- Assert.Throws(() =>
- parser.Parse(nullMessage, GetMessage(), nullConfiguration));
- Assert.Throws(() => parser.Parse(GetMessage(), version, nullConfiguration));
- }
-
private static void SetMessageHeader(Model.V251.Message.OML_O21 msg, string messageCode, string messageTriggerEvent, string processingId)
{
var msh = msg.MSH;
diff --git a/tests/NHapi.NUnit/Parser/PipeParserV231Tests.cs b/tests/NHapi.NUnit/Parser/PipeParserV231Tests.cs
index bed4124d0..7eaf76f1e 100644
--- a/tests/NHapi.NUnit/Parser/PipeParserV231Tests.cs
+++ b/tests/NHapi.NUnit/Parser/PipeParserV231Tests.cs
@@ -199,7 +199,7 @@ public void ParseORFR04ToXML()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new DefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
@@ -312,12 +312,12 @@ public void ParseORFR04ToXmlNoOCR()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new DefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
Assert.IsNotNull(recoveredMessage);
- Assert.IsFalse(recoveredMessage.IndexOf("ORC") > -1, "Returned Message added ORC segment.");
+ Assert.IsFalse(recoveredMessage.IndexOf("ORC", StringComparison.Ordinal) > -1, "Returned Message added ORC segment.");
}
[Test]
@@ -340,12 +340,12 @@ public void ParseORFR04ToXmlNoNTE()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new DefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
Assert.IsNotNull(recoveredMessage);
- Assert.IsFalse(recoveredMessage.IndexOf("NTE") > -1, "Returned Message added ORC segment.");
+ Assert.IsFalse(recoveredMessage.IndexOf("NTE", StringComparison.Ordinal) > -1, "Returned Message added ORC segment.");
}
[Test]
@@ -436,12 +436,12 @@ public void ParseORFR04FromDHTest()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new DefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
Assert.IsNotNull(recoveredMessage);
- Assert.IsFalse(recoveredMessage.IndexOf("NTE") > -1, "Returned Message added ORC segment.");
+ Assert.IsFalse(recoveredMessage.IndexOf("NTE", StringComparison.Ordinal) > -1, "Returned Message added ORC segment.");
}
public void TestDHPatient1111111()
@@ -455,12 +455,12 @@ public void TestDHPatient1111111()
Assert.IsNotNull(orfR04);
object range = orfR04.GetQUERY_RESPONSE().GetORDER().GetOBSERVATION().OBX.GetObservationValue(1);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new DefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
Assert.IsNotNull(recoveredMessage);
- Assert.IsFalse(recoveredMessage.IndexOf("NTE") > -1, "Returned Message added ORC segment.");
+ Assert.IsFalse(recoveredMessage.IndexOf("NTE", StringComparison.Ordinal) > -1, "Returned Message added ORC segment.");
}
private static string GetQRYR02XML()
@@ -566,9 +566,12 @@ public void TestOBXDataTypes()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new DefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
+
+ Assert.IsNotNull(recoveredMessage);
+ Assert.IsFalse(string.Empty.Equals(recoveredMessage));
}
private static string GetDHPatient1111111()
diff --git a/tests/NHapi.NUnit/Parser/PipeParserV23Tests.cs b/tests/NHapi.NUnit/Parser/PipeParserV23Tests.cs
index c2df94112..efaf2878e 100644
--- a/tests/NHapi.NUnit/Parser/PipeParserV23Tests.cs
+++ b/tests/NHapi.NUnit/Parser/PipeParserV23Tests.cs
@@ -106,7 +106,7 @@ public void ParseORFR04ToXML()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new DefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
@@ -154,7 +154,7 @@ public void ParseORFR04ToXmlNoOCR()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new DefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
@@ -194,9 +194,12 @@ public void TestOBXDataTypes()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new DefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
+
+ Assert.IsNotNull(recoveredMessage);
+ Assert.IsFalse(string.Empty.Equals(recoveredMessage));
}
[Test]
@@ -219,7 +222,7 @@ public void ParseORFR04ToXmlNoNTE()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new DefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
diff --git a/tests/NHapi.NUnit/Parser/PipeParserV24Tests.cs b/tests/NHapi.NUnit/Parser/PipeParserV24Tests.cs
index 2d983f7dd..65d955339 100644
--- a/tests/NHapi.NUnit/Parser/PipeParserV24Tests.cs
+++ b/tests/NHapi.NUnit/Parser/PipeParserV24Tests.cs
@@ -73,7 +73,7 @@ public void ParseORFR04ToXML()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new DefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
@@ -121,7 +121,7 @@ public void ParseORFR04ToXmlNoOCR()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new DefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
@@ -161,9 +161,12 @@ public void TestOBXDataTypes()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new DefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
+
+ Assert.IsNotNull(recoveredMessage);
+ Assert.IsFalse(string.Empty.Equals(recoveredMessage));
}
[Test]
@@ -186,7 +189,7 @@ public void ParseORFR04ToXmlNoNTE()
Assert.IsNotNull(orfR04);
- XMLParser xmlParser = new DefaultXMLParser();
+ var xmlParser = new DefaultXMLParser();
var recoveredMessage = xmlParser.Encode(orfR04);
diff --git a/tests/NHapi.NUnit/Parser/XMLParserTests.cs b/tests/NHapi.NUnit/Parser/XMLParserTests.cs
new file mode 100644
index 000000000..c8056098b
--- /dev/null
+++ b/tests/NHapi.NUnit/Parser/XMLParserTests.cs
@@ -0,0 +1,394 @@
+namespace NHapi.NUnit.Parser
+{
+ using System;
+ using System.IO;
+ using System.Text.RegularExpressions;
+
+ using global::NUnit.Framework;
+
+ using NHapi.Base.Model;
+ using NHapi.Base.Parser;
+ using NHapi.Base.Util;
+ using NHapi.Model.V25.Datatype;
+ using NHapi.Model.V25.Message;
+
+ using ADT_A01 = NHapi.Model.V23.Message.ADT_A01;
+ using ED = NHapi.Model.V25.Datatype.ED;
+ using ORU_R01 = NHapi.Model.V25.Message.ORU_R01;
+ using ST = NHapi.Model.V25.Datatype.ST;
+
+ [TestFixture]
+ public class XMLParserTests
+ {
+ private static readonly string TestDataDir = $"{TestContext.CurrentContext.TestDirectory}/TestData/Parser";
+
+ [Test]
+ public void Encode_DuplicateSegmentInStructureDefinition_IsHandledCorrectly()
+ {
+ // Arrange
+ var message = File.ReadAllText($"{TestDataDir}/adt_a17.xml");
+ var defaultXmlParser = new DefaultXMLParser();
+ var pipeParser = new PipeParser();
+
+ // Act
+ var parsed = defaultXmlParser.Parse(message);
+ var er7Encoded = pipeParser.Encode(parsed);
+ var er7Message = Regex.Replace(er7Encoded, "\\r", "\\r\\n");
+
+ var firstIndex = er7Message.IndexOf("PID", StringComparison.Ordinal);
+ var secondIndex = er7Message.IndexOf("PID", firstIndex + 1, StringComparison.Ordinal);
+ var thirdIndex = er7Message.IndexOf("PID", secondIndex + 1, StringComparison.Ordinal);
+
+ // Assert
+ Assert.True(firstIndex > 0);
+ Assert.True(secondIndex > firstIndex);
+ Assert.AreEqual(-1, thirdIndex, $"Found third PID {firstIndex} {secondIndex} {thirdIndex}:\r\n{er7Message}");
+ }
+
+ [Test]
+ public void Parse_XmlContainsExtraComponents_HandledCorrectly()
+ {
+ // Arrange
+ var xmlMessage = File.ReadAllText($"{TestDataDir}/extracmp_xml.xml");
+ var xmlParser = new DefaultXMLParser();
+ var pipeParser = new PipeParser();
+
+ var message = xmlParser.Parse(xmlMessage);
+ var er7Encoded = pipeParser.Encode(message);
+
+ Assert.IsTrue(er7Encoded.Contains("HD.4"));
+ Assert.IsTrue(er7Encoded.Contains("HD.5"));
+ }
+
+ [TestCase("12", "12")]
+ [TestCase(" help >>> *** 12 \r", "12")]
+ [TestCase("x", null)]
+ [TestCase("x", null)]
+ public void GetAckID_ValidInput_ReturnsExpectedResult(string input, string expected)
+ {
+ // Arrange
+ var parser = new DefaultXMLParser();
+
+ // Act / Assert
+ Assert.AreEqual(expected, parser.GetAckID(input));
+ }
+
+ [Test]
+ public void GetAckID_ValidInput_ReturnsExpectedResult()
+ {
+ // Arrange
+ var expected = "876";
+ var xmlMessage = File.ReadAllText($"{TestDataDir}/get_ack_id.xml");
+ var parser = new DefaultXMLParser();
+
+ // Act / Assert
+ Assert.AreEqual(expected, parser.GetAckID(xmlMessage));
+ }
+
+ [TestCase("\r|\r^~\\&\r", "XML")]
+ [TestCase("blorg gablorg", null)]
+ public void GetEncoding_ValidInput_ReturnsExpectedResult(string input, string expected)
+ {
+ // Arrange
+ var parser = new DefaultXMLParser();
+
+ // Act / Assert
+ Assert.AreEqual(expected, parser.GetEncoding(input));
+ }
+
+ [Test]
+ public void GetVersion_ValidInput_ReturnsExpectedResult()
+ {
+ // Arrange
+ var expected = "2.4";
+ var xmlMessage = File.ReadAllText($"{TestDataDir}/get_version.xml");
+ var parser = new DefaultXMLParser();
+
+ // Act / Assert
+ Assert.AreEqual(expected, parser.GetVersion(xmlMessage));
+ }
+
+ [TestCase("\t\r\nhello ", "hello")]
+ [TestCase(" hello \t \rthere\r\n", "hello there")]
+ public void RemoveWhitespace_ValidInput_ReturnsExpectedResult(string input, string expected)
+ {
+ // Arrange
+ var parser = new DefaultXMLParser();
+
+ // Act / Assert
+ Assert.AreEqual(expected, parser.RemoveWhitespace(input));
+ }
+
+ [Test]
+ public void Parse_XmlHasNamespaces_ReturnsExpectedResult()
+ {
+ // Arrange
+ var expectedVersion = "2.2";
+ var expectedMsh7 = "19951010134000";
+ var xmlMessage = File.ReadAllText($"{TestDataDir}/parse_and_encode_with_ns.xml");
+ var parser = new DefaultXMLParser();
+ var message = parser.Parse(xmlMessage);
+ var terser = new Terser(message);
+
+ // Act / Assert
+ Assert.AreEqual(expectedVersion, parser.GetVersion(xmlMessage));
+ Assert.AreEqual(expectedMsh7, terser.Get("MSH-7"));
+ }
+
+ [Test]
+ public void GetCriticalResponseData_ValidInput_ReturnsExpectedResult()
+ {
+ // Arrange
+ var xmlMessage = File.ReadAllText($"{TestDataDir}/critical_response_data.xml");
+ var parser = new DefaultXMLParser();
+ var segment = parser.GetCriticalResponseData(xmlMessage);
+ var actual = segment.GetField(2, 0);
+ var expected = "^~\\&";
+
+ // Act / Assert
+ Assert.AreEqual(expected, actual.ToString());
+ }
+
+ [Test]
+ public void Parse_OruR01_CorrectlyHandlesEDEscaping()
+ {
+ // Arrange
+ var xmlMessage = File.ReadAllText($"{TestDataDir}/ed_issue.xml");
+ var parser = new DefaultXMLParser();
+ var message = parser.Parse(xmlMessage) as ORU_R01;
+
+ // Act
+ var obx5 =
+ message?.GetPATIENT_RESULT().GetORDER_OBSERVATION().GetOBSERVATION(1)
+ .OBX.GetObservationValue(0).Data as ED;
+
+ // Assert
+ Assert.True(obx5?.Data.Value.StartsWith("JVBERi0xLjMKJeLjz9MKCjEgMCBvYmoKPDwgL1R5cG"));
+ }
+
+ [Test]
+ public void Parse_FollowedByEncode_WorksAsExpected()
+ {
+ // Arrange
+ var xmlMessage = File.ReadAllText($"{TestDataDir}/parse_and_encode.xml");
+ var xmlParser = new DefaultXMLParser();
+
+ var oruR01 = new NHapi.Model.V26.Message.ORU_R01();
+
+ // Action
+ xmlParser.Parse(oruR01, xmlMessage);
+ var encodedOruR01 = xmlParser.Encode(oruR01);
+
+ // Assert
+ Assert.AreEqual("LABMI1199510101340007", oruR01.MSH.MessageControlID.Value);
+ StringAssert.Contains("LABMI1199510101340007", encodedOruR01);
+ }
+
+ [Test]
+ public void Parse_OmdO03_OrderDietSegmentIsNotMissing()
+ {
+ // Arrange
+ var xmlMessage = File.ReadAllText($"{TestDataDir}/OMD_O03.xml");
+ var xmlParser = new DefaultXMLParser();
+
+ // Action
+ var omdO03 = (OMD_O03)xmlParser.Parse(xmlMessage);
+
+ // Assert
+ Assert.AreEqual("S", omdO03.GetORDER_DIET().DIET.GetODS().Type.Value);
+ }
+
+ [Test]
+ public void Parse_EncodedMessageIsModifiedWithEscapeSequence_IsParsedCorrectly()
+ {
+ // Arrange
+ var obx5Value = "CONTENT";
+
+ var oruR01 = new ORU_R01();
+
+ SetMessageHeader(oruR01, "ORU", "R01", "T");
+ var obx = oruR01.GetPATIENT_RESULT().GetORDER_OBSERVATION().GetOBSERVATION().OBX;
+ obx.ValueType.Value = "FT";
+ obx.GetObservationValue(0).Data = new FT(oruR01) { Value = obx5Value };
+
+ var xmlParser = new DefaultXMLParser();
+
+ // Act
+ var xml = xmlParser.Encode(oruR01);
+ xml = xml.Replace(
+ obx5Value,
+ $"{obx5Value}{obx5Value}");
+
+ var message = (ORU_R01)xmlParser.Parse(xml);
+ var parsedObx5Value =
+ ((FT)message.GetPATIENT_RESULT().GetORDER_OBSERVATION().GetOBSERVATION()
+ .OBX.GetObservationValue(0).Data).Value;
+
+ // Assert
+ Assert.AreEqual($"\\H\\{obx5Value}\\.br\\{obx5Value}\\N\\", parsedObx5Value);
+ }
+
+ [Test]
+ public void Encode_OmdO03_CorrectlyHandlesEscaping()
+ {
+ // Arrange
+ var er7Message = File.ReadAllText($"{TestDataDir}/omd_o03.txt");
+ var xmlParser = new DefaultXMLParser();
+ var pipeParser = new PipeParser();
+ var message = pipeParser.Parse(er7Message);
+
+ // Action
+ var xml = xmlParser.Encode(message);
+
+ // Assert
+ Assert.IsTrue(xml.Contains("OMD_O03.DIET"));
+ }
+
+ [Test]
+ public void Encode_WhenMessageContainsLongValues_DoesNotWrap()
+ {
+ // Arrange
+ var obx5Value = "AAAABBBB CCCCDDDD ";
+ obx5Value = obx5Value + obx5Value + obx5Value + obx5Value + obx5Value;
+ obx5Value = obx5Value + obx5Value + obx5Value + obx5Value + obx5Value;
+ obx5Value = obx5Value.Trim();
+
+ var oruR01 = new ORU_R01();
+
+ SetMessageHeader(oruR01, "ORU", "R01", "T");
+ var obx = oruR01.GetPATIENT_RESULT().GetORDER_OBSERVATION().GetOBSERVATION().OBX;
+ obx.ValueType.Value = "ST";
+ obx.GetObservationValue(0).Data = new ST(oruR01) { Value = obx5Value };
+
+ var xmlParser = new DefaultXMLParser();
+
+ // Act
+ var xml = xmlParser.Encode(oruR01);
+
+ var message = (ORU_R01)xmlParser.Parse(xml);
+ var parsedObx5Value =
+ ((ST)message.GetPATIENT_RESULT().GetORDER_OBSERVATION().GetOBSERVATION()
+ .OBX.GetObservationValue(0).Data).Value;
+
+ // Assert
+ Assert.AreEqual(obx5Value, parsedObx5Value);
+ }
+
+ [TestCase("ABC\\H\\highlighted\\N\\EFG", "ABChighlightedEFG")]
+ [TestCase("\\H\\highlighted\\N\\EFG", "highlightedEFG")]
+ [TestCase("ABC\\H\\highlighted\\N\\", "ABChighlighted")]
+ [TestCase("ABC\\E\\no escape sequence\\H\\highlighted\\N\\EFG", "ABC\\no escape sequence\\no escape sequence")]
+ public void Encode_MessageHasEscapedSequences_IsHandledCorrectly(string obx5Value, string expectedXml)
+ {
+ // Arrange
+ var parserOptions = new ParserOptions { PrettyPrintEncodedXml = false };
+ var xmlParser = new DefaultXMLParser();
+
+ var oruR01 = new ORU_R01();
+
+ SetMessageHeader(oruR01, "ORU", "R01", "T");
+ var obx = oruR01.GetPATIENT_RESULT().GetORDER_OBSERVATION().GetOBSERVATION().OBX;
+
+ var encodingCharacters = EncodingCharacters.FromMessage(oruR01);
+
+ obx.ValueType.Value = "FT";
+ obx.GetObservationValue(0).Data = new FT(oruR01) { Value = Escape.UnescapeText(obx5Value, encodingCharacters) };
+
+ // Test a couple of cases of escape sequences
+ // Action
+ var encoded = xmlParser.Encode(oruR01, parserOptions);
+
+ // Assert
+ StringAssert.Contains(expectedXml, encoded);
+ }
+
+ [TestCase("1234", "1234")]
+ [TestCase("1234\\E\\1234", "1234\\E\\1234")]
+ [TestCase("1234\\E\\", "1234\\E\\")]
+ [TestCase("1234\\E\\\\E\\", "1234\\E\\\\E\\")]
+ [TestCase("1234\\E\\\\.BR\\", "1234\\E\\")]
+ public void Encode_HandlesMessagesWithTrailingEncodedBackslash(string messageControlId, string expectedXml)
+ {
+ // Arrange
+ var adtA01 = new ADT_A01();
+
+ SetMessageHeader(adtA01, "ADT", "A01", "T");
+ adtA01.MSH.MessageControlID.Value = messageControlId;
+
+ // Test a couple of cases of escape sequences
+ var parserOptions = new ParserOptions { PrettyPrintEncodedXml = false };
+ var xmlParser = new DefaultXMLParser();
+
+ // Action
+ var encoded = xmlParser.Encode(adtA01, parserOptions);
+
+ // Assert
+ StringAssert.Contains(expectedXml, encoded);
+ }
+
+ [TestCase("2.1")]
+ [TestCase("2.2")]
+ [TestCase("2.3")]
+ [TestCase("2.3.1")]
+ [TestCase("2.4")]
+ [TestCase("2.5")]
+ [TestCase("2.5.1")]
+ [TestCase("2.6")]
+ [TestCase("2.7")]
+ [TestCase("2.7.1")]
+ [TestCase("2.8")]
+ [TestCase("2.8.1")]
+ public void Encode_GenericMessage_WorksAsExpected(string version)
+ {
+ // Arrange
+ var xmlParser = new DefaultXMLParser();
+ var type = GenericMessage.GetGenericMessageClass(version);
+
+ var constructor = type.GetConstructor(new[] { typeof(IModelClassFactory) });
+ var message = (IMessage)constructor?.Invoke(new object[] { new DefaultModelClassFactory() });
+
+ // Action
+ var document = xmlParser.EncodeDocument(message);
+
+ // Assert
+ Assert.IsNotNull(document);
+ Assert.AreEqual($"GenericMessageV{version.Replace(".", string.Empty)}", document.DocumentElement?.LocalName);
+ Assert.IsNotNull(xmlParser.Encode(message));
+ }
+
+ [Test]
+ public void Encode_AdtA01_CanBeParsedAgain()
+ {
+ // Arrange
+ var er7Message = File.ReadAllText($"{TestDataDir}/adt_a03.txt");
+ var xmlParser = new DefaultXMLParser();
+ var pipeParser = new PipeParser();
+ var message = pipeParser.Parse(er7Message);
+
+ // Action
+ var document = xmlParser.EncodeDocument(message);
+ var decodedMessage = xmlParser.ParseDocument(document, message.Version);
+
+ Console.WriteLine(decodedMessage.ToString());
+ }
+
+ private static void SetMessageHeader(IMessage msg, string messageCode, string messageTriggerEvent, string processingId)
+ {
+ var msh = (ISegment)msg.GetStructure("MSH");
+
+ var version27 = new Version("2.7");
+ var messageVersion = new Version(msg.Version);
+
+ Terser.Set(msh, 1, 0, 1, 1, "|");
+ Terser.Set(msh, 2, 0, 1, 1, version27 > messageVersion ? "^~\\&" : "^~\\");
+ Terser.Set(msh, 7, 0, 1, 1, DateTime.Now.ToString("yyyyMMddHHmmssK"));
+ Terser.Set(msh, 9, 0, 1, 1, messageCode);
+ Terser.Set(msh, 9, 0, 2, 1, messageTriggerEvent);
+ Terser.Set(msh, 10, 0, 1, 1, Guid.NewGuid().ToString());
+ Terser.Set(msh, 11, 0, 1, 1, processingId);
+ Terser.Set(msh, 12, 0, 1, 1, msg.Version);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/NHapi.NUnit/Test23Orc.cs b/tests/NHapi.NUnit/Test23Orc.cs
index 6e338b99b..b03faa773 100644
--- a/tests/NHapi.NUnit/Test23Orc.cs
+++ b/tests/NHapi.NUnit/Test23Orc.cs
@@ -2,8 +2,6 @@
{
using global::NUnit.Framework;
- using NHapi.Model.V23.Datatype;
- using NHapi.Model.V23.Group;
using NHapi.Model.V23.Message;
[TestFixture]
diff --git a/tests/NHapi.NUnit/TestData/Parser/OMD_O03.xml b/tests/NHapi.NUnit/TestData/Parser/OMD_O03.xml
new file mode 100644
index 000000000..aaa0cb609
--- /dev/null
+++ b/tests/NHapi.NUnit/TestData/Parser/OMD_O03.xml
@@ -0,0 +1,52 @@
+
+
+
+ |
+ ^~\&
+
+ TestSendingSystem
+
+
+ 200701011539
+
+
+ OMD
+ O03
+ OMD_O03
+
+
+ 2.5
+
+ 123
+
+
+
+
+ 1
+
+ hellin
+
+ PI
+
+
+
+ Doe
+
+ John
+
+
+
+
+
+
+ S
+
+ breakfast
+
+
+ 320^1/2 HAM SANDWICH^99FD8
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/NHapi.NUnit/TestData/Parser/adt_a03.txt b/tests/NHapi.NUnit/TestData/Parser/adt_a03.txt
new file mode 100644
index 000000000..e7e2b7a1a
--- /dev/null
+++ b/tests/NHapi.NUnit/TestData/Parser/adt_a03.txt
@@ -0,0 +1,5 @@
+MSH|^~\&|IRIS|SANTER|AMB_R|SANTER|200803051508||ADT^A03|263206|P|2.5
+EVN||200803051509||||200803031508
+PID|||5520255^^^PK^PK~ZZZZZZ83M64Z148R^^^CF^CF~ZZZZZZ83M64Z148R^^^SSN^SSN^^20070103^99991231~^^^^TEAM||ZZZ^ZZZ||19830824|F||||||||||||||||||||||N
+PV1||I|6402DH^^^^^^^^MED. 1 - ONCOLOGIA^^OSPEDALE MAGGIORE DI LODI&LODI|||^^^^^^^^^^OSPEDALE MAGGIORE DI LODI&LODI|13936^TEST^TEST||||||||||5068^TEST2^TEST2||2008003369||||||||||||||||||||||||||200803031508
+PR1|1||1111^Mastoplastica|Protesi|20090224|02|
\ No newline at end of file
diff --git a/tests/NHapi.NUnit/TestData/Parser/adt_a17.xml b/tests/NHapi.NUnit/TestData/Parser/adt_a17.xml
new file mode 100644
index 000000000..6920d6e89
--- /dev/null
+++ b/tests/NHapi.NUnit/TestData/Parser/adt_a17.xml
@@ -0,0 +1,221 @@
+
+
+
+ |
+ ^~\&
+
+ HIS
+
+
+ 050101
+
+
+ CUIDADOS
+
+
+ 050101
+
+ 20080427092114
+
+ ADT
+ A17
+ ADT_A17
+
+ MENSAJE_EJEMPLO_ADT_A17_1
+ P
+ 2.5
+ AL
+ ER
+
+
+ 20080427092100
+ 20080427092100
+
+
+ 1
+
+ 12345679
+ MI
+ NNESP
+
+ ESP
+ ISO3166
+
+
+
+
+ AST12345679
+ MS
+ HC
+
+ ESP
+ ISO3166
+
+
+
+ 100000
+ HIS
+ PI
+
+ 050101
+ 99CENTROMICASA
+
+
+
+
+ SaEZ
+
+ ALBERTO
+
+
+
+ TORRES
+
+
+
+ 19750322
+
+ M
+
+
+ C
+ NiNa Bonita
+ 78
+
+ 5
+ 051159
+ 5
+ 5291
+ ESP
+ H
+ Maello
+
+
+
+ PRN
+ PH
+ 921787865
+
+
+ ESP
+ EspaNa
+ ISO3166
+
+
+
+
+ 1
+ I
+
+ UE6D
+ 117-
+ 117-1
+
+ 050101
+
+
+
+ 11111111111111
+ HOS
+ VN
+
+ 050101
+ 99CENTROMICASA
+
+
+
+
+ 2
+
+
+ 2222222l
+ MI
+ NNESP
+
+ ESP
+ ISO3166
+
+
+
+
+ AST2222222
+ MS
+ HC
+
+ ESP
+ ISO3166
+
+
+
+
+ 200000
+ HIS
+ PI
+
+ 050101
+ 99CENTROMICASA
+
+
+
+
+ SWAP2
+
+ SWAP1
+
+
+
+ SWAP3
+
+
+
+ 19750322
+
+ M
+
+
+ C
+ NiNa Bonita
+ 78
+
+ 4 Izquierda
+ 051159
+ 5
+ 5291
+ ESP
+ H
+ Maello
+
+
+
+ PRN
+ PH
+ 921787865
+
+
+ ESP
+ EspaNa
+ ISO3166
+
+
+
+ 2
+ I
+
+ UE6D
+ 217-
+ 217-2
+
+ 050101
+
+
+
+ 2222222222222222
+ HOS
+ VN
+
+ 050101
+ 99CENTROMICASA
+
+
+
+
\ No newline at end of file
diff --git a/tests/NHapi.NUnit/TestData/Parser/critical_response_data.xml b/tests/NHapi.NUnit/TestData/Parser/critical_response_data.xml
new file mode 100644
index 000000000..b228fc5dc
--- /dev/null
+++ b/tests/NHapi.NUnit/TestData/Parser/critical_response_data.xml
@@ -0,0 +1,20 @@
+
+
+
+ |
+ ^~\&
+ LABMI1
+ DMCRES
+
+ 19951010134000
+
+
+ ORU
+ R01
+
+ LABMI1199510101340007
+ D
+ 2.2
+ AL
+
+
\ No newline at end of file
diff --git a/tests/NHapi.NUnit/TestData/Parser/ed_issue.xml b/tests/NHapi.NUnit/TestData/Parser/ed_issue.xml
new file mode 100644
index 000000000..ed375547b
--- /dev/null
+++ b/tests/NHapi.NUnit/TestData/Parser/ed_issue.xml
@@ -0,0 +1,364 @@
+
+
+
+ |
+ ^~\&
+
+ APPNAME
+
+
+ VENDOR NAME
+
+
+ CLINIC APPLICATION
+
+
+ CLINIC ID
+
+
+ 20100723170708
+
+
+ ORU
+ R01
+
+ 12345
+
+ P
+
+
+ 2.5
+
+
+
+
+
+
+ MODEL:xxx/SERIAL:xxx
+
+ STJ
+
+ U
+
+
+
+ Doe
+
+ John
+ Adams
+
+
+ 197903110920
+
+ M
+
+
+ Street
+
+ City
+ 06531
+ Country
+
+
+
+
+ 1
+ R
+
+ DoctorID
+
+
+ 123456
+
+
+
+
+
+
+ 1
+
+ 123456
+
+
+ Remote Follow-up
+
+
+ 20040328134623
+
+
+ 20040328134623
+
+
+ 20040328134623
+
+ F
+
+
+ 1
+ L
+ Comment
+
+
+
+ 1
+ ST
+
+ 257
+ MDC-IDC_SYSTEM_STATUS
+ MDC_IDC
+
+ 1
+
+ m
+
+ L
+ F
+
+ 20070422170125
+
+
+ LastFU
+ Since Last Follow-up Aggregate
+
+
+
+
+
+ 2
+ ED
+
+ 18750-0
+ Cardiac Electrophysiology Report
+ LN
+
+
+ Application
+ PDF
+ Base64
+
+
+ JVBERi0xLjMKJeLjz9MKCjEgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cKL091dGxpbmVzID
+ IgMCBS
+
+ Ci9QYWdlcyAzIDAgUgovT3BlbkFjdGlvbiA4IDAgUiA+PgplbmRvYmoKMiAwIG9iago8PCAvVHlw
+
+ ZSAvT3V0bGluZXMgL0NvdW50IDAgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzCi9L
+
+ aWRzIFs2IDAgUgpdCi9Db3VudCAxCi9SZXNvdXJjZXMgPDwKL1Byb2NTZXQgNCAwIFIKL0ZvbnQg
+
+ PDwgCi9GMSAxMSAwIFIKL0YyIDEyIDAgUgovRjMgMTcgMCBSID4+Ci9YT2JqZWN0IDw8IAovSTEg
+
+ OSAwIFIgPj4KPj4KL01lZGlhQm94IFswLjAwMCAwLjAwMCA1OTUuMjgwIDg0MS44OTBdCiA+Pgpl
+
+ bmRvYmoKNCAwIG9iagpbL1BERiAvVGV4dCAvSW1hZ2VDIF0KZW5kb2JqCjUgMCBvYmoKPDwKL0Ny
+
+ ZWF0b3IgKERPTVBERiBDb252ZXJ0ZXIpCi9DcmVhdGlvbkRhdGUgKDIwMTAtMDItMTUpCj4+CmVu
+
+ ZG9iago2IDAgb2JqCjw8IC9UeXBlIC9QYWdlCi9QYXJlbnQgMyAwIFIKL0Fubm90cyBbIDEzIDAg
+
+ UiAxNSAwIFIgXQovQ29udGVudHMgNyAwIFIKPj4KZW5kb2JqCjcgMCBvYmoKPDwgL0ZpbHRlciAv
+
+ RmxhdGVEZWNvZGUKL0xlbmd0aCA5MTcgPj4Kc3RyZWFtCnicnVbZbttGFH3XV9y3xoA8nn1IFQgg
+
+ 23HSBC1cS4Uf6jyMpZE5KReXS4wG+Zi+9i97SYuLRSl2AgMUfMR77pm7nNGEEUopDJ/53YTBA3B4
+
+ DxQ+wZ/wET/XE8abb2UgiA4ZKMMIDygIqQkLQ8gdbCZ/T0LdvFX/iYDImvQxzoSMyJDCKpmc/MLg
+
+ PJv8PqGP7w6emPt02YUYQxgPYLmGkwvWoLDcALy6rG5jX0RuDVkKR7D8BG+WdVwgiaFBE8cZfYzj
+
+ g7hfbVHYVVQVriwLmMdfIucTl/9UwLkvnC0cXDn8zFcRnLm0dPmAWwQBCbg8LApuXrWvD0/EBucS
+
+ WB2m9GGOqCzvZycniV3nK5LcRSSy+Webr4lbV4fJr94iqLBlFFvWtkuECstf51LESAYJKCWJYLRD
+
+ Ylgc7IBSgij9jRbcHD132LaJIiShoi1D2BK8yxLXU+yqZzhcqg4OSIiSExCaGCU64FvaRT2C4mBi
+
+ eF13fpPlVQI2XcPiev7bDJaRg8scu5+WDXpRlRWOdLaBxcoj6Dd+BdfuFs6yJKlSX3pX9PI1ryX0
+
+ z0MrJLYl4UToEJQOmxXaiq8XaO+ZxiB2vCuvaLiwvbiYNVuLxIjwLcK3yI6AGDBlR8RGROy7iHYF
+
+ vIxpjAw1tUw7IvcI2Keon0IdojUg3AyDAC7bafiuURjYQcurKbqaGfM+PzUDEgyTXLUkOz63sqVH
+
+ mzu3pZv9OwzGEwvFm2gh5XhFOaVmnEyhL+hW8fD1s9jmf8GyOfIHn2ZF5EuLA3zTEN0ckadrUzzY
+
+ dAYl1up+UKtNV6uiL8ADFmDVF4DsqaMyuJ+KjVWd5t5t4NRnPsXcybOOodCwtJY4MvV9I2QH1I6B
+
+ +UJc0EAezvczBDNgWhwzvN+GOhUJ0LmVwn7TPcV+erW8danLbQzvsuIeqxhP4dqmdzBPbqvYlln+
+
+ D5xZLNPjJQOLypcODGfnU8wDl9iHxKawKHPnyimcZkWZpdNh1TjHSxjNXeGCGLXHnp/KoZwxOYU/
+
+ FvMn88OIxvlRDC2e8THH0sXuPspSNwPNzLHh+liEgYGvcGFXhU98PPhGMsogy7v/jWHBMBlOnanr
+
+ TrF+Wo2T/QfvbVpZrAybAqdo8fM4hit/F6H++lbOP+N1/6OOK+u21z9aXui47W8dnKE6rHeyFul9
+
+ q6XuHKgHeiOTQbhLtEVeSrQr4GVMY2SgqWPaEblHwB5F/wMtHGbMCmVuZHN0cmVhbQplbmRvYmoK
+
+ CjggMCBvYmoKWzYgMCBSIC9GaXRdCmVuZG9iagoKOSAwIG9iago8PAovVHlwZSAvWE9iamVjdAov
+
+ U3VidHlwZSAvSW1hZ2UKL1dpZHRoIDIwMAovSGVpZ2h0IDgwCi9GaWx0ZXIgL0ZsYXRlRGVjb2Rl
+
+ Ci9EZWNvZGVQYXJtcyA8PCAvUHJlZGljdG9yIDE1IC9Db2xvcnMgMSAvQ29sdW1ucyAyMDAgL0Jp
+
+ dHNQZXJDb21wb25lbnQgOD4+Ci9Db2xvclNwYWNlICBbIC9JbmRleGVkIC9EZXZpY2VSR0IgMzIg
+
+ MTAgMCBSIF0KL0JpdHNQZXJDb21wb25lbnQgOAovTGVuZ3RoIDIwMzEgPj4Kc3RyZWFtCmje7Zlp
+
+ l6soEIZNOp2OccGoGBNc+P+/cqhiK1zSduaeM33u6Ic0IhT1SBXg25H8S65oB9lBdpAdZAf5/SB1
+
+ fXflZ133r1rW7xh9v48DqaPo0xSvUXQxxbsvSnk5RFF0OD/xwUmVo49M+j7e8/snPIw+YbBLFLkB
+
+ anIrT9FpYrSO3FVLX1YeBDehI0sg0U2/6oP3/qw62BafxtShRkB9WfijL8rMjZp9C0KNbgWhfZZB
+
+ Dr0ewYE8jTemwbmXz8vhhG3Ayu1kpwF9N2/opgxl2PJ7kMCournW+uohNmv1GuHPc3JD+yyDRGfr
+
+ kwG5RMdzdLRl3bYHf22Lu/PqfMTeODkHXX3Pvg2twGhNw5MOGd4EfZZzBAz1B/ir3VTl7G6tq/4+
+
+ wUggmc7Pq5nQzM1hOP4ayJ1a2Qhyf7FqgZVTdOw/lT8WJAPfTsZnSIvzrXf5crx6c2fllKK+6vJh
+
+ 4oyOlusSSGDUh1b/CiToswyi0vwEKW9BjtEXvuEnyeFPfN39B5SPX0+bSjcgOJLViIxPUniW7NRo
+
+ TRuugwR9lkFgEYX3b0BqjeCC//l1BAvazwzXjsPNpJLGyX4MQo1uBQkdWQSB1ah3ICrSLur6MMGP
+
+ Jq7KxNXc3MzirILqBA0PaHoeWi/3EWJ0Y47MHVkAueNeokGe4S5krv5I3oSO+yx456+TPXOZerAT
+
+ 7Yz+BGTiyAxE1tKBqFd7wuuIkdN/6Xn5gP7ZzW4ZNcTeUTfUvh3s8vu8zEGekfH/hryB0Y0gQZ9V
+
+ EOlA3NoFjzKMuutT9nh+gbVaodwx6m40pp/BhniegcB2+wVmDhiw1Gi4Ia6DBH02gFx8agB777L2
+
+ qLbdD3tzpcFu2F2owcxMQeAA5B4GRsMjyipI2Od7kP7oYzjTUXFGH85629Pr7w2X9czvJwivD436
+
+ VDcFkf3ZnyhDo9tAJo68d4y/k8N5D+ee987m4Rn/vv3E/6LP/oW4g+wgO8gO8mtABs6HV/f2ann7
+
+ J1zgXJifPw0i4li8urcXi9nWYco4HteexTE3P/8VSMWqrcOwuJAvQRhr/zuQH1wsbl+D/MEcGThj
+
+ rBqs4yox9NXifVeyQtOMjWomXI7AT4PvU7gmsi1YiZ4LzmVXVjLHyIIWDQkxaNZpEJ0jQ8WYyUdn
+
+ QgpVyXQ3P/bMlgPpYn21BkSYe5UHqtgk5pl8YClubI6onxwqqtZ2lx1WYDDxOO4S1QxvKqxO7OyO
+
+ TPfwOfKIrW1iotGVSac4E1s7FljKh4UZSXPOVU4mI52RHFwDJsZV1wRtsYcoMNgMSJxUFYyQYndl
+
+ KY/TVnD9olV1nFYSAAdVI1qWkgUg5VUae5A0LsSjjIfAxBAXnDN4oapH/hBV0sI7SZSthKw2HgSn
+
+ qTWOC5cdHH8LPbBQP+lIJgNBOuwH9dikxRrFoN4JN5NnjI12HIlcTJXH3IPE8UM3oCZ0B2V5UGNV
+
+ +vmgrdLkJSAPzpsODLrnKipK3x7/JNovFQSUxmYsNin0CjVCmZNMVqOnjY+FRjsrOzojSYV11IQc
+
+ WpWorX6LcfnQfRO0kXvrPkcSkxMeRIVRIScgsbvEGghzTTiAeNcxi1hn90F4F+E+gj6kDxmYaP2A
+
+ MHsqprCvy+ApSBInrdBhaRxX3fJxDsLMajasg6SmifDe6jlplCfJ4EBG89rdhji2kMQPakJNmEqM
+
+ R6l9eJSKtcKJtmvqBETo+JQexHEEIDkGG9nZ5yBVnPrjRwCiZ5m7ERvThO7sIxgkJgyvTwe19kFk
+
+ Dyv7iNArZ+NBSlzxpiCtaQeMKyAqFzCz0i4EETlYSV1Yq/WqGcfGr1pj2mJaM2rCBCeuk4zrhQD6
+
+ ggND3s5XLZVoJaxyFqQJ9hEHgosmSxFnBQSGSmBzKUOQwvQc/LtD+w6kMf1aagKWiIrDIg1RFjOG
+
+ diGboJiP82QHiFQ4EL4CIrnNyFUQ+UhNUgYgo+9pSKBZQXKkdf2ICSwxXLUELgLlaN3VxdlZaxDd
+
+ tmNN933DQYgtPTsxrPbzRdJqFGKcF/cvxB1kB9lBdpC/DORfizu/BeRtPeH9N/DLQN5XVP4+ECvc
+
+ hEoL0V9otZeOUE8dK2YbF63xR9VNNbe2sLIO0X2gv9WaBo7fbUM4GvFsC4gVbgKlxesvQTWRjvCL
+
+ LMczrhGKQFFQX3Go55T0gKgVnlSrCk4YolqTsN+0wWjOs80gKNwESovXX0IBxktHMLj6lGFIp79o
+
+ OgABpSc28oKeW3UaL6BTg9JFKRS+6e+0Jjibpwy+6ulozrPNIPjpGSgtTn+ZCDBeOkJHSq0Q4Rdl
+
+ 22JopfpbjtOPXhTvKvyIwzlOplqTy5FgNCopbQPhM6XF6S8TAcZLRzAYM2Pzac7SqtQHR2dmqoKq
+
+ 8KttyQm+aQWgIIOUE6XF6S9hNZGO7Md+qHQvgJCy8JrSCkgwGp8rDd+AuJkhSovRX8JqIh05ACvD
+
+ rINUBKQ0xtZB/GgzJWYbyFxpAf0lqKbSkZ8Ju7CMyyAFpjY+HmPyT4ZFkMCJN0ECpcXrL7SaSkce
+
+ RBkoBrXw5OMiiGqXC7V/wIqglaaxLBdACjmOoRPvghClhegvgQBDpCOSG7kJ68ciiPmnAkYYpF7O
+
+ EsQJQXKzj9DR3gWhSgvRX2g1kY5okqPck4vl0DK6TooL6Vg6GTgEwWWkC0f7IQi9VkQXUl6RjoQY
+
+ tupIa03dGBO5Z/+w2kF2kB1kB9lBdpD/E8g/QZSt+wplbmRzdHJlYW0KZW5kb2JqCgoxMCAwIG9i
+
+ ago8PCAvRmlsdGVyIC9GbGF0ZURlY29kZQovTGVuZ3RoIDExMCA+PgpzdHJlYW0KeJwBYwCc/2MG
+
+ EW0WIHclL4A1PopETZRUXJ5ka6hzerGCiJaWlrqSlp2dnaOjo8ShpaqqqrCwsLe3t86xtL6+vsTE
+
+ xNjBw8rKytHR0eLQ0tfX197e3uvg4eXl5evr6/Xv8PLy8vj4+P///6g8Qs0KZW5kc3RyZWFtCmVu
+
+ ZG9iagoKMTEgMCBvYmoKPDwgL1R5cGUgL0ZvbnQKL1N1YnR5cGUgL1R5cGUxCi9OYW1lIC9GMQov
+
+ QmFzZUZvbnQgL0hlbHZldGljYQovRW5jb2RpbmcgL1dpbkFuc2lFbmNvZGluZwo+PgplbmRvYmoK
+
+ MTIgMCBvYmoKPDwgL1R5cGUgL0ZvbnQKL1N1YnR5cGUgL1R5cGUxCi9OYW1lIC9GMgovQmFzZUZv
+
+ bnQgL0hlbHZldGljYS1Cb2xkT2JsaXF1ZQovRW5jb2RpbmcgL1dpbkFuc2lFbmNvZGluZwo+Pgpl
+
+ bmRvYmoKMTMgMCBvYmoKPDwgL1R5cGUgL0Fubm90Ci9TdWJ0eXBlIC9MaW5rCi9BIDE0IDAgUgov
+
+ Qm9yZGVyIFswIDAgMF0KL0ggL0kKL1JlY3QgWyAzOTYuMTU2MCA3NzcuMTI4NCA1NTMuNTYwMCA3
+
+ OTEuMDAwNCBdCj4+CmVuZG9iagoxNCAwIG9iago8PCAvVHlwZSAvQWN0aW9uCi9TIC9VUkkKL1VS
+
+ SSAoaHR0cDovL21hZHJjLm1naC5oYXJ2YXJkLmVkdSkKPj4KZW5kb2JqCjE1IDAgb2JqCjw8IC9U
+
+ eXBlIC9Bbm5vdAovU3VidHlwZSAvTGluawovQSAxNiAwIFIKL0JvcmRlciBbMCAwIDBdCi9IIC9J
+
+ Ci9SZWN0IFsgMTIuMDAwMCA3MzkuOTUwMCAzNi4wMDMwIDc1MC4zNTQwIF0KPj4KZW5kb2JqCjE2
+
+ IDAgb2JqCjw8IC9UeXBlIC9BY3Rpb24KL1MgL1VSSQovVVJJIChodHRwOi8vbWFkcmMubWdoLmhh
+
+ cnZhcmQuZWR1LykKPj4KZW5kb2JqCjE3IDAgb2JqCjw8IC9UeXBlIC9Gb250Ci9TdWJ0eXBlIC9U
+
+ eXBlMQovTmFtZSAvRjMKL0Jhc2VGb250IC9IZWx2ZXRpY2EtQm9sZAovRW5jb2RpbmcgL1dpbkFu
+
+ c2lFbmNvZGluZwo+PgplbmRvYmoKeHJlZgowIDE4CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAw
+
+ MDAxNSAwMDAwMCBuIAowMDAwMDAwMDk4IDAwMDAwIG4gCjAwMDAwMDAxNDQgMDAwMDAgbiAKMDAw
+
+ MDAwMDM0NyAwMDAwMCBuIAowMDAwMDAwMzg0IDAwMDAwIG4gCjAwMDAwMDA0NjAgMDAwMDAgbiAK
+
+ MDAwMDAwMDU0OSAwMDAwMCBuIAowMDAwMDAxNTM5IDAwMDAwIG4gCjAwMDAwMDE1NjggMDAwMDAg
+
+ biAKMDAwMDAwMzg2OCAwMDAwMCBuIAowMDAwMDA0MDUyIDAwMDAwIG4gCjAwMDAwMDQxNjAgMDAw
+
+ MDAgbiAKMDAwMDAwNDI4MCAwMDAwMCBuIAowMDAwMDA0NDA4IDAwMDAwIG4gCjAwMDAwMDQ0ODgg
+
+ MDAwMDAgbiAKMDAwMDAwNDYxNCAwMDAwMCBuIAowMDAwMDA0Njk1IDAwMDAwIG4gCnRyYWlsZXIK
+
+ PDwKL1NpemUgMTgKL1Jvb3QgMSAwIFIKL0luZm8gNSAwIFIKPj4Kc3RhcnR4cmVmCjQ4MDgKJSVF
+ T0YK
+
+
+ F
+
+ 20070422170125
+
+
+
+
+
+ 3
+ CE
+
+ 257
+ MDC-IDC_SYSTEM_STATUS
+ MDC_IDC
+
+ 1
+
+ T57000
+ GALLBLADDER
+ SNM
+
+
+ m
+
+ L
+ F
+
+ 20070422170125
+
+
+ LastFU
+ Since Last Follow-up Aggregate
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/NHapi.NUnit/TestData/Parser/extracmp_xml.xml b/tests/NHapi.NUnit/TestData/Parser/extracmp_xml.xml
new file mode 100644
index 000000000..25a70604f
--- /dev/null
+++ b/tests/NHapi.NUnit/TestData/Parser/extracmp_xml.xml
@@ -0,0 +1,33 @@
+
+
+
+ |
+ ^~\&
+
+ OLIS
+ X500
+ HD.4
+ HD.5
+
+
+ 2.16.840.1.113883.3.59.3:0947
+ ISO
+
+
+ 20130819093140-0400
+
+
+ ERP
+ Z99
+ ERP_R09
+
+ a9aa9d25-d4c0-4e1d-8e4f-b79d16ef5179
+
+ T
+
+
+ 2.3.1
+
+ 8859/1
+
+
\ No newline at end of file
diff --git a/tests/NHapi.NUnit/TestData/Parser/get_ack_id.xml b/tests/NHapi.NUnit/TestData/Parser/get_ack_id.xml
new file mode 100644
index 000000000..cb4138e41
--- /dev/null
+++ b/tests/NHapi.NUnit/TestData/Parser/get_ack_id.xml
@@ -0,0 +1,46 @@
+
+
+
+
+ |
+ ^~/&
+
+ MPI
+ ISO
+
+
+ HealthLink
+ ISO
+
+
+ UHN Vista
+ ISO
+
+
+ UHN
+ ISO
+
+ 200204292049
+
+ RSP
+ K22
+ RSP_K22
+
+ 200204292049100799
+
+ P
+
+
+ 2.4
+
+ Q22
+
+
+ AA
+ 876
+
+
+ OK
+
+ Q22
+
\ No newline at end of file
diff --git a/tests/NHapi.NUnit/TestData/Parser/get_version.xml b/tests/NHapi.NUnit/TestData/Parser/get_version.xml
new file mode 100644
index 000000000..e760491fd
--- /dev/null
+++ b/tests/NHapi.NUnit/TestData/Parser/get_version.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+ |
+ ^~\&
+
+ UHN Vista
+ ISO
+
+
+ UHN
+ ISO
+
+
+ MPI
+ ISO
+
+
+ HealthLink
+ ISO
+
+ 20020429132718.734-0400
+
+ QBP
+ Q22
+ QBP_Q21
+
+ 855
+
+ P
+
+
+ 2.4
+
+ Q22
+
+
+
+ Q22
+ Find Candidates
+ HL7nnnn
+
+
+
+ @PID.3.1
+ 9583518684
+
+
+ @PID.3.4.1
+ CANON
+
+
+ @PID.5.1.1
+ ECG-Acharya
+
+
+ @PID.5.2
+ Nf
+
+
+ @PID.5.7
+ L
+
+
+ @PID.7
+ 197104010000
+
+
+ @PID.8
+ M
+
+
+ 100
+
+
+ TTH
+
+
+ 13831
+ ULTIuser2
+ 234564
+ R&H Med
+
+
+ I
+
+ 100
+ RD
+
+
+ R
+
+
+
\ No newline at end of file
diff --git a/tests/NHapi.NUnit/TestData/Parser/omd_o03.txt b/tests/NHapi.NUnit/TestData/Parser/omd_o03.txt
new file mode 100644
index 000000000..41f584110
--- /dev/null
+++ b/tests/NHapi.NUnit/TestData/Parser/omd_o03.txt
@@ -0,0 +1 @@
+MSH|^~\&|DIETOOLS|1^DOMINION|JARA||20101027181706||OMD^O03^OMD_O03|20101027181706|P|2.5|||ER|AL
PID|1||CIPNUM^^^CAEX^CIP^^^^EX&&ES-EX||DUMAS^VICTOR HUGO|ZAPATA|19740325|1
PV1|1|I|^^51C302-1^^^^^^^0005&51UHP31&UH TERCERA 1 HSPA&TIPOUOENF||||||||||||||||100002739^005^^^^^^^^0005&APARATO DIGESTIVO&5DIG&TIPOUOSERV|
ORC|XO||||||||20101117|1^^HERNAME
TQ1|1||CE||||20101117
ODS|D||PAN4^PEDIATRICA 1|
ODS|P||EVENTO^LACT|
ODS|P||CARACTERISTICA^SIN SAL|
ODS|P||CARACTERISTICA^DIABETICO|
\ No newline at end of file
diff --git a/tests/NHapi.NUnit/TestData/Parser/parse_and_encode.xml b/tests/NHapi.NUnit/TestData/Parser/parse_and_encode.xml
new file mode 100644
index 000000000..70d504b24
--- /dev/null
+++ b/tests/NHapi.NUnit/TestData/Parser/parse_and_encode.xml
@@ -0,0 +1,20 @@
+
+
+
+ |
+ ^~\&
+ LABMI1
+ DMCRES
+
+ 19951010134000
+
+
+ ORU
+ R01
+
+ LABMI1199510101340007
+ D
+ 2.2
+ AL
+
+
\ No newline at end of file
diff --git a/tests/NHapi.NUnit/TestData/Parser/parse_and_encode_with_ns.xml b/tests/NHapi.NUnit/TestData/Parser/parse_and_encode_with_ns.xml
new file mode 100644
index 000000000..59898a249
--- /dev/null
+++ b/tests/NHapi.NUnit/TestData/Parser/parse_and_encode_with_ns.xml
@@ -0,0 +1,20 @@
+
+
+
+ |
+ ^~\&
+ LABMI1
+ DMCRES
+
+ 19951010134000
+
+
+ ORU
+ R01
+
+ LABMI1199510101340007
+ D
+ 2.2
+ AL
+
+
\ No newline at end of file