Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 2 new Parser Options 'DefaultObx2Type' and 'InvalidObx2Type' #250

Merged
merged 1 commit into from
Nov 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/NHapi.Base/Model/AbstractSegment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ public virtual int GetMaxCardinality(int number)
/// </summary>
/// <param name="fieldNum">Repeatable field number.</param>
/// <param name="index">0-based index to be removed.</param>
/// <exception cref="HL7Exception">If field has no repetitions.</exception>
/// <exception cref="HL7Exception">If field index does not exist.</exception>
public void RemoveRepetition(int fieldNum, int index)
{
if (fieldNum < 1 || fieldNum > items.Count)
Expand Down Expand Up @@ -303,6 +305,9 @@ public void RemoveRepetition(int fieldNum, int index)
/// </summary>
/// <param name="fieldNum">Repeatable field number.</param>
/// <param name="removeItem">Item to be removed.</param>
/// <exception cref="HL7Exception">If field number is not valid.</exception>
/// <exception cref="HL7Exception">If field has no repetitions.</exception>
/// <exception cref="HL7Exception">If field does not contain the repetition to remove.</exception>
public void RemoveRepetition(int fieldNum, IType removeItem)
{
if (fieldNum < 1 || fieldNum > items.Count)
Expand Down
57 changes: 48 additions & 9 deletions src/NHapi.Base/Model/Varies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ static Varies()
Log = HapiLogFactory.GetHapiLog(typeof(Varies));
}

/// <summary> Creates new Varies.
///
/// <summary>
/// Creates new Varies.
/// </summary>
/// <param name="message">message to which this type belongs.
/// </param>
Expand All @@ -75,8 +75,8 @@ public Varies(IMessage message)
Message = message;
}

/// <summary> Creates new Varies.
///
/// <summary>
/// Creates new Varies.
/// </summary>
/// <param name="message">message to which this type belongs.</param>
/// <param name="description">description of what this Type represents.</param>
Expand Down Expand Up @@ -139,15 +139,26 @@ public virtual IType Data
"StyleCop.CSharp.NamingRules",
"SA1300:Element should begin with upper-case letter",
Justification = "As this is a public member, we will duplicate the method and mark this one as obsolete.")]
public static void fixOBX5(ISegment segment, IModelClassFactory factory)
public static void fixOBX5(ISegment segment, IModelClassFactory factory, ParserOptions parserOptions)
{
FixOBX5(segment, factory);
FixOBX5(segment, factory, parserOptions);
}

