在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, "&", "&") as_str = replace(as_str, "<", "<") as_str = replace(as_str, ">", ">") as_str = replace(as_str, '"', """) as_str = replace(as_str, "'", "'") 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"?><TCS101Messageinfno="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 // 逐条入库... nextXPath表达式//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——适合处理对方返回的复杂嵌套响应
七、扩展方向
- DTD/Schema校验——加载XML前先用MSXML验证结构合法性
- CDATA支持——处理包含特殊字符的长文本字段
- 流式解析——超大XML文件用SAX解析替代DOM,避免内存撑爆
- 命名空间支持——处理带
xmlns:前缀的XML报文
八、结语
在PB生态里造XML工具——这个选择本身就是一个权衡。字符串拼接快但转义容易漏,MSXML功能全但部署有依赖。2014年的选择是两套并存——生成用自制类(快)、解析用MSXML(稳)。放到今天,所有的现代语言都自带了XML序列化,但在那个PB主导的年代,这套工具就是医院HIS和医保平台之间所有数据交换的管道。