Removing all trailing delimiters for fields and segments in HL7 messages

Two fairly common requests we receive from HL7 customers are (1) how to remove all trailing delimiters for fields and segments in HL7 messages and (2) how to "find and replace" for an entire HL7 message (as opposed to one segment/field at a time).  The code sample below shows a custom function that solves for item 1 and by extension item 2 above.  In other words the same approach can be used for finding/replacing any sequence of chars in an entire HL7 message, with some tweaks to the custom function.

Here’s an example of how to actually call the function from DTL.  I recommend doing this as the last line of code in any DTL where it is used.  Note that “target” is used in both the value and property of the code below (as opposed to the DTL “source” message).

<assign value='##class(ISCSample.Custom.Function.ConversionScrub).ConversionScrub(target)' property='target' action='set' />

 

Here's the  sample class and classmethod:

 

Class ISCSample.Custom.Function.ConversionScrub Extends Ens.Rule.FunctionSet
{

/// This function removes trailing pipes "|" and/or up-carats "^" from the given HL7 message.
/// 
/// Per the HL7 standard, empty fields at the end of a segment need not be sent at all.
/// This also applies to empty sub-components at the end of a field.
/// For example, PID|1|12345||DOE^JOHN^^^^||||||||| is equivalent to PID|1|12345||DOE^JOHN.
/// 
/// We created this function to help with our file compares, so that Ensemble mimics the
/// behavior of Cerner Open Engine which removes trailing pipes automatically. By calling
/// this function, we ensure that Ensemble produces the same output as Cerner Open Engine.
ClassMethod ConversionScrub(pHL7 As EnsLib.HL7.Message) As EnsLib.HL7.Message
{
   set tSegCount = pHL7.SegCount
   set tCount = pHL7.SegCount
   while tCount > 0 {
   set tSegment = pHL7.GetValueAt(tCount)
   
   ; Remove trailing up carats
   while ..StartsWith(($REVERSE(tSegment)),"^") {
   set tLength = ..Length(tSegment)
   set tSegment = ..SubString(tSegment,1,(tLength-1))
   do pHL7.SetValueAt(tSegment,tCount)
   }

   ; Remove trailing up carats within a field
   set tIndex = 0
   while $FIND(tSegment,"^|",tIndex) > 0 {
   set tSegment = $Replace(tSegment,"^|","|")
   do pHL7.SetValueAt(tSegment,tCount)
   }
   
   ; Remove trailing pipes
   while ..StartsWith(($REVERSE(tSegment)),"|") {
   set tLength = ..Length(tSegment)
   set tSegment = ..SubString(tSegment,1,(tLength-1))
   do pHL7.SetValueAt(tSegment,tCount)
   }
   
   ; Remove empty segment
   if ..Length(tSegment) = 3 {
   do pHL7.SetValueAt("",tCount,"remove")
   }
   set tCount = tCount-1
   }
quit pHL7
}

}
 

Comments

Does this call do what you want?

USER>w $zstrip(pHL7,">","|^")
PID|1|12345||DOE^JOHN

No, it doesn't... pHL7 is an object of type EnsLib.HL7.Message, not a string... and also we need to remove the trailing chars not just from the end of a segment but within segments too... note the second example below which shows the carats remaining in PID:4 (my function would remove them):

 

USER>set pHL7 = "PID|1|12345||DOE^JOHN|^^^"
 
USER>w $zstrip(pHL7,">","|^")
PID|1|12345||DOE^JOHN
USER>set pHL7 = "PID|1|12345|myval^^^|DOE^JOHN|^^^"
 
USER>w $zstrip(pHL7,">","|^")
PID|1|12345|myval^^^|DOE^JOHN
USER>
 

I am having trouble implementing this in the DTL.  Were currently on  2012.2.5.

Would this version have an impact on this class from functioning?

I created the class as is, but renamed to: CUH.Other.StripDelimiterfunction

The DTL calling on the class is: 

<assign value='##class(CUH.Other.StripDelimiterfunction).ConversionScrub(target.{})' property='target.{}' action='set' />

When I run a test message I get the following error:

ERROR <Ens>ErrException: <INVALID OREF>zConversionScrub+1^CUH.Other.StripDelimiterfunction.1 -- logged as '-' number - @' set tSegCount = pHL7.SegCount'

')+'

'; content += ''; return content; }, "modalShow": function() { // add ensExceptionModalGroup to class for floating div var modalGroup = EnsException.modalGroup; if (modalGroup) { var div = modalGroup.getFloatingDiv(); if (div) div.className += ' ensExceptionModalGroup'; } // override default behaviour -- user must make a choice var mouseTrap = document.getElementById('zenMouseTrap'); if (mouseTrap) mouseTrap.onmouseup = null; }, "modalDelete": function() { // clean up floating div var modalGroup = EnsException.modalGroup; if (modalGroup) { var div = modalGroup.getFloatingDiv(); if (div && div.parentNode) { div.parentNode.removeChild(div); } } } } window.zenUserExceptionHandler = EnsException.exceptionHandler;
 

I managed to resolve the issue. I had created a new class to house the code above. I must of typo'd the DTL.

Here's my version of the same thing. (I realise the naming doesn't follow InterSystems' normal practice but I constantly swap between languages so follow other conventions.) It's worth noting that both the original version and my own actually change the original message passed into the function as well as returning this changed message - if this isn't the required behaviour, clone the message first and work on the clone.

/// Strip trailing null components from fields and trailing null fields from HL7 segments,
/// and optionally strip empty segments
ClassMethod TrimNullTrailingHL7(hl7Msg As EnsLib.HL7.Message, stripEmptySegments As %Boolean = 0) As EnsLib.HL7.Message
{
    #Dim segCount As %Integer = hl7Msg.SegCount
    #Dim sepField As %Char = ..SubString(hl7Msg.Separators, 1, 1)
    #Dim sepComponent As %Char = ..SubString(hl7Msg.Separators, 2, 2)

    While segCount > 0
    {
        #Dim segStr As EnsLib.HL7.Segment = hl7Msg.GetValueAt(segCount)

        // Remove trailing component separators
        Set segStr = $ZStrip(segStr, ">", sepComponent)

        // Remove trailing component separators within a field
        While $Find(segStr, sepComponent_sepField) > 0
        {
            Set segStr = $Replace(segStr, sepComponent_sepField, sepField)
        }

        // Remove trailing field separators
        Set segStr = $ZStrip(segStr, ">", sepField)

        If stripEmptySegments && ($Find(segStr, sepField) = 0)
        {
            Do hl7Msg.SetValueAt("", segCount, "remove")
        }
        Else
        {
            // Store resulting segment back in message
            Do hl7Msg.SetValueAt(segStr, segCount)
        }

        Set segCount = segCount - 1
    }
    Quit hl7Msg
}