Mirth Tools: User defined functions

04-17-2012, 10:32 AM

Hi,

Out of a desire to learn, partial boredom, and a double-dog dare, I decided to create a thread topic to list some things that I find useful when dealing with Mirth, creating Channels, or fine-tuning the database.

First, I'd figure I would start out with some functions that some may find useful when developing channels.

Feel free to use them, tweak them, or add more to this thread of others to gaze upon. I hope to start other threads with other tools/scripts/code examples to assist in making our daily Mirth Connect experience that more enjoyable and less migraine inducing.

These functions can be defined and called from within a single Channel itself, or placed into the Code Template section for use throughout all of your Channels.

1. Determine a Channel's status
Purpose: To determine the current state of a channel.

Sometimes, you need to know the status of a certain channel, from within another channel. To accomplish this, you can use the following to see if the channel is :
-STARTED
-STOPPED
-PAUSED
-NA (Non-available)

To start with I only added the ones I wrote, because I didn't want to presume and add code from others without their explicit permission. Pull requests welcome!

Haha, I'll play along:

xmlToHL7

Deserializes an E4X XML node into HL7 v2.x. This allows you to pass any E4X node of a message and convert it into HL7 v2.x correctly. Here's the code (I've also attached it as a code template):

Code:

function xmlToHL7(node,fieldSeparator,componentSeparator,repetitionMarker,subcomponentSeparator) {
// Data validation
if (!node) return '';
// This shouldn't really ever happen with E4X anyway, but just in case.
String.prototype.replaceAmp = function() {return this.replace(/&amp;/g,'&');};
// If we're just dealing with a simple node, then just return its contents.
if (node.hasSimpleContent()) return node.toString().replaceAmp();
// Used for StringUtils
importPackage(org.apache.commons.lang);
// Defaults to standard HL7 encoding characters
var fs = fieldSeparator || '|';
var cs = componentSeparator || '^';
var rm = repetitionMarker || '~';
var ss = subcomponentSeparator || '&';
var cr = '\x0D';
// What will actually be returned
var output = '';
// Get the XML name of the node (in the case of an XMLList of repeating fields, the first one will be returned, since they all have the same name anyway)
var qname = node[0].name().toString();
// Use the HL7 dot notation to find what level we're at
var level = StringUtils.countMatches(qname,'.');
// If the name is HL7Message, we're at the root node
if (qname == 'HL7Message')
// Recursively append serialization for each segment
for each (segment in node.children())
output += xmlToHL7(segment,fs,cs,rm,ss);
// If we're at the segment level
else if (level == 0) {
// If the node is an XMLList of multiple segments
if (node.length() > 1) {
// Recursively append serialization for each segment
for each (segment in node)
output += xmlToHL7(segment,fs,cs,rm,ss);
}
else {
// Add the segment name to the output
output += qname;
// Initialize name placeholder
var prevName = '';
// Iterate through each field in the segment
for each (field in node.children()) {
// Get the QName of the field
var fieldName = field.name().toString();
// If we're dealing with the special cases of MSH.1/2, then just add the field contents
if (fieldName in {'MSH.1':1,'MSH.2':1})
output += field.toString().replaceAmp();
// Otherwise add the recursive serialization of the field
else
// If we're on a field repetition, then prepend a repetition marker, otherwise prepend a field separator
output += (prevName==fieldName?rm:fs) + xmlToHL7(field,fs,cs,rm,ss);
// Update the field name placeholder
prevName = fieldName;
}
// Add a carriage return to the end of the segment
output += cr;
}
}
// If we're at the field level
else if (level == 1) {
// If the node is an XMLList of multiple fields
if (node.length() > 1) {
// Recursively append serialization for each field
for each (field in node)
output += xmlToHL7(field,fs,cs,rm,ss) + rm;
// Remove the final repetition marker
output = StringUtils.chomp(output,rm);
}
else {
// Recursively append serialization for each component
for each (component in node.children())
// Append a component separator to the end
output += xmlToHL7(component,fs,cs,rm,ss) + cs;
// Remove the last component separator
output = StringUtils.chomp(output,cs);
}
}
// If we're at the component level
else if (level == 2) {
// Recursively append serialization for each subcomponent
for each (subcomponent in node.children())
// Append a subcomponent separator to the end
output += xmlToHL7(subcomponent,fs,cs,rm,ss) + ss;
// Remove the last subcomponent separator
output = StringUtils.chomp(output,ss);
}
// If we're at the subcomponent level
else
// Just add the contents of the node
output = node.toString().replaceAmp();
// Return the final output
return output;
}

