文章 Lilian Huang · 八月 6 9m read

DTL 深度解析:基于长度的条件语句、ReplaceStr、特殊字符清理以及时间戳格式化

大家好,

数据转换语言(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.MessageDocType设置为 2.5:ADT_A01

单击“确定”。可视化 DTL 编辑器将打开,显示一个空画布,左侧显示源内容,右侧显示目标内容。

在画布顶部,您会看到一个包含操作按钮的工具栏:AssignIfCodeForeach 等。我们将在这里添加各项转换。在添加任何操作之前,请先点击一次“保存”。这将编译该类,并避免工作成果丢失。

现在我们可以开始构建了。


场景 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 函数

ReplaceStrEns.Util.FunctionSet 类上的一个静态方法。它接受三个参数:原始字符串、要查找的内容以及替换内容。将空字符串作为第三个参数传递将删除匹配的文本。

您可以在 DTL 中的“赋值”字段内直接调用该函数。

添加“赋值”操作

点击“+”并添加一个“赋值”操作:

  • 属性target.{PID:PhoneNumberHome(1).TelephoneNumber}
  • :##class(Ens.Util.FunctionSet).ReplaceStr(source.{PID:PhoneNumberHome(1).TelephoneNumber},"+92-","")

测试

保存并编译 DTL 文件。

粘贴示例消息并点击**“测试”**按钮。在输出结果中,PID-13 应显示为 03001234567;前缀应已消失。

另一个实际应用

ReplaceStr 不仅用于去除前缀。如果您需要替换代码值,例如源数据发送 MF 表示性别,但目标系统期望 MaleFemale,此时使用带有显式 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 消息中的不同字段:

  1. 如果检查 PID-5.1 的 $Length,则要么用 * 截断它,要么将其原样传递。
  2. “Assign”使用 ReplaceStr 从 PID-13 中移除 +92-
  3. “First Code”通过链式调用两个 ReplaceStr 来从 PID-3.1 中移除 #/
  4. 第二段 代码将 PV1-44 从 HL7 DTM 格式重新格式化为 DD/MM/YYYY HH:MM

将完整的示例消息最后一次通过测试器运行,并一次性验证输出中的所有四个字段。


结论

一旦开始将 DTL 与 $Length$Extract$ZStrip 等 ObjectScript 函数以及诸如 ReplaceStr等实用方法结合使用时,DTL 才真正展现出其强大功能。本文介绍的这四种模式并非边缘案例;它们几乎在每一个真实的集成项目中都会出现。

开发过程中通常会在将转换应用到生产环境之前,先对其进行测试。DTL 测试工具可以处理其中大部分工作,而终端始终可用,以便在需要时快速验证逻辑。

谢谢!