在PowerBuilder里手写XML序列化——没有现成库的年代怎么拼报文

AI1天前发布 beixibaobao
3 0 0

在PowerBuilder里手写XML序列化——没有现成库的年代怎么拼报文

PB 9.0原生没有XML序列化,但医保接口的对接需要XML格式的报文。没有库、没有现成的工具——全靠字符串拼接和OLE调用MSXML自己造一套。这篇记录在PB里从零手写XML编解码的过程:转义字符处理、属性拼接、嵌套元素递归、节点解析。

文章目录

  • 在PowerBuilder里手写XML序列化——没有现成库的年代怎么拼报文
    • 一、PB和XML之间的一道鸿沟
    • 二、XML生成——递归构建节点树
    • 三、XML解析——MSXML反向抽取
    • 四、XML生成 vs 解析的策略选择
    • 五、亮点总结
    • 六、适用场景
    • 七、扩展方向
    • 八、结语

一、PB和XML之间的一道鸿沟

医保平台升级后,接口格式从定长报文改成了XML。医院HIS是PB写的,服务端是Java,中间的数据报文要用XML传。

问题来了——PB没有 XmlSerializer,没有 XDocument,没有 JAXB。只有两样东西可以用:字符串拼接OLE调用MSXML

最简单的方案是字符串拼XML,但写了几十条接口后发现——不同地方的字符串拼法不一样,有的没做转义导致 & 变成非法字符报错,有的嵌套多层全塞在一个方法里改不动。必须抽象出一套通用的工具。


二、XML生成——递归构建节点树

// nv_xml_builder - 自定义类,递归构建XML
// 实例变量
string is_xml         // 最终XML字符串
boolean ib_indent     // 是否格式化缩进
integer ii_level      // 当前缩进层级
// constructor
constructor:
    is_xml = '<?xml version="1.0" encoding="UTF-8"?>~r~n'
    ii_level = 0
end constructor
// 设置是否格式化
public subroutine of_setformatted(boolean ab_formatted)
    ib_indent = ab_formatted
end subroutine
// 生成缩进
private function of_indent returns string
    string ls_indent = ""
    int i
    if not ib_indent then return ""
    for i = 1 to ii_level
        ls_indent += "    "
    next
    return ls_indent
end function
// 打开节点(带属性)
public subroutine of_open_tag(string as_name, string as_attrs[])
    is_xml += of_indent() + "<" + as_name
    int i
    for i = 1 to upperbound(as_attrs) step 2
        is_xml += " " + as_attrs[i] + '="' + as_attrs[i+1] + '"'
    next
    is_xml += ">~r~n"
    ii_level++
end subroutine
// 写入节点值(处理转义)
public subroutine of_write_tag(string as_name, string as_value)
    is_xml += of_indent() + "<" + as_name + ">"
    is_xml += of_escape(as_value)
    is_xml += "</" + as_name + ">~r~n"
end subroutine
// 关闭节点
public subroutine of_close_tag(string as_name)
    ii_level--
    is_xml += of_indent() + "</" + as_name + ">~r~n"
end subroutine
// 获取最终XML
public function of_getxml returns string
    return is_xml
end function
// XML转义:& < > " '
private function of_escape(string as_str) returns string
    as_str = replace(as_str, "&", "&amp;")
    as_str = replace(as_str, "<", "&lt;")
    as_str = replace(as_str, ">", "&gt;")
    as_str = replace(as_str, '"', "&quot;")
    as_str = replace(as_str, "'", "&apos;")
    return as_str
end function

五个方法覆盖了XML生成的所有场景:开标签、写值、关标签、属性、转义。调用时按XML结构逐级嵌套:

nv_xml_builder lxml
lxml = create nv_xml_builder
string ls_attrs[]
ls_attrs[1] = "infno"
ls_attrs[2] = "1101"
ls_attrs[3] = "msgid"
ls_attrs[4] = "20260525120001"
lxml.of_open_tag("TCS101Message", ls_attrs)
    ls_attrs[1] = ""   // 子节点无属性
    ls_attrs[2] = ""
    lxml.of_open_tag("MessageHead", ls_attrs)
        lxml.of_write_tag("SenderId", "T46100009000")
        lxml.of_write_tag("ReceiverId", "T46100008000")
        lxml.of_write_tag("SendTime", "20260525120001")
    lxml.of_close_tag("MessageHead")
    lxml.of_open_tag("MessageBody", ls_attrs)
        // 递归写入业务数据
        lxml.of_write_nodefromdw(dw_person, "personInfo", ...)
    lxml.of_close_tag("MessageBody")
lxml.of_close_tag("TCS101Message")
string ls_xml = lxml.of_getxml()

生成的XML:

<?xml version="1.0" encoding="UTF-8"?>
<TCS101Message infno="1101" msgid="20260525120001">
    <MessageHead>
        <SenderId>T46100009000</SenderId>
        <ReceiverId>T46100008000</ReceiverId>
        <SendTime>20260525120001</SendTime>
    </MessageHead>
    <MessageBody>
        ...
    </MessageBody>