Nicholas Rupley
Work: 949-237-6069
Always include what Mirth Connect version you're working with. Also include (if applicable) the code you're using and full stacktraces for errors (use CODE tags). Posting your entire channel is helpful as well; make sure to scrub any PHI/passwords first.

Issue 625 is scheduled to be fixed in 3.0, but in the meantime, here's another way to fix it:

fixHL7NodeOrder takes in a single HL7 v2.x node (it can be the root HL7Message, or a single segment/field/component/etc.), and returns the same node, with all children sorted in ascending order as per the HL7 dot notation. So the following node:

Here's the code, or you can just import the attached code template. Cheers!

Code:

/*
Author: Nick Rupley
Date Modified: 4/18/2012
fixHL7NodeOrder: Returns a new E4X node where the order of all siblings and descendants have been fixed as per the Mirth HL7 dot notation convention.
Arguments
---------
Required
--------
node: The node to be fixed.
*/
function fixHL7NodeOrder(node) {
// Create output node
var newNode = new XML();
// In case the node is an XMLList of multiple siblings, loop through each sibling
for each (sibling in node) {
// Create new sibling node
var newSibling = new XML('<'+sibling.name().toString()+'/>');
// Iterate through each child node
for each (child in sibling.children())
// If the child has its own children, then recursively fix the node order of the child
if (child.hasComplexContent())
newSibling.appendChild(fixHL7NodeOrder(child));
// If the child doesn't have its own children, then just add the child to the new sibling node
else
newSibling.appendChild(child);
// After recursively fixing all of the child nodes, now we'll fix the current node
newNode += sortHL7Node(newSibling);
}
// Return the fixed node
return newNode;
}
// Helper function for fixHL7NodeOrder
function sortHL7Node(node) {
// If the node has no children, then there's nothing to sort
if (node.hasSimpleContent())
return node;
// Create new output node
var newNode = new XML('<'+node.name().toString()+'/>');
// Iterate through each child in the node
for each (child in node.children()) {
// If the child has a QName, then we can sort on it
if (child.name()) {
// Get the current "index" of the child. Id est, if the QName is PID.3.1, then the index is 1
curChildIndex = parseInt(child.name().toString().substring(child.name().toString().lastIndexOf('.')+1),10);
// Boolean placeholder
var inserted = false;
// Iterate through each child currently in the NEW node
for (var i = 0; i <= newNode.children().length()-1; i++) {
// Get the index of the child of the new node
loopChildIndex = parseInt(newNode.child(i).name().toString().substring(newNode.child(i).name().toString().lastIndexOf('.')+1),10);
// If the child we want to insert has a lower index then the current child of the new node, then we're going to insert the child
// right before the current newNode child
if (curChildIndex < loopChildIndex) {
// Insert the child
newNode.insertChildBefore(newNode.children()[i],child);
// Set our flag, indicating that an insertion was made
inserted = true;
// No need to continue iteration
break;
}
}
// If no insertion was made, then the index of the child we want to insert is greater than or equal to all of the
// indices of the children that have already been inserted in newNode. So, we'll just append the child to the end.
if (!inserted)
newNode.appendChild(child);
}
}
// Return the sorted HL7 node
return newNode;
}

Nicholas Rupley
Work: 949-237-6069
Always include what Mirth Connect version you're working with. Also include (if applicable) the code you're using and full stacktraces for errors (use CODE tags). Posting your entire channel is helpful as well; make sure to scrub any PHI/passwords first.

To start with I only added the ones I wrote, because I didn't want to presume and add code from others without their explicit permission. Pull requests welcome!

UPDATE 2-13-13: Made a change so that segName can be a regular expression

Okay, this one might be helpful to some people....

getSegmentsAfter: Returns an array of segments with the specified name that come after a given segment in the message.

