大家好,
数据转换语言(DTL)是那种乍看之下简单得令人误判的工具之一——拖动一个源字段,将其拖放到目标字段上,就大功告成了。然而,一旦开始处理实际的集成项目,你很快就会意识到,仅靠简单的字段映射是远远不够的。 当你需要验证字段长度、清理输入字符串、从 ID 中去除不需要的字符,或者生成下游系统要求的特定格式的时间戳时,真正的挑战才刚刚开始。
在本文中,我们将超越基础知识,探讨实际集成中经常出现的四个实用 DTL 场景。每个部分都围绕一个示例 HL7 消息展开,方便您在自己的环境中跟随操作,并亲眼查看结果。
本文将涵盖的内容:
- 在管理门户中从零开始创建 DTL。
- 基于字段长度(
$Length、$Extract)的条件逻辑。 - 使用
ReplaceStr去除和替换文本。 - 使用链式
ReplaceStr从患者 ID 中去除特殊字符。 - 将 HL7 时间戳重新格式化为自定义显示格式。
先决条件
- 配备了支持互操作性命名空间的 InterSystems IRIS 或 HealthShare。
- 可访问管理门户。
- 对 HL7 v2 消息结构有基本了解。
注意:虽然所有示例均使用 HL7 v2 ADT 消息,但相同的 DTL 函数同样适用于 XML、JSON 或自定义消息类。
什么是 DTL?
在 InterSystems IRIS 中,DTL(数据转换语言)是一种类,用于定义如何映射和转换消息字段——无论是不同消息格式之间,还是同一消息类型内部。您可以在管理门户中通过可视化方式构建它,IRIS 会自动生成底层的 ObjectScript 代码。
每次转换都由一系列操作组成。最常见的操作包括:
- 赋值:映射或设置字段值。
- If:处理 条件分支。
- 代码:允许 直接编写 ObjectScript 代码。
- Foreach:遍历 重复段。
HL7 消息示例
本文中的所有四个场景均使用同一条传入的 ADT A01 消息。保持这种一致性至关重要,因为在实际生产环境中,您很少会只对一个字段进行转换。
以下是我们的示例消息:
MSH|^~\&|SENDING_APP|SENDING_FAC|RECEIVING_APP|RECEIVING_FAC|20250315143022||ADT^A01|MSG00001|P|2.5
EVN|A01|20250315143022
PID|1||MRN#001/2025^^^HOSP||VanDerBerghMontgomeryXYZABC^John^M||19850101|M|||123 Main Street^^Lahore^Punjab^54000^PK||+92-03001234567|||M||ACC001|
PV1|1|I|WARD-A^Room101^Bed1|E|||DOC001^Smith^James|||SUR|||||||V01|ACC001||||||||||||||||||||||||||20250315143022
每个字段包含的、对本示例而言重要的内容如下:
- PID-5.1(姓氏):
VanDerBerghMontgomeryXYZABC(27个字符;故意超过25个字符的限制)。 - PID-3.1(患者 ID):
MRN#001/2025包含#和/字符,目标系统无法接受这些字符 - PID-13(电话号码):
+92-03001234567(包含多余的国家代码前缀)。 - PV1-44(入院日期/时间):
20250315143022(标准 HL7 DTM 格式;需转换为 15/03/2025 14:30)。
注意:在处理每个部分时,请在文本编辑器中保持此消息处于打开状态。
从头开始创建 DTL
在开始进行转换之前,让我们先设置一个 DTL,用于容纳以下四个示例。
进入 DTL 编辑器:
进入**管理门户 → 互操作性 → 构建 → 数据转换(**Management Portal → Interoperability → Build → Data Transformations)
点击“新建”以创建一个新的转换。
在对话框中填写信息:
- 包:
Demo - 名称:
ADTTransform - 描述:DTL 深度解析演示
- 源代码:
EnsLib.HL7.Message将文档类型设置为2.5:ADT_A01 - 目标:
EnsLib.HL7.Message将DocType设置为2.5:ADT_A01
单击“确定”。可视化 DTL 编辑器将打开,显示一个空画布,左侧显示源内容,右侧显示目标内容。
在画布顶部,您会看到一个包含操作按钮的工具栏:Assign、If、Code、Foreach 等。我们将在这里添加各项转换。在添加任何操作之前,请先点击一次“保存”。这将编译该类,并避免工作成果丢失。
现在我们可以开始构建了。
场景 1:基于字段长度的条件逻辑
问题
目标系统仅接受长度不超过25个字符的患者姓氏。超过该长度将触发无提示的拒绝。我们的示例消息向PID-5.1发送了VanDerBerghMontgomeryXYZABC,共27个字符。
规则:如果姓氏超过25个字符,则将其截断为25个字符,并在末尾追加 *,以便接收团队能够标记并审核该信息。如果符合限制,则原样通过。
添加“If”操作
首先,将目标(target)设置为源(source),以便将源数据赋值给目标消息。 在 DTL 画布中,点击+按钮添加一个操作,然后选择“If”。
将条件设置为以下内容:
$Length(source.{PID:PatientName(1).FamilyName}) > 25
$Length 返回字符串的字符数。您可以在 DTL 条件中直接使用它,无需任何特殊封装。
在“true”分支(即 If 这一侧)中,添加一个“赋值”操作:
- 属性:
target.{PID:PatientName(1).FamilyName} - 值:$Extract(source.{PID:PatientName(1).FamilyName},1,25) _ "*"
$Extract(string, start, end) 按位置提取字符,这意味着 $Extract(value,1,25) 提取前 25 个字符。_ "*" 使用 ObjectScript 的字符串连接运算符在末尾追加星号。
在“Else”分支中,添加另一个用作穿透的“Assign”:
- 属性:
target.{PID:PatientName(1).FamilyName} - 值:
source.{PID:PatientName(1).FamilyName}
测试
保存并编译 DTL。 粘贴示例消息,然后单击“测试”按钮。
在输出消息中,PID-5.1 应显示 VanDerBerghMontgomeryXYZAB*,这正好是 25 个字符加上星号。
场景 2:使用 ReplaceStr 清理字段值
问题
示例中的 PID-13(电话号码)显示为 +92-03001234567。目标系统仅需本地号码,不包含国家代码和连字符。我们需要在将电话号码传递出去之前,从每个电话号码中去除 +92-。
ReplaceStr 函数
ReplaceStr 是 Ens.Util.FunctionSet 类上的一个静态方法。它接受三个参数:原始字符串、要查找的内容以及替换内容。将空字符串作为第三个参数传递将删除匹配的文本。
您可以在 DTL 中的“赋值”字段内直接调用该函数。
添加“赋值”操作
点击“+”并添加一个“赋值”操作:
- 属性:
target.{PID:PhoneNumberHome(1).TelephoneNumber} - 值:##class(Ens.Util.FunctionSet).ReplaceStr(source.{PID:PhoneNumberHome(1).TelephoneNumber},"+92-","")
测试
保存并编译 DTL 文件。
粘贴示例消息并点击**“测试”**按钮。在输出结果中,PID-13 应显示为 03001234567;前缀应已消失。
另一个实际应用
ReplaceStr 不仅用于去除前缀。如果您需要替换代码值,例如源数据发送 M 或 F 表示性别,但目标系统期望 Male 或 Female,此时使用带有显式 If/ElseIf 逻辑的“代码”操作是最简洁的方法:
Set sex = source.{PID:AdministrativeSex}
If sex = "M" { Set target.{PID:AdministrativeSex} = "Male" }
ElseIf sex = "F" { Set target.{PID:AdministrativeSex} = "Female" }
Else { Set target.{PID:AdministrativeSex} = sex }
对于简单的替换,嵌套的 ReplaceStr 调用可以奏效,但请注意,如果排序不当,ReplaceStr(value,"M","Male") 在第二次处理时也会匹配 Female 内部的 M,因此请务必注意处理顺序。
场景 3:从患者 ID 中去除特殊字符
问题
请看示例消息中的 PID-3.1。患者 ID 显示为 MRN#001/2025。 #和/这些字符很可能是由旧系统生成的,该系统在内部将它们用作分隔符。目标系统要求ID必须是纯字母数字格式,如果消息中包含这些字符,系统将拒绝该消息或将其错误路由。
我们需要去除这两个字符,使 MRN#001/2025 变为 MRN0012025。在此应使用两个串联的**“赋值”**操作——每个字符各一个。
第一个“Assign”操作——去除 #:点击 + 并添加一个“Assign”操作:
- 属性:target.{PID:PatientIdentifierList(1).IDNumber}
- 值:##class(Ens.Util.FunctionSet).ReplaceStr(source.{PID:PatientIdentifierList(1).IDNumber},"#","")
第二次赋值 — 从第一次赋值写入的内容中去除 /:
点击 + 并添加一个“赋值”操作:
- 属性:target.{PID:PatientIdentifierList(1).IDNumber}
- 值:##class(Ens.Util.FunctionSet).ReplaceStr(target.{PID:PatientIdentifierList(1).IDNumber},"/","")
注意:第二个“赋值”操作从目标读取数据,而非源,因为第一个“赋值”操作已经将经过 # 清理的值写入目标;现在我们对其进行进一步清理。
测试
保存并编译 DTL。
粘贴示例消息并单击**“测试”**按钮。在输出结果中,PID-3.1 应显示 MRN0012025,且 # 和 / 均已被移除。
场景 4:重新格式化 HL7 时间戳
问题
示例中的 PV1-44(入院日期/时间)以 20250315143022 的形式输入,这是标准的 HL7 DTM 格式(YYYYMMDDHHMMSS)。 目标系统要求采用易于人类阅读的格式:15/03/2025 14:30(DD/MM/YYYY HH:MM)。
为什么 $Extract 在此处效果良好
HL7 DTM 时间戳是固定位置的字符串,这使其非常适合 $Extract。这些位置始终保持不变:
| 组件 | 字符 | 如何提取 |
|---|---|---|
| 年 | 1 – 4 | `$Extract(dt,1,4)` |
| 月份 | 5 – 6 | `$Extract(dt,5,6)` |
| 日 | 7 – 8 | `$Extract(dt,7,8)` |
| 小时 | 9 – 10 | `$Extract(dt,9,10)` |
| 分钟 | 11 – 12 | `$Extract(dt,11,12)` |
添加“分配操作”
在根级别点击“+”号,添加一个“分配”操作:
- 属性:
target.{PV1:AdmitDateTime.Time} - 值: $Extract(source.{PV1:AdmitDateTime},7,8)_"/"_$Extract(source.{PV1:AdmitDateTime},5,6)_"/"_$Extract(source.{PV1:AdmitDateTime},1,4)_" "_$Extract(source.{PV1:AdmitDateTime},9,10)_":"_$Extract(source.{PV1:AdmitDateTime},11,12)
测试
保存并编译 DTL。
粘贴示例消息并点击“测试”按钮。输出中的 PV1-44 应显示为 15/03/2025 14:30。
综合应用
我们最终的 DTL 包含四个操作块,每个操作块针对同一 ADT A01 消息中的不同字段:
- 如果检查 PID-5.1 的
$Length,则要么用*截断它,要么将其原样传递。 - “Assign”使用
ReplaceStr从 PID-13 中移除+92-。 - “First Code”通过链式调用两个
ReplaceStr来从 PID-3.1 中移除#和/。 - 第二段 代码将 PV1-44 从 HL7 DTM 格式重新格式化为
DD/MM/YYYY HH:MM。
将完整的示例消息最后一次通过测试器运行,并一次性验证输出中的所有四个字段。
结论
一旦开始将 DTL 与 $Length、$Extract、$ZStrip 等 ObjectScript 函数以及诸如 ReplaceStr等实用方法结合使用时,DTL 才真正展现出其强大功能。本文介绍的这四种模式并非边缘案例;它们几乎在每一个真实的集成项目中都会出现。
开发过程中通常会在将转换应用到生产环境之前,先对其进行测试。DTL 测试工具可以处理其中大部分工作,而终端始终可用,以便在需要时快速验证逻辑。
谢谢!













