Building a New Output Message¶
Most mapping work edits the message in place. Sometimes the destination needs a message in a different shape or format than what arrived, an HL7 ORU rebuilt as an MDM, or a database query rendered as a DICOM worklist XML document. For those cases, replace the working message with a new, empty message of the target format and populate it from source.
The qie.create*Message functions return a new, empty message and rebind message to it. After that, source still holds the original inbound message, so you can read from source while writing to the new message.
Change the output format¶
message = qie.createHL7Message(); // empty HL7
message = qie.createXMLMessage('<?xml version="1.0" encoding="UTF-8"?>\n<DATASETS/>');
message = qie.createTextMessage(); // empty plain text
The full set is listed under New Message Functions.
Example: rebuild an HL7 ORU as an MDM¶
This reads patient, visit, and report fields from the inbound ORU and assembles an MDM^T02 document message. Individual fields are set with setNode; whole segments are appended with addChild.
message = qie.createHL7Message();
message.setNode('MSH-3', source.getNode('MSH-3'));
message.setNode('MSH-4', source.getNode('MSH-4'));
message.setNode('MSH-5', source.getNode('MSH-5'));
message.setNode('MSH-6', source.getNode('MSH-6'));
message.setNode('MSH-7', source.getNode('MSH-7'));
message.setNode('MSH-9.1', 'MDM');
message.setNode('MSH-9.2', 'T02');
message.setNode('MSH-10', source.getNode('MSH-10'));
message.setNode('MSH-11', source.getNode('MSH-11'));
message.setNode('MSH-12', source.getNode('MSH-12'));
message.setNode('EVN-1', 'T02');
message.setNode('EVN-2', source.getNode('MSH-7'));
message.setNode('PID-3', source.getNode('PID-3'));
message.setNode('PID-5', source.getNode('PID-5'));
message.setNode('PID-7', source.getNode('PID-7'));
message.setNode('PID-8', source.getNode('PID-8'));
message.setNode('TXA-1', '1');
message.setNode('TXA-2', 'CM');
message.setNode('TXA-4', source.getNode('OBR-7'));
message.setNode('TXA-12', source.getNode('OBR-3'));
message.setNode('TXA-17', 'AU');
message.setNode('TXA-19', 'AV');
Add repeating segments in a loop¶
addChild(nodePath, segmentName, segmentText) appends a segment to the message. For HL7 the nodePath must be the root, '/'. When the segment text begins with the segment name, the whole line is parsed as that segment.
This splits the report narrative, stored in OBX-5 with embedded \.br\ HL7 line-break escapes, into one OBX per line, skipping blank lines:
var reportText = source.getNode('OBX-5');
var reportLines = StringUtils.splitByWholeSeparatorPreserveAllTokens(reportText, '\\.br\\');
var provider = source.getNode('OBX-16');
var obxIndex = 1;
for (var i = 0; i < reportLines.length; i++) {
if (StringUtils.isNotBlank(reportLines[i])) {
message.addChild('/', 'OBX',
'OBX|' + obxIndex + '|FT|REPORT^Report Text||' + reportLines[i] + '||||||F|||||' + provider);
obxIndex++;
}
}
splitByWholeSeparatorPreserveAllTokens splits on the literal separator and keeps empty tokens, so consecutive \.br\\.br\ markers are preserved as blank entries; the isNotBlank guard then drops them. Use StringUtils.split instead if you do not care about preserving empty positions.
Look up a value while mapping¶
To translate a code during the rebuild (for example mapping an ordering provider's NPI to the destination's EMR id) use qie.doTableLookup(value, notFoundValue, tableName, sourceColumn, targetColumn) against a system variable table. Passing the original value as notFoundValue lets unmatched codes pass through unchanged:
var npi = source.getNode('OBR-16.1');
message.setNode('TXA-5',
qie.doTableLookup(npi, npi, 'Radiology Providers', 'NPI', 'EMR'));
Example: build an XML document from a database query¶
You can render the rows of a database query as an XML document in whatever shape the receiving system expects. Create the XML message, then append one element per row returned from a parameterized query. Every value read from the database is passed through qie.escapeXml so characters like &, <, and > cannot break the document.
The example below produces the DATASET/ATTRIBUTE structure one downstream tool happens to consume. The element and attribute names are dictated by that destination (they are not a QIE or DICOM standard) so adapt them to whatever your own destination requires.
var pQuery = qie.getParameterizedQuery(
'SELECT name, patient_id, birth_date, sex, order_num, physician ' +
'FROM rad_work_list ' +
"WHERE name NOT LIKE '%<MRG>%'");
var rows = pQuery.doSelectQuery('Quantum');
message = qie.createXMLMessage('<?xml version="1.0" encoding="UTF-8"?>\n<DATASETS/>');
for (var i = 1; rows != null && i <= rows.getRowCount(); i++) {
var birthDate = rows.getNode('birth_date', i);
if (StringUtils.isNotBlank(birthDate)) {
birthDate = qie.formatDate('yyyyMMdd', birthDate);
}
message.addChildNode('/DATASETS', 'DATASET',
'<DATASET>' +
'<ATTRIBUTE TAG="0010,0010" TEXT="PatientName">' + qie.escapeXml(rows.getNode('name', i)) + '</ATTRIBUTE>' +
'<ATTRIBUTE TAG="0010,0020" TEXT="PatientID">' + qie.escapeXml(rows.getNode('patient_id', i)) + '</ATTRIBUTE>' +
'<ATTRIBUTE TAG="0010,0030" TEXT="PatientBirthDate">' + qie.escapeXml(birthDate) + '</ATTRIBUTE>' +
'<ATTRIBUTE TAG="0010,0040" TEXT="PatientSex">' + qie.escapeXml(rows.getNode('sex', i)) + '</ATTRIBUTE>' +
'<ATTRIBUTE TAG="0040,1001" TEXT="PlacerOrderNumber">'+ qie.escapeXml(rows.getNode('order_num', i)) + '</ATTRIBUTE>' +
'<ATTRIBUTE TAG="0008,0090" TEXT="ReferringPhysician">' + qie.escapeXml(rows.getNode('physician', i)) + '</ATTRIBUTE>' +
'</DATASET>');
}
addChildNode(nodePath, nodeName, value) appends a child to the first node matching nodePath (here the <DATASETS> root) so each loop pass adds one more DATASET.
Always escape values placed into XML or SQL
Database and message values can contain characters that are significant in the target format. Wrap each value with qie.escapeXml when building XML (or qie.escapeHL7 for HL7 field content), and use parameterized queries rather than concatenating values into SQL. Skipping this produces malformed output and opens an injection path.