/// <summary> Sets the data type of field 5 in the given OBX segment to the value of OBX-2. The argument
/// <summary>
/// Sets the data type of field 5 in the given OBX segment to the value of OBX-2. The argument
/// is a Segment as opposed to a particular OBX because it is meant to work with any version.
/// <para>
/// Note that if no value is present in OBX-2, or an invalid value is present in
/// OBX-2, this method will throw an error.This behaviour can be corrected by using the
/// <see cref="ParserOptions.DefaultObx2Type"/> and <see cref="ParserOptions.InvalidObx2Type"/>.
/// </para>
/// </summary>
public static void FixOBX5(ISegment segment, IModelClassFactory factory)
/// <param name="segment"><see cref="ISegment"/> instance.</param>
/// <param name="factory"><see cref="IModelClassFactory"/> to be used.</param>
/// <param name="parserOptions"><see cref="ParserOptions"/> to be used.</param>
/// <exception cref="HL7Exception">If no value is present in OBX-2.</exception>
/// <exception cref="HL7Exception">If an invalid value is present in OBX-2.</exception>
public static void FixOBX5(ISegment segment, IModelClassFactory factory, ParserOptions parserOptions)
{
try
{
Expand All @@ -157,6 +168,7 @@ public static void FixOBX5(ISegment segment, IModelClassFactory factory)
foreach (var repetition in segment.GetField(5))
{
var v = (Varies)repetition;
SetObx2Fallback(obx2, segment, parserOptions);

if (obx2.Value == null)
{
Expand All @@ -174,7 +186,7 @@ public static void FixOBX5(ISegment segment, IModelClassFactory factory)
{
UseDTInsteadOfDTMForEarlierVersionsOfHL7(segment, obx2);

var type = factory.GetTypeClass(obx2.Value, segment.Message.Version);
var type = GetObx5Type(obx2, segment, factory, parserOptions);

if (type == null)
{
Expand Down Expand Up @@ -214,6 +226,33 @@ public static void FixOBX5(ISegment segment, IModelClassFactory factory)
}
}

private static Type GetObx5Type(IPrimitive obx2, ISegment segment, IModelClassFactory factory, ParserOptions parserOptions)
{
var type = factory.GetTypeClass(obx2.Value, segment.Message.Version);

if (type == null)
{
if (parserOptions.InvalidObx2Type != null)
{
type = factory.GetTypeClass(parserOptions.InvalidObx2Type, segment.Message.Version);
}
}

return type;
}

private static void SetObx2Fallback(IPrimitive obx2, ISegment segment, ParserOptions parserOptions)
{
if (obx2.Value == null)
{
if (!(parserOptions.DefaultObx2Type is null))
{
Log.Debug($"setting default {segment.GetStructureName()}-{2} type to {parserOptions.DefaultObx2Type}");
obx2.Value = parserOptions.DefaultObx2Type;
}
}
}

private static void UseDTInsteadOfDTMForEarlierVersionsOfHL7(ISegment segment, IPrimitive obx2)
{
var versionsWithoutDTM = new string[] { "2.1", "2.2", "2.3", "2.3.1", "2.4", "2.5" };
Expand Down
37 changes: 24 additions & 13 deletions src/NHapi.Base/Parser/DefaultXMLParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,23 +142,33 @@ public override XmlDocument EncodeDocument(IMessage source)
/// <throws> EncodingNotSupportedException if the message encoded. </throws>
/// <summary> is not supported by this parser.
/// </summary>
public override IMessage ParseDocument(XmlDocument xmlMessage, string version)
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);
Parse(message, xmlMessage.DocumentElement, parserOptions);
return message;
}

/// <inheritdoc />
public override void Parse(IMessage message, string @string, ParserOptions parserOptions)
{
if (parserOptions is null)
{
throw new ArgumentNullException(nameof(parserOptions));
}

try
{
var xmlDocument = new XmlDocument();
xmlDocument.Load(new StringReader(@string));

Parse(message, xmlDocument.DocumentElement);
Parse(message, xmlDocument.DocumentElement, parserOptions);
}
catch (XmlException e)
{
Expand Down Expand Up @@ -201,7 +211,7 @@ protected internal static string MakeGroupElementName(string messageName, string
/// <summary> Populates the given group object with data from the given group element, ignoring
/// any unrecognized nodes.
/// </summary>
private void Parse(IGroup groupObject, XmlElement groupElement)
private void Parse(IGroup groupObject, XmlElement groupElement, ParserOptions parserOptions)
{
var childNames = groupObject.Names;
var messageName = groupObject.Message.GetStructureName();
Expand All @@ -222,14 +232,14 @@ private void Parse(IGroup groupObject, XmlElement groupElement)
for (var i = 0; i < childNames.Length; i++)
{
SupportClass.ICollectionSupport.Remove(unparsedElementList, childNames[i]);
ParseReps(groupElement, groupObject, messageName, childNames[i], 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);
ParseReps(groupElement, groupObject, messageName, segName, segIndexName, parserOptions);
}
}

Expand Down Expand Up @@ -284,7 +294,8 @@ private void ParseReps(
IGroup groupObject,
string messageName,
string childName,
string childIndexName)
string childIndexName,
ParserOptions parserOptions)
{
var reps = GetChildElementsByTagName(groupElement, MakeGroupElementName(messageName, childName));
Log.Debug("# of elements matching " + MakeGroupElementName(messageName, childName) + ": " + reps.Count);
Expand All @@ -293,36 +304,36 @@ private void ParseReps(
{
for (var i = 0; i < reps.Count; i++)
{
ParseRep((XmlElement)reps[i], groupObject.GetStructure(childIndexName, i));
ParseRep((XmlElement)reps[i], groupObject.GetStructure(childIndexName, i), parserOptions);
}
}
else
{
if (reps.Count > 0)
{
ParseRep((XmlElement)reps[0], groupObject.GetStructure(childIndexName, 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));
ParseRep((XmlElement)reps[i], groupObject.GetStructure(newIndexName, i - 1), parserOptions);
}
}
}
}

private void ParseRep(XmlElement theElem, IStructure theObj)
private void ParseRep(XmlElement theElem, IStructure theObj, ParserOptions parserOptions)
{
if (theObj is IGroup)
{
Parse((IGroup)theObj, theElem);
Parse((IGroup)theObj, theElem, parserOptions);
}
else if (theObj is ISegment)
{
Parse((ISegment)theObj, theElem);
Parse((ISegment)theObj, theElem, parserOptions);
}

Log.Debug("Parsed element: " + theElem.Name);
Expand Down
27 changes: 23 additions & 4 deletions src/NHapi.Base/Parser/LegacyPipeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,11 @@ public static string StripLeadingWhitespace(string in_Renamed)

public override void Parse(IMessage message, string @string, ParserOptions parserOptions)
{
if (parserOptions is null)
{
throw new ArgumentNullException(nameof(parserOptions));
}

var messageIter = new Util.MessageIterator(message, "MSH", true);
FilterIterator.IPredicate segmentsOnly = new AnonymousClassPredicate(this);
var segmentIter = new FilterIterator(messageIter, segmentsOnly);
Expand Down Expand Up @@ -299,7 +304,7 @@ public override void Parse(IMessage message, string @string, ParserOptions parse

if (dirIter.MoveNext())
{
Parse((ISegment)dirIter.Current, segments[i], encodingChars);
Parse((ISegment)dirIter.Current, segments[i], encodingChars, parserOptions);
}
}
}
Expand Down Expand Up @@ -384,15 +389,29 @@ public virtual string GetMessageStructure(string message)
return GetStructure(message).Structure;
}

/// <summary> Parses a segment string and populates the given Segment object. Unexpected fields are
/// <summary>
/// Parses a segment string and populates the given Segment object. Unexpected fields are
/// added as Varies' at the end of the segment.
///
/// </summary>
/// <throws> HL7Exception if the given string does not contain the. </throws>
/// <summary> given segment or if the string is not encoded properly.
/// </summary>
public virtual void Parse(ISegment destination, string segment, EncodingCharacters encodingChars)
{
Parse(destination, segment, encodingChars, DefaultParserOptions);
}

/// <summary>
/// Parses a segment string and populates the given Segment object. Unexpected fields are
/// added as Varies' at the end of the segment.
/// </summary>
/// <throws> HL7Exception if the given string does not contain the. </throws>
/// <summary> given segment or if the string is not encoded properly.
/// </summary>
public virtual void Parse(ISegment destination, string segment, EncodingCharacters encodingChars, ParserOptions parserOptions)
{
parserOptions = parserOptions ?? DefaultParserOptions;

var fieldOffset = 0;
if (IsDelimDefSegment(destination.GetStructureName()))
{
Expand Down Expand Up @@ -451,7 +470,7 @@ public virtual void Parse(ISegment destination, string segment, EncodingCharacte
// set data type of OBX-5
if (destination.GetType().FullName.IndexOf("OBX") >= 0)
{
Varies.FixOBX5(destination, Factory);
Varies.FixOBX5(destination, Factory, parserOptions);
}
}

Expand Down
9 changes: 7 additions & 2 deletions src/NHapi.Base/Parser/ParserBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ namespace NHapi.Base.Parser
/// <author>Bryan Tripp (bryan_tripp@sourceforge.net).</author>
public abstract class ParserBase
{
private static readonly IHapiLog Log;
private static readonly ParserOptions DefaultParserOptions = new ParserOptions();
protected static readonly ParserOptions DefaultParserOptions = new ParserOptions();

private static readonly IHapiLog Log;
private IValidationContext validationContext;
private MessageValidator messageValidator;

Expand Down Expand Up @@ -265,6 +265,11 @@ public virtual IMessage Parse(string message, string version)
/// <exception cref="ArgumentNullException">If <paramref name="parserOptions"/> is null.</exception>
public virtual IMessage Parse(string message, string version, ParserOptions parserOptions)
{
if (parserOptions is null)
{
throw new ArgumentNullException(nameof(parserOptions));
}

var encoding = GetEncoding(message);
if (!SupportsEncoding(encoding))
{
Expand Down
42 changes: 42 additions & 0 deletions src/NHapi.Base/Parser/ParserOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,51 @@ public class ParserOptions
{
public ParserOptions()
{
DefaultObx2Type = null;
InvalidObx2Type = null;
NonGreedyMode = false;
}

/// <summary>
/// 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
/// when parsing messages from systems which do not correctly populate OBX-2.
/// </summary>
/// <example>
/// <para>
/// For example, if this property is set to "ST", and the following OBX
/// segment is encountered:
/// <code>
/// OBX|||||This is a value
/// </code>
/// It will be parsed as though it had read:
/// <code>
/// OBX||ST|||This is a value
/// </code>
/// </para>
/// </example>
public string DefaultObx2Type { get; set; }

/// <summary>
/// If this property is set, the value provides a default datatype ("ST",
/// "NM", etc) for an OBX segment with an invalid OBX-2 value. This is useful
/// when parsing messages from systems which do not correctly populate OBX-2.
/// </summary>
/// <example>
/// <para>
/// For example, if this property is set to "ST", and the following OBX
/// segment is encountered:
/// <code>
/// OBX||INVALID|||This is a value
/// </code>
/// It will be parsed as though it had read:
/// <code>
/// OBX||ST|||This is a value
/// </code>
/// </para>
/// </example>
public string InvalidObx2Type { get; set; }

/// <summary>
/// If set to <c>true</c> (default is <c>false</c>), pipe parser will be put in non-greedy mode. This setting
/// applies only to <see cref="PipeParser"/> and will have no effect on <see cref="XMLParser"/>.
Expand Down
Loading