HL7 卫生信息交换标准(Health Level 7)
标准化的卫生信息传输协议,是医疗领域不同应用之间电子传输的协议。HL7汇集了不同厂商用来设计应用软件之间接口的标准格式,它将允许各个医疗机构在异构系统之间,进行数据交互。
HL7的主要应用领域是HIS/RIS,主要是规范HIS/RIS系统及其设备之间的通信,它涉及到病房和病人信息管理、化验系统、药房系统、放射系统、收费系统等各个方面。HL7的宗旨是开发和研制医院数据信息传输协议和标准,规范临床医学和管理信息格式,降低医院信息系统互连的成本,提高医院信息系统之间数据信息共享的程度。
Health Level 7中的“Level 7”是指OSI的七层模型中的最高一层,第七层。但这并不是说它遵循OSI第七层的定义数据元素,它只是用来构成它自己的抽象数据类型和编码规则。它也没有规定规范说明如何支持OSI第一到第六层的数据。
HL7 基本语法 每一个 HL7 消息由一些段组成,段由<CR> (即<0x0D>)结尾。消息以 utf-8 格式传输
每个段由三个字符的段名和固定数目的域组成,域由组件和子组件构成,在每个消息的 MSH 段定义各 个组成单元的分隔符。
例如:
MSH|^~&|Z3|Zybio|||20180514144801||ORU^R01|2018481414050147670|P|2.3.1||||||UNICODE
其中:
在 MSH 之后的五个字符定义用来区分各域、组件和子组件的分隔符。 标准使用下表的字符:
| 域分隔符
^ 组件分隔符
& 子组件分隔符
~ 重复分隔符
\ 转义字符
MSH 的第一个域包括各个分隔符。后面的有些域都是空的,因为他们是可选的并且迈瑞 HL7 接口没有 使用它,详细的域的定义和选取在后面说明。
域 9: 包含消息类型和事件(ORU、R01)
域 10: 包含一个唯一标识该消息的消息 ID
域 11: 包含处理 ID(P 表示产品)
域 12: 定义消息使用的 HL7 版本(2.3.1)
服务端代码
using hepu.BloodModel;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml;
namespace hepu
{
public partial class Blood5_TCP : Form
{
public Blood5_TCP()
{
InitializeComponent();
}
public delegate void showData(string msg);//委托,防止跨线程的访问控件,引起的安全异常
private const int bufferSize = 8000;//缓存空间
private TcpClient client;
private TcpListener server;
/// <summary>
/// 结构体:Ip、端口
/// </summary>
struct IpAndPort
{
public string Ip;
public string Port;
}
private void Blood5_TCP_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
if (txtIP.Text.Trim() == string.Empty)
{
return;
}
if (txtPort.Text.Trim() == string.Empty)
{
return;
}
Thread thread = new Thread(reciveAndListener);
//如果线程绑定的方法带有参数的话,那么这个参数的类型必须是object类型,所以讲ip,和端口号 写成一个结构体进行传递
IpAndPort ipHePort = new IpAndPort();
ipHePort.Ip = txtIP.Text;//服务器IP
ipHePort.Port = txtPort.Text;//为程序设置端口
thread.Start((object)ipHePort);
}
/// <summary>
/// 侦听客户端的连接并接收客户端发送的信息
/// </summary>
/// <param name="ipAndPort">服务端Ip、侦听端口</param>
private void reciveAndListener(object ipAndPort)
{
IpAndPort ipHePort = (IpAndPort)ipAndPort;
IPAddress ip = IPAddress.Parse(ipHePort.Ip);
server = new TcpListener(ip, int.Parse(ipHePort.Port));
server.Start();//启动监听
rtbtxtShowData.Invoke(new showData(rtbtxtShowData.AppendText), "服务端开启侦听....\n");
// btnStart.IsEnabled = false;
//获取连接的客户端对象
client = server.AcceptTcpClient();
rtbtxtShowData.Invoke(new showData(rtbtxtShowData.AppendText), "有客户端请求连接,连接已建立!");//AcceptTcpClient 是同步方法,会阻塞进程,得到连接对象后才会执行这一步
//获得流
NetworkStream reciveStream = client.GetStream();
#region 循环监听客户端发来的信息
do
{
byte[] buffer = new byte[bufferSize];
int msgSize;
try
{
lock (reciveStream)
{
msgSize = reciveStream.Read(buffer, 0, bufferSize);
}
if (msgSize == 0)
return;
string msg = Encoding.Default.GetString(buffer, 0, bufferSize);
//将hl7数据包转换成xml 方便提取数据
NewBlood5 blood5= FormatXml(Encoding.Default.GetString(buffer, 0, msgSize));
//这里只做单条数据传输 工作站没有批量发送 如果有批量的话可以判断数据包开头<SB>MSH|^~\&|公司名称(发送端公司名称)|XXX型号(发送端设备名称)||
//出现了几次,如果出现多次. New一个list集合 将实体类添加到集合里
YLB_OneSynchronous(blood5);
//rtbtxtShowData.Invoke(new showData(rtbtxtShowData.AppendText), "\n客户端曰:" + result + "\r\n");
}
catch (Exception ex)
{
server.Stop();
string aa = ex.Message;
MessageBox.Show(aa);
rtbtxtShowData.Invoke(new showData(rtbtxtShowData.AppendText), "\n 出现异常:连接被迫关闭");
break;
}
} while (true);
#endregion
}
/// <summary>
/// 将数据包转换成xml格式 提取有效参数
/// </summary>
/// <param name="hl7data"></param>
/// <returns></returns>
public NewBlood5 FormatXml(string hl7data)
{
XmlDocument xmlObject = HL7ToXmlConverter.ConvertToXmlObject(hl7data);
var xns = xmlObject.SelectSingleNode("HL7Message");
XmlNodeList xnl = xns.ChildNodes;
NewBlood5 blood5 = new NewBlood5();
blood5.Barcode = HL7ToXmlConverter.GetText(xmlObject, "PID/PID.2", 0);//样本编号 没有重复的节点
blood5.WBC = GetValue(xnl, "WBC");//白细胞数目 //重复的节点根据名字取值
blood5.Neu = GetValue(xnl, "NeuCount");//中性粒细胞数目 Neu#
return blood5;
}
public string GetValue(XmlNodeList xnl,string 项目名) {
string Value = string.Empty;
if (!string.IsNullOrEmpty(项目名))
{
foreach (XmlNode xn in xnl)
{
XmlElement xe = (XmlElement)xn;
XmlNodeList xnl2 = xe.ChildNodes;
foreach (XmlNode xn2 in xnl2)
{
XmlElement xe2 = (XmlElement)xn2;
if (xe2.InnerText.Equals(项目名))
{
var objs = new
{
Name = xe.GetElementsByTagName("OBX.5")
};
Value = objs.Name[0].InnerText;
return Value;
}
}
}
}
return Value;
}
}
}
解析hl7数据为xml
在分割hl7信息时 一定要注意有的仪器发出的信息每一小段是\r(回车)分割,有的是\n(换行),在分割时需要准确判断,不然解析出来的数据会被解析成一整个大的xml节点。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
namespace hepu.BloodModel
{
/// <summary>
/// HL7解析器
/// </summary>
public class HL7ToXmlConverter
{
private static XmlDocument _xmlDoc;
/// <summary>
/// 把HL7信息转成XML形式
/// 分隔顺序 \n,|,~,^,&
/// </summary>
/// <param name="sHL7">HL7字符串</param>
/// <returns></returns>
public string ConvertToXml(string sHL7)
{
_xmlDoc = ConvertToXmlObject(sHL7);
return _xmlDoc.OuterXml;
}
public static XmlDocument ConvertToXmlObject(string sHL7)
{
_xmlDoc = CreateXmlDoc();
//把HL7分成段
string[] sHL7Lines = sHL7.Split('\r');//这里一定注意 经过测试,TCP方式接收的用\r分隔,webService方式接收的用\n分隔
//去掉XML的关键字
for (int i = 0; i < sHL7Lines.Length; i++)
{
sHL7Lines[i] = Regex.Replace(sHL7Lines[i], @"[^ -~]", "");
}
for (int i = 0; i < sHL7Lines.Length; i++)
{
// 判断是否空行
if (sHL7Lines[i] != string.Empty)
{
string sHL7Line = sHL7Lines[i];
//通过/r 或/n 回车符分隔
string[] sFields = HL7ToXmlConverter.GetMessgeFields(sHL7Line);
// 为段(一行)创建第一级节点
XmlElement el = _xmlDoc.CreateElement(sFields[0]);
_xmlDoc.DocumentElement.AppendChild(el);
// 循环每一行
for (int a = 0; a < sFields.Length; a++)
{
// 为字段创建第二级节点
XmlElement fieldEl = _xmlDoc.CreateElement(sFields[0] + "." + a.ToString());
//是否包括HL7的连接符
if (sFields[a] != @"^~\&")
{//0:如果这一行有任何分隔符
//通过~分隔
string[] sComponents = HL7ToXmlConverter.GetRepetitions(sFields[a]);
if (sComponents.Length > 1)
{//1:如果可以分隔
for (int b = 0; b < sComponents.Length; b++)
{
XmlElement componentEl = _xmlDoc.CreateElement(sFields[0] + "." + a.ToString() + "." + b.ToString());
//通过&分隔
string[] subComponents = GetSubComponents(sComponents[b]);
if (subComponents.Length > 1)
{//2.如果有字组,一般是没有的。。。
for (int c = 0; c < subComponents.Length; c++)
{
//修改了一个错误
string[] subComponentRepetitions = GetComponents(subComponents[c]);
if (subComponentRepetitions.Length > 1)
{
for (int d = 0; d < subComponentRepetitions.Length; d++)
{
XmlElement subComponentRepEl = _xmlDoc.CreateElement(sFields[0] + "." + a.ToString() + "." + b.ToString() + "." + c.ToString() + "." + d.ToString());
subComponentRepEl.InnerText = subComponentRepetitions[d];
componentEl.AppendChild(subComponentRepEl);
}
}
else
{
XmlElement subComponentEl = _xmlDoc.CreateElement(sFields[0] + "." + a.ToString() + "." + b.ToString() + "." + c.ToString());
subComponentEl.InnerText = subComponents[c];
componentEl.AppendChild(subComponentEl);
}
}
fieldEl.AppendChild(componentEl);
}
else
{//2.如果没有字组了,一般是没有的。。。
string[] sRepetitions = HL7ToXmlConverter.GetComponents(sComponents[b]);
if (sRepetitions.Length > 1)
{
XmlElement repetitionEl = null;
for (int c = 0; c < sRepetitions.Length; c++)
{
repetitionEl = _xmlDoc.CreateElement(sFields[0] + "." + a.ToString() + "." + b.ToString() + "." + c.ToString());
repetitionEl.InnerText = sRepetitions[c];
componentEl.AppendChild(repetitionEl);
}
fieldEl.AppendChild(componentEl);
el.AppendChild(fieldEl);
}
else
{
componentEl.InnerText = sComponents[b];
fieldEl.AppendChild(componentEl);
el.AppendChild(fieldEl);
}
}
}
el.AppendChild(fieldEl);
}
else
{//1:如果不可以分隔,可以直接写节点值了。
fieldEl.InnerText = sFields[a];
el.AppendChild(fieldEl);
}
}
else
{//0:如果不可以分隔,可以直接写节点值了。
fieldEl.InnerText = sFields[a];
el.AppendChild(fieldEl);
}
}
}
}
return _xmlDoc;
}
/// <summary>
/// 通过|分隔 字段
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
private static string[] GetMessgeFields(string s)
{
return s.Split('|');
}
/// <summary>
/// 通过^分隔 组字段
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
private static string[] GetComponents(string s)
{
return s.Split('^');
}
/// <summary>
/// 通过&分隔 子分组组字段
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
private static string[] GetSubComponents(string s)
{
return s.Split('&');
}
/// <summary>
/// 通过~分隔 重复
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
private static string[] GetRepetitions(string s)
{
return s.Split('~');
}
/// <summary>
/// 创建XML对象
/// </summary>
/// <returns></returns>
private static XmlDocument CreateXmlDoc()
{
XmlDocument output = new XmlDocument();
XmlElement rootNode = output.CreateElement("HL7Message");
output.AppendChild(rootNode);
return output;
}
public string GetText(XmlDocument xmlObject, string path)
{
XmlNode node = xmlObject.DocumentElement.SelectSingleNode(path);
if (node != null)
{
return node.InnerText;
}
else
{
return null;
}
}
public string GetValue(XmlNodeList xnl,string 项目名) {
string Value = string.Empty;
foreach (XmlNode xn in xnl)
{
XmlElement xe = (XmlElement)xn;
XmlNodeList xnl2 = xe.ChildNodes;
foreach (XmlNode xn2 in xnl2)
{
XmlElement xe2 = (XmlElement)xn2;
if (xe2.InnerText.Equals(项目名))
{
var objs = new
{
Name = xe.GetElementsByTagName("OBX.5")
};
Value = objs.Name[0].InnerText;
return Value;
}
}
}
return Value;
}
public static string GetText(XmlDocument xmlObject, string path, int index)
{
XmlNodeList nodes = xmlObject.DocumentElement.SelectNodes(path);
if (index <= nodes.Count)
{
return nodes[index].InnerText;
}
else
{
return null;
}
}
public String[] GetTexts(XmlDocument xmlObject, string path)
{
XmlNodeList nodes = xmlObject.DocumentElement.SelectNodes(path);
String[] arr = new String[nodes.Count];
int index = 0;
foreach (XmlNode node in nodes)
{
arr[index++] = node.InnerText;
}
return arr;
}
}
}
测试:
使用tcp调试工具作为客户端发送hl7数据(工具可在我的资源中下载)tcp-udp助手.zip_tcpudp调试工具-网络安全文档类资源-CSDN下载
以接受病历号和HCT为例
病历号是唯一的节点
HCT的父节点名称有重复
重复时遍历所有节点 根据hct查找
public string GetValue(XmlNodeList xnl,string 项目名) {
string Value = string.Empty;
if (!string.IsNullOrEmpty(项目名))
{
foreach (XmlNode xn in xnl)
{
XmlElement xe = (XmlElement)xn;
XmlNodeList xnl2 = xe.ChildNodes;
foreach (XmlNode xn2 in xnl2)
{
XmlElement xe2 = (XmlElement)xn2;
if (xe2.InnerText.Equals(项目名))
{
var objs = new
{
Name = xe.GetElementsByTagName("OBX.5")
};
Value = objs.Name[0].InnerText;
return Value;
}
}
}
}
return Value;
}