Say you have multiple OBR groups in your message. Sometimes you want to do something with only the OBXs that come after that particular OBR, and not necessarily all of the OBXs in the entire message. The same goes for NTEs after an OBX, etc. Assuming that you're not using the strict HAPI parser (most of us don't), this might be fairly tricky to do.

The getSegmentsAfter function does it for you. Given the root node (root), the segment you want to start at (startSeg), and the name of the segments you want to get (segName), this function will return an array of those segments.

There are two other optional parameters:

consecutiveInd: If this is true, then the segments to be collected are expected to come directly after the starting segment. If any segment is encountered that doesn't have a name of segName, collection is immediately stopped. If this is false, then collection is stopped when another segment of the same name as startSeg is encountered. For example, say you have the following message:

Code:

MSH...
PID...
OBR...
OBX...
OBX...
NTE...
OBX...
OBR...
OBX...

If startSeg is the first OBR, segName is "OBX", and consecutiveInd is true, then getSegmentsAfter will return the first two OBXs in the message, but not the third one. If consecutiveInd is false, then it will return the first three OBXs, but not the fourth one.

stopSegNames: This is an array of segment names that you want to act as an "emergency brake" to stop collection. If one of these segments is encountered, then iteration is stopped. This is useful when, for example, you have a message like this:

Code:

MSH...
PID...
OBR...
OBX...
NTE...
OBX...
SPM...
OBX...

In HL7Land, the OBR segment contains two groups, the observation group (consisting of the first two OBXs) and the specimen group (consisting of the SPM and the third OBX). In this case you don't want to get consecutive OBXs because there may be NTEs/ZDSs in between, but on the other hand you don't want to include the OBX that is part of the specimen group. So if "SPM" is included in stopSegNames, then all will be good.

Examples

Get all OBXs after the first OBR segment:

Code:

getSegmentsAfter(msg,msg.OBR[0],'OBX')

Get all consecutive NTEs after the first OBX segment:

Code:

getSegmentsAfter(msg,msg.OBX[0],'NTE',true)

Get all OBXs after the first OBR segment, but don't include any OBXs in the SPM or ZBP groups:

Code:

getSegmentsAfter(msg,msg.OBR[0],'OBX',false,['SPM','ZBP'])

Add a new NTE segment to the first OBR group (at the order level rather than the observation level):

for each (obr in msg.OBR) {
for each (zseg in getSegmentsAfter(msg,obr,/Z[A-Z\d]{2}/)) {
// Do something
}
}

The code:

Code:

/*
Author: Nick Rupley
Date Modified: 2/13/2013
getSegmentsAfter: Returns an array of segments with the specified name that come after a given segment in the message.
Arguments
---------
Required
--------
root: The root HL7Message node of the message, or the parent of the segment node.
startSeg: The segment AFTER which to start collecting segments.
segName: The name (String or RegExp) of the segments you want to collect.
Optional
--------
consecutiveInd: If true, indicates that the segments are expected to come directly after startSeg.
If false, segments are collected until another segment with the same name as startSeg is encountered.
Defaults to false.
stopSegNames: An array of segment names that, when encountered, stop the collection of segments.
*/
function getSegmentsAfter(root, startSeg, segName, consecutiveInd, stopSegNames) {
function test(str) {
return segName instanceof RegExp ? segName.test(str) : segName === str;
}
// The index to start collection is the next one up from the starting segment
var index = startSeg.childIndex()+1;
// The return array
var out = [];
// Boolean placeholder to stop iteration
var done = false;
// Object that will contain all of the stopSegNames strings, bound to a truthy value (1)
var stopNames = {};
// Indicates whether we have any stop segments
var stopNamesInd = false;
// If stopSegNames is defined
if (stopSegNames !== undefined && stopSegNames !== null) {
// Set our indicator to true
stopNamesInd = true;
// Add each string in the array to our object
for each (name in stopSegNames)
stopNames[name] = 1;
}
// Iterate through each child in the root, starting at the segment after startSeg, and
// ending at the final segment, or when the done flag is set to true.
while (index < root.children().length() && !done) {
// If a stop segment is encountered, stop iteration
if (stopNamesInd && root.children()[index].name().toString() in stopNames)
done = true;
// If a segment with the same name as startSeg is encountered, stop iteration
else if (root.children()[index].name().toString() == startSeg.name().toString() && !consecutiveInd)
done = true;
// If we're only collecting consecutive segments and we encounter a segment with a name other than segName, stop iteration
else if (!test(root.children()[index].name().toString()) && consecutiveInd)
done = true;
// If all previous tests passed, and the current segment has a name of segName, then add it to our array
else if (test(root.children()[index].name().toString()))
out.push(root.children()[index]);
// Increment our index counter
index++;
}
// Return the output array
return out;
}

