Transformer-Handbuch
Das Herzstück: Feldpfade lesen und schreiben, Segmente anlegen, Wiederholfelder, Escapes — in Groovy, GraalJS und Rhino, mit Beispielen, die genau so laufen.
Stand: Beispiele geprüft gegen brio-server fa237ec7ad
Ein Integrationsserver verbringt die meiste Zeit damit, Nachrichten umzuschreiben. In Brio-IO passiert das im Transformer. Dieses Kapitel ist die Referenz dafür: jedes Beispiel ist gegen den laufenden Server geprüft, in allen drei Engines.
Falls du das Modell dahinter noch nicht kennst — kanonisches XML, Underscores, positionale Komponenten — lies zuerst Konzepte.
Die Regel für Feldpfade
- Einfaches Feld → direkt:
msg.MSH.MSH_10 - Zusammengesetztes Feld → eine Ebene je Komponente:
msg.PID.PID_5.PID_5_1 - Subkomponente → noch eine Ebene:
msg.PID.PID_5.PID_5_1_1
Ein Zugriff auf einen Pfad, den es nicht gibt, wirft nicht — er liefert einen leeren String. Das ist Absicht und entspricht dem Verhalten, das Mirth-Praktiker kennen: eine Nachricht ohne das erwartete Feld soll die Verarbeitung nicht abbrechen.
msg ist das Wurzel-Element. Du steigst also unterhalb der Wurzel ein —
kein ADT_A01-Präfix.
Lesen
// Groovy — .text() liefert den Feldwert
def nachname = msg.PID.PID_5.PID_5_1.text() // "Schmidt"
def vorname = msg.PID.PID_5.PID_5_2.text() // "Hans"
// GraalJS (js-modern) — ein Text-Feld liest sich direkt als String
let nachname = msg.PID.PID_5.PID_5_1; // "Schmidt"
let patId = msg.PID.PID_3.PID_3_1; // "12345"
// Rhino (js-legacy) — E4X-Brackets mit Punkt-Notation, wie in Mirth
var nachname = msg['PID']['PID.5']['PID.5.1'].toString();
Dass Rhino die Punkt-Variante derselben Nachricht sieht, erledigt der Pipeline-Prozessor. Du musst dich nicht darum kümmern.
Schreiben und zurückgeben
Ein Transformer verändert das geparste msg-Objekt und gibt es zurück. Die
Engine serialisiert an der Schritt-Grenze automatisch zurück nach kanonischem
XML — von Hand serialisieren musst du nichts.
// Groovy: explizites return, Wert am Knoten über [0].value setzen
def nachname = msg.PID.PID_5.PID_5_1.text()
msg.PID.PID_5.PID_5_1[0].value = nachname.toUpperCase()
return msg
// GraalJS: direkte Zuweisung, letzter Ausdruck ist das Ergebnis
msg.PID.PID_5.PID_5_1 = msg.PID.PID_5.PID_5_1.toUpperCase();
msg;
// Rhino: Feld setzen, msg als letzter Ausdruck
msg['PID']['PID.5']['PID.5.1'] = 'Mueller';
msg;
Der Unterschied zwischen Groovy und GraalJS ist nur Sprach-Idiom: Groovy braucht
ein explizites return und setzt den Wert am Knoten ([0].value), in GraalJS
reicht der letzte Ausdruck und die direkte Zuweisung.
Fehlende Pfade werden angelegt
Schreibst du auf einen Pfad, den es in der Nachricht nicht gibt, legt Brio-IO die fehlenden Ebenen an. Du musst also nicht erst prüfen, ob ein Feld existiert, bevor du es setzt.
Filter: gib einen Wahrheitswert zurück
Ein Filter muss einen Boolean liefern. true lässt die Nachricht durch,
alles andere verwirft sie — sichtbar als FILTERED im Message-Browser.
// Groovy: nur Nachrichten mit gesetzter Patienten-ID
return msg.PID.PID_3.PID_3_1.text() != ""
// GraalJS
msg.PID.PID_3.PID_3_1 !== "";
Ein Beispiel, das in der Demo genau so läuft — nur ADT^A08 durchlassen:
msg.MSH.MSH_9.MSH_9_2.text() == 'A08'
Wiederholfelder
Ein wiederholtes Element liest sich als indexierbare Liste:
// GraalJS — zwei Wiederholungen von PID-3
msg.PID.PID_3[0] // "MRN-1"
msg.PID.PID_3[1] // "MRN-2"
msg.PID.PID_3.length // 2
In Groovy gilt dasselbe Muster über den Index (msg.PID.PID_3[0].text()).
Wenn du komplexer navigierst, steht dir zusätzlich der volle XPath-Zugriff über
xml.xpath offen.
Segmente anlegen
Ein neues Segment — etwa ein Z-Segment für hauseigene Zusatzinformationen —
legst du mit createSegment an. Das Ergebnis ist ein Knoten, den du direkt
befüllst.
// Groovy: ZBR anhängen und füllen
def z = createSegment('ZBR', msg)
z.appendNode('ZBR_1', 'script-created')
msg
// GraalJS: ZBR anhängen und füllen
var z = createSegment('ZBR', msg);
z.ZBR_1 = 'script-created';
msg;
Beides erzeugt <ZBR><ZBR_1>script-created</ZBR_1></ZBR> am Ende der Nachricht.
An eine bestimmte Position einfügen statt anhängen:
// Index 1 = direkt nach dem ersten Segment (MSH)
createSegment('ZZZ', msg, 1);
msg;
Direkt hinter ein bestimmtes Segment einfügen:
// GraalJS
createSegmentAfter('ZZZ', msg.MSH);
msg;
// Groovy — hier den Knoten explizit adressieren
createSegmentAfter('ZZZ', msg.MSH[0])
msg
In GraalJS gibt es zusätzlich die Kurzform, die Segment und Inhalt in einem Schritt erzeugt:
msg.ZBR = 'script-created';
msg;
Escapes
HL7-Escape-Sequenzen stehen in der kanonischen XML verbatim — \S\ bleibt
\S\. Wer sie im Skript auflösen oder erzeugen will, nutzt die beiden Helfer,
die es in allen drei Engines gibt:
escapeHl7('a^b&c') // "a\S\b\T\c"
unescapeHl7('a\\S\\b\\T\\c') // "a^b&c"
Das ist der Weg, um einen Wert sicher in ein Feld zu schreiben, der selbst HL7-Trennzeichen enthält — ohne die Nachrichtenstruktur zu zerlegen.
Eine zweite Nachricht parsen
Manchmal braucht ein Skript eine zweite Nachricht — etwa eine, die in einer Map liegt. Dafür gibt es die Serializer-Fassade:
// GraalJS
var s = SerializerFactory.getSerializer('HL7V2');
var x = s.toXML(einAndererEr7String);
// x ist jetzt kanonisches XML, gegen das dieselben Pfade laufen
Der Nachrichten-Kontext: Maps
Werte zwischen Filter, Transformer und Destinations weiterreichen:
def patientId = msg.PID.PID_3.PID_3_1.text()
channelMap.put("patientId", patientId)
return msg
channelMap— lebt für diese eine Nachricht bis zu den DestinationssourceMap,responseMap— Kontext des Source-Connectors bzw. der AntwortglobalChannelMap— channelweit persistentglobalMap— serverweit persistenttmp— Scratchpad, nur für diese eine Ausführung
xml.get / xml.set: die Übergangs-API
Für Bestandsskripte und Nicht-XML-Payloads (JSON, Raw — dort bleibt
msg ein String) gibt es den Pfad-Helfer xml. Er ist engine-übergreifend
identisch und akzeptiert msg sowohl als String als auch als geparstes Objekt:
// Pfad ab der Wurzel, inklusive Wurzel-Element
def nachname = xml.get(msg, "ADT_A01/PID/PID_5/PID_5_1")
Anders als beim Property-Zugriff startet der Helfer-Pfad am Wurzel-Element.
Als Trennzeichen akzeptiert er Schrägstrich und Punkt. Neben get gibt es set
zum Schreiben und xpath für vollen XPath-Zugriff:
let nachname = xml.xpath(msg, "//*[local-name()='PID_5_1']/text()");
Für neue Skripte ist der Property-Zugriff der empfohlene Weg — er ist lesbarer und schneller, weil er direkt auf dem geparsten Objekt arbeitet, statt die Nachricht bei jedem Feldzugriff neu zu serialisieren.
Was in Rhino anders ist
Rhino (js-legacy) existiert für importierte Mirth-Skripte und bekommt als
einzige Engine die Punkt-Notation — die Welt, die diese Skripte erwarten.
msg ist dort direkt ein E4X-XML-Objekt; das aus Mirth gewohnte
new XML(msg) ist nicht mehr nötig.
Rhino ist bewusst deprecated: Bei jedem Deploy eines Channels mit
mindestens einem Rhino-Skript gibt Brio-IO eine Deprecation-Warnung aus — im Log
und in der API-Antwort — und nennt die betroffenen Skripte namentlich. Auch im
Skript-Editor erscheint eine Warnung, sobald du js-legacy wählst.
Der Migrationsweg steht in der Mirth-Migration.
Häufige Stolpersteine
Datentyp-Pfade aus einer Zwischenfassung. msg.PID.PID_5.XPN_1.FN_1 liefert
heute still einen leeren String. Richtig ist msg.PID.PID_5.PID_5_1. Beim
Deployment warnt Brio-IO namentlich, wenn ein Skript noch solche Pfade enthält —
diese Warnung ernst nehmen.
Vergessenes return in Groovy. Ohne return msg gibt der Transformer den
Wert des letzten Ausdrucks zurück, und das ist selten die Nachricht.
Mehrstufige Vivification per Property in Groovy/GraalJS. Ein fehlender
Zwischenknoten lässt sich in diesen beiden Engines nicht durch reine
Property-Zuweisung erzeugen — dafür gibt es createSegment bzw. xml.set. Nur
Rhino/E4X kann das nativ.
router.routeMessage() gibt es nicht. Der Weg von Channel zu Channel ist
eine Destination am Channel.
Diese Seite gibt es bisher auf Deutsch und Englisch. Die spanische Fassung folgt.