</TCS101Message>

三、XML解析——MSXML反向抽取

XML的生成可以用字符串拼,但解析不行——字符串split对付不了嵌套。PB的做法是调用Windows内置的MSXML组件:

// nv_xml_reader - 基于OLE MSXML的XML解析器
OLEObject iole_xml
constructor:
    iole_xml = create OLEObject
    iole_xml.ConnectToNewObject("Msxml2.DOMDocument.4.0")
end constructor
// 加载XML字符串
public subroutine of_loadxml(string as_xml)
    ole_xml.async = false
    try
        ole_xml.loadxml(as_xml)
        if ole_xml.parseerror.errorcode <> 0 then
            messagebox("解析错误", ole_xml.parseerror.reason)
        end if
    catch (Exception e)
        messagebox("加载失败", e.getmessage())
    end try
end subroutine
// 读取节点值
public function of_getelement(string as_path) returns string
    OLEObject lnode
    try
        lnode = ole_xml.selectSingleNode(as_path)
        if not isnull(lnode) then
            return lnode.text
        end if
    catch (Exception e)
    end try
    return ""
end function
// 读取属性
public function of_getattribute(string as_path, string as_attr) returns string
    OLEObject lnode
    try
        lnode = ole_xml.selectSingleNode(as_path)
        if not isnull(lnode) then
            return lnode.getAttribute(as_attr)
        end if
    catch (Exception e)
    end try
    return ""
end function
// 读取节点列表
public function of_getnodes(string as_path, ref OLEObject a_nodes[]) returns int
    OLEObject lnodelist, lnode
    int li_count = 0, i
    try
        lnodelist = ole_xml.selectnodes(as_path)
        for i = 0 to lnodelist.length - 1
            li_count++
            a_nodes[li_count] = lnodelist.item(i)
        next
    catch (Exception e)
    end try
    return li_count
end function

解析医保接口返回的XML报文:

nv_xml_reader lreader
lreader = create nv_xml_reader
// 接口返回的XML
lreader.of_loadxml(as_response)
// 读公共头
string ls_code = lreader.of_getelement("//output/result/code")
string ls_msg  = lreader.of_getelement("//output/result/message")
// 读业务数据——人员列表
OLEObject a_nodes[]
int li_count
li_count = lreader.of_getnodes("//output/personList/personInfo", a_nodes)
int i
for i = 1 to li_count
    string ls_name = a_nodes[i].selectSingleNode("aac003").text
    string ls_idcard = a_nodes[i].selectSingleNode("aac002").text
    // 逐条入库...
next

XPath表达式 //output/personList/personInfo 直接定位到列表节点,selectSingleNode 取子节点值。MSXML的优势是有完整的XPath和DOM支持,比用字符串split硬解析强悍太多。


四、XML生成 vs 解析的策略选择

场景 方案 原因
生成XML发给对方 递归类拼字符串 性能好,纯PB代码无外部依赖
解析对方返回的XML OLE MSXML XPath查询方便,DOM操作比字符串split稳健
内部系统间交换 递归类双向处理 不用OLE,部署简单

生成用字符串拼是因为性能——一次请求可能生成几十个节点的XML,纯字符串拼接比OLE快。解析用MSXML是因为稳健——对方返回的XML嵌套深度不确定,XPath+DOM比手写字符串解析靠谱得多。


五、亮点总结

✅ XML生成纯PB代码——字符串递归拼装,零外部依赖
✅ XML解析借力MSXML——XPath+DOM,支持复杂嵌套查询
✅ 五字符转义全处理——& < > " ' 五个XML特殊字符逐个替换
✅ 双模式策略——生成用字符串(快),解析用MSXML(稳)
✅ 完整调用链——从PB DataWindow到XML报文到接口对接,全链路覆盖

六、适用场景

  • PB老系统需要对接XML格式的外部接口(医保平台、银行接口、政务平台)
  • 字符串拼接生成XML——适合输出报文给对方
  • MSXML解析XML——适合处理对方返回的复杂嵌套响应

七、扩展方向

  1. DTD/Schema校验——加载XML前先用MSXML验证结构合法性
  2. CDATA支持——处理包含特殊字符的长文本字段
  3. 流式解析——超大XML文件用SAX解析替代DOM,避免内存撑爆
  4. 命名空间支持——处理带xmlns:前缀的XML报文

八、结语

在PB生态里造XML工具——这个选择本身就是一个权衡。字符串拼接快但转义容易漏,MSXML功能全但部署有依赖。2014年的选择是两套并存——生成用自制类(快)、解析用MSXML(稳)。放到今天,所有的现代语言都自带了XML序列化,但在那个PB主导的年代,这套工具就是医院HIS和医保平台之间所有数据交换的管道。

© 版权声明

相关文章