Nicholas Rupley
Work: 949-237-6069
Always include what Mirth Connect version you're working with. Also include (if applicable) the code you're using and full stacktraces for errors (use CODE tags). Posting your entire channel is helpful as well; make sure to scrub any PHI/passwords first.

- How do I foo?
- You just bar.

1 like

Comment

NOTE: This code template is probably not necessary for 3.0 because we now have the ChannelUtil utility class, which has methods to deploy/undeploy/start/stop/etc. channels and connectors. However you can still use this to deploy channels on a different instance of Mirth Connect.

deployChannels: Deploys channels using an array of channel IDs and an optional client info object.

Rather self-explanatory; you just pass it an array or comma-separated string of channel IDs, and it will initiate a Client and deploy the specified channels. Client connection settings are hard-coded as defaults, but they can be overridden via the optional clientInfo argument.

The function returns an object containing a status String element, a message String element, and an overloaded toString method. The status will be "SUCCESS" if the client was able to successfully login, and at least one of the channel IDs refers to an enabled channel. Otherwise, the status is "FAILURE".

Nicholas Rupley
Work: 949-237-6069
Always include what Mirth Connect version you're working with. Also include (if applicable) the code you're using and full stacktraces for errors (use CODE tags). Posting your entire channel is helpful as well; make sure to scrub any PHI/passwords first.

- How do I foo?
- You just bar.

Comment

Date(Time) is always a pain in the **** especially with javascript.
So we ended up developing a javascript object that does the translation.
note the date is still string based note a javascript Date object.

//Make a new InternalDateTimeObject instance.
var date = new InternalDateTimeObject();
//You can set the currentdate in the object
date.SetCurrentDate();
//or set a specific date(time)
date.SetFromDBDateTimeEN("2012-04-30 12:58:57");
//And if you have special formatting you can use this option
date.SetDateFromCustomTemplate ("yyyy/dd/MM/", "2012/03/04") ;
//And this is how you can get the date(times) back.
var englishDateTime = date.GetDBDateTimeEN();
var HL7DateTime = date.GetHL7UniversalDateTimeWithMilliseconds();;
//Here are all the getters
date.GetHL7UniversalDateTimeWithMilliseconds();;
date.GetHL7UniversalDateTime();
date.GetHL7UniversalDate();
date.GetDBDateTimeEN();
date.GetDBDateEN();
date.GetDBDateTimeNL();
date.GetDBDateNL();
date.GetTime();
date.GetTimeWithMilliseconds();
date.GetWeek();
//And here are all the setters
date.SetCurrentDate();
date.SetFromDBDateNL("30-04-2012");
date.SetFromDBDateEN("2012-04-30");
date.SetFromDBDateTimeNL("30-04-2012 12:58:57");
date.SetFromDBDateTimeEN("2012-04-30 12:58:57");
date.SetFromHL7UniversalDate("20120430");
date.SetFromHL7UniversalDateTime(20120430125857)
date.GetDateFromCustomTemplate ("yyyy/dd/MM/");
date.SetDateFromCustomTemplate ("yyyy/dd/MM/", "2012/03/04") ;

Nicholas Rupley
Work: 949-237-6069
Always include what Mirth Connect version you're working with. Also include (if applicable) the code you're using and full stacktraces for errors (use CODE tags). Posting your entire channel is helpful as well; make sure to scrub any PHI/passwords first.

We process personal data about users of our site, through the use of cookies and other technologies, to deliver our services, personalize advertising, and to analyze site activity. We may share certain information about our users with our advertising and analytics partners. For additional details, refer to our Privacy Policy.

By clicking "I AGREE" below, you agree to our Privacy Policy and our personal data processing and cookie practices as described therein. You also acknowledge that this forum may be hosted outside your country and you consent to the collection, storage, and processing of your data in the country where this forum is hosted.