PBIDEA:用 uo_xml 解析与生成 XML(节点树遍历、读写节点与属性)
PBIDEA:用 uo_xml 解析与生成 XML(节点树遍历、读写节点与属性)阅读说明
- ① 适用版本:PowerBuilder 12.5 + PBIDEA 组件库(uo_xml / uo_xml_node / uo_xml_attribute 均来自 PBIDEA 的 websuite.pbl)。
- ② 支持数据库:本文不涉及数据库,纯 XML 文件与字符串处理,无需任何 DBMS。
- ③ 操作系统与环境要求:Windows 7 及以上;运行环境需安装 PbIdea.dll 运行库(PBIDEA 组件的统一底层库),否则调用会报找不到外部函数。
- ④ 难度系数:★★★☆☆(中等偏易,主要是熟悉节点树与"取节点/取值/加节点/加属性"的对象模型,概念不多)。
- ⑤ 其它阅读说明:示例均为最小可运行片段,依赖 uo_xml / uo_xml_node / uo_xml_attribute 三个对象已加入你的 PBL;文中所有方法名、常量均已对照 PBIDEA 导出的真实 .sru 源码核实,可直接照搬。
一、为什么要写这篇
在对接外部系统、读写配置文件、解析 WebService 返回报文时,XML 仍然是最常见的数据格式之一。PB 自带的 PBDOM 也能干,但 PBIDEA 的 uo_xml 把"解析—遍历—读写—生成—保存"封装成了三个对象,调用更接近直觉,而且支持 XPath 风格的路径取节点。
本文只讲清楚一件事:怎么用 uo_xml / uo_xml_node / uo_xml_attribute 这套对象,把一段 XML 读出来、改掉、再生成回去。围绕这件事,把节点树模型、三个对象的关系、常用 API 全貌、典型写法、边界坑、和 PBDOM 的对比一次讲透。
二、核心概念与对象模型
uo_xml 这套组件的核心是一个节点树:
[*]一个 uo_xml 对象代表整份文档(document 节点);
[*]文档里每个元素(element)是一个 uo_xml_node 对象;
[*]元素上的每个属性(attribute)是一个 uo_xml_attribute 对象。
三者的关系:
uo_xml (文档根)
└─ uo_xml_node "root" (元素)
├─ uo_xml_attribute id="1" (属性)
├─ uo_xml_node "name" (子元素)
└─ uo_xml_node "items"
├─ uo_xml_node "item"(子元素,可多个)
└─ uo_xml_node "item"
拿到文档对象后,通过 GetNode("节点名") 一层层往下取子节点;在某节点上用 GetAttribute("属性名") 取属性;用 GetContent() 取节点的文本内容;用 AddNode / AddAttribute 往树上挂新东西。
三个对象的几个关键节点类型常量(定义在 uo_xml_node 与 uo_xml 上,数值一致):
常量值含义
node_element2普通元素节点(最常用)
node_pcdata3文本节点
node_cdata4CDATA 段
node_comment5注释
node_document1文档根
取"兄弟/首尾"节点时用到的常量:
常量值含义
NODE_FIRST1第一个子节点
NODE_LAST2最后一个子节点
NODE_NEXT3下一个兄弟
NODE_PREV4上一个兄弟
属性遍历常量(在 uo_xml_node / uo_xml_attribute 上):ATTR_FIRST=1、ATTR_NEXT=2、ATTR_PREV=3、ATTR_LAST=4。
三、uo_xml 对象 API 全貌
uo_xml 是文档入口,构造时自动调用 xmlCreate(),析构自动 xmlDestroy(),你不用手动管底层句柄。
解析(三种入口):
[*]boolean Parse(string xml) —— 解析一段 XML 字符串。
[*]boolean Parse(blob xml) —— 解析 blob(比如从文件读进来的二进制)。
[*]boolean ParseFile(string xmlFile) —— 直接解析一个 XML 文件。
取节点:
[*]uo_xml_node GetNode(string nodeName) —— 按名称(支持 XPath 风格路径)取第一个匹配的子节点。
[*]uo_xml_node GetNode(int nodeType) —— 按节点类型取(如 NODE_FIRST)。
[*]long GetNode(ref uo_xml_node nodeArray[]) —— 取出所有子节点到数组,返回个数。
[*]long GetAllNodes(ref ulong nodeArray[]) —— 不分层次,取出整棵树的节点句柄列表。
输出与保存:
[*]string ToString() / ToString(boolean crlf) / ToString(boolean crlf, boolean withHeader) —— 把整棵树序列化成字符串;withHeader=true 会带上 <?xml ...?> 声明。
[*]boolean Save(string xmlFile) —— 写到文件。
[*]boolean IsEmpty() —— 是否为空文档。
[*]subroutine Clear() —— 清空。
增删:
[*]uo_xml_node AddNode(...) —— 加节点(支持按名、按类型、按节点对象四种重载)。
[*]uo_xml_attribute AddAttribute(...) —— 加属性(支持字符串/整型/浮点/布尔等多种重载)。
[*]boolean Remove(...) —— 移除节点/属性(先尝试移除属性,失败再移除节点)。
其它: long dwImport(datawindow dw) 可从 DataWindow 导入;boolean FromJson(uo_json / string) 可直接把 JSON 转成 XML。
四、uo_xml_node 与 uo_xml_attribute API 全貌
uo_xml_node 代表一个元素,常用方法:
[*]取子节点:GetNode(string) / GetNode(int) / GetNode(ref 数组)。
[*]取文本:string GetContent() / GetContent(boolean crlf) —— 取该节点及其内容的文本。
[*]取名称与值:boolean Get(ref string name, ref string value)(还有 long/ulong/int/double/boolean 等重载)—— 对属性型节点取"名称=值",对元素常用于取 pair。
[*]设值:boolean Set(string value) / Set(string name, string value) 等重载 —— 设置节点文本或属性值。
[*]类型判断:int GetType() 返回节点类别;boolean Isvalid() 判断句柄是否有效;boolean IsEmpty()。
[*]属性:GetAttribute(string) / GetAttribute(int) 取属性对象;GetAttribute(ref 数组) 取全部。
[*]增:AddNode(...) / AddAttribute(...);删:Remove(...)。
uo_xml_attribute 代表一个属性:
[*]boolean Get(ref string name, ref string value) —— 取属性名与值。
[*]boolean Set(string name, string value) 等重载 —— 设属性值。
[*]boolean Isvalid() —— 句柄是否有效。
[*]GetAttribute(int) / GetAttribute(string) —— 继续往后取下一个属性(用于遍历)。
五、典型用法示例
下面所有片段都遵循 PB 代码书写规范:输入值先声明赋值并注释,代码结尾用 MessageBox 输出结果。
示例 1:解析 XML 字符串并读取一个节点的值
假设要解析的报文如下:
<root>
<user id="1001">
<name>张三</name>
<age>30</age>
</user>
</root>
// 说明:解析一段 XML 字符串,按节点名取出 name 元素的文本内容
uo_xml lx // 声明 XML 文档对象
lx = create uo_xml // 创建对象(构造时自动 xmlCreate)
string ls_xml // 声明 XML 字符串变量
ls_xml = '<root><user id="1001"><name>张三</name><age>30</age></user></root>'// 示例报文
uo_xml_node ln_user // 声明 user 节点对象
uo_xml_node ln_name // 声明 name 节点对象
string ls_name // 声明用于接收节点文本的变量
boolean lb_ok
lb_ok = lx.Parse(ls_xml) // 解析字符串,成功返回 true
if not lb_ok then
MessageBox('提示', 'XML 解析失败')
destroy lx
return
end if
ln_user = lx.GetNode('user')// 按名称取第一个 user 元素
if ln_user.Isvalid() then
ln_name = ln_user.GetNode('name') // 在 user 下取 name 子元素
if ln_name.Isvalid() then
ls_name = ln_name.GetContent()// 取 name 的文本"张三"
end if
end if
MessageBox('读取结果', ls_name)// 输出:张三
destroy lx
示例 2:读取节点的属性值
// 说明:取 user 节点上 id 属性的值(1001)
uo_xml lx // 声明 XML 文档对象
lx = create uo_xml // 创建对象
string ls_xml // 声明 XML 字符串变量
ls_xml = '<root><user id="1001"><name>张三</name></user></root>'// 示例报文
uo_xml_node ln_user // 声明 user 节点对象
uo_xml_attribute la_id // 声明 id 属性对象
string ls_attr_name // 声明接收属性名的变量
string ls_attr_value // 声明接收属性值的变量
lx.Parse(ls_xml) // 解析字符串
ln_user = lx.GetNode('user')// 取 user 元素
if ln_user.Isvalid() then
la_id = ln_user.GetAttribute('id')// 按名称取 id 属性
if la_id.Isvalid() then
la_id.Get(ref ls_attr_name, ref ls_attr_value)// 取回"id"与"1001"
end if
end if
MessageBox('id 属性', ls_attr_value)// 输出:1001
destroy lx
示例 3:遍历同一级的所有同名子节点
// 说明:把 item 节点逐个读出,统计数量并拼接内容
uo_xml lx // 声明 XML 文档对象
lx = create uo_xml // 创建对象
string ls_xml // 声明 XML 字符串变量
ls_xml = '<root><item>a</item><item>b</item><item>c</item></root>'// 示例报文
uo_xml_node ln_root // 声明 root 节点对象
uo_xml_node lna_items[] // 声明节点数组(接收所有 item)
long ll_count // 声明节点个数变量
long i // 声明循环计数变量(必须先声明,不能内联)
string ls_all // 声明拼接结果变量
uo_xml_node ln_item // 声明单个 item 节点对象
lx.Parse(ls_xml) // 解析字符串
ln_root = lx.GetNode('root')// 取 root
ll_count = ln_root.GetNode(lna_items)// 取所有子节点,返回个数到 ll_count
ls_all = ''
for i = 1 to ll_count
ln_item = lna_items // 取第 i 个节点
ls_all = ls_all + ln_item.GetContent() + ';'// 拼文本内容
next
MessageBox('共 ' + string(ll_count) + ' 项', ls_all)// 输出:a;b;c;
destroy lx
示例 4:从零生成一份 XML 并保存
// 说明:新建文档、加节点和属性、序列化并保存到文件
uo_xml lx // 声明 XML 文档对象
lx = create uo_xml // 创建对象(空文档)
uo_xml_node ln_root // 声明 root 节点对象
uo_xml_node ln_user // 声明 user 节点对象
uo_xml_node ln_name // 声明 name 节点对象
uo_xml_attribute la_id // 声明 id 属性对象
boolean lb_save // 声明保存结果变量
ln_root = lx.AddNode('root') // 加 root 元素
ln_user = ln_root.AddNode('user') // 在 root 下加 user
la_id = ln_user.AddAttribute('id', '1001')// 给 user 加属性 id="1001"
ln_name = ln_user.AddNode('name') // 在 user 下加 name
ln_name.Set('张三') // 设 name 的文本为"张三"
string ls_out
ls_out = lx.ToString(true, true) // 带换行、带 <?xml?> 声明
lb_save = lx.Save('C:\temp\demo.xml') // 写到文件
if lb_save then
MessageBox('已生成', ls_out) // 输出序列化后的 XML 文本
else
MessageBox('提示', '保存失败')
end if
destroy lx
六、边界情况与易错点
[*]节点取不到要用 Isvalid() 判断:GetNode / GetAttribute 取不到时返回的对象句柄可能无效,直接调用其方法会报错。务必先 if ln_x.Isvalid() then ...。
[*]for 循环不能内联声明变量:经典 PB 不支持 for long i = 1 to n。必须 long i 先声明,再 for i = 1 to n。本文示例严格遵循。
[*]声明与初始化拆两行:uo_xml lx 与 lx = create uo_xml 分开写,不要写 uo_xml lx = create uo_xml。
[*]Get 与 GetContent 的区别:Get(ref name, ref value) 适合取"属性名=值"这种 pair(也常用于属性节点);取元素里的文本要用 GetContent()。混用会取不到东西。
[*]ToString 的 withHeader 参数:默认 ToString() 不带 XML 声明。要生成标准文件头,用 ToString(true, true)(第一个 true=换行,第二个 true=带声明)。
[*]用完要 destroy:uo_xml / uo_xml_node / uo_xml_attribute 都包装了底层 PbIdea.dll 句柄,使用完毕 destroy 释放,避免句柄泄漏。
[*]Parse 失败不等于异常:Parse 返回 false 表示解析失败(如格式不合法),需要显式判断 lb_ok,不能假设调用后文档一定有效。
[*]节点数组下标从 1 开始:PB 数组默认下标为 1,GetNode(ref 数组) 填充后从 数组 取起,循环写成 for i = 1 to ll_count。
七、与 PBDOM 的对比 / 替代方案
维度uo_xml(PBIDEA)PBDOM(PB 自带)
上手成本三个对象,方法直白,支持 XPath 风格取节点类较多(PxDocument/PxElement/PxNode…),概念更重
路径取节点GetNode('user/name') 风格直接需逐层 GetChildBy...
JSON 互通自带 FromJson(JSON 直接转 XML)无,需自己处理
依赖需要 PbIdea.dll 运行库PB 标准组件,无需额外库
适用已用 PBIDEA 生态、要快速解析/生成纯 PB 环境、不想引入额外运行库
选择建议:项目已经引入 PBIDEA 组件库的,优先考虑 uo_xml,读写最省事;如果是给第三方交付、不能带 PbIdea.dll 的,用 PBDOM 更稳妥。
八、进阶扩展点
[*]JSON 转 XML:lx.FromJson(lj) 或 lx.FromJson('{"a":1}') 可直接把 JSON 结构转成一棵 XML 树,适合做格式桥接。
[*]从 DataWindow 导 XML:lx.dwImport(dw_1) 把 DataWindow 当前数据导入为 XML 节点树,再 Save 即可导出 XML 文件。
[*]深层路径:GetNode 支持 XPath 风格路径(官方建议学 XPath),可直接 lx.GetNode('root/user/name') 一步到位,不必层层 GetNode。
[*]节点类型过滤:用 GetNode(NODE_FIRST) / NODE_NEXT 配合 GetType() 做按类型的精细遍历,处理混合内容的节点时很有用。
掌握上面这些,日常 90% 的 XML 解析、配置读写、报文处理都能用 uo_xml 一把梭。把"文档—节点—属性"三层对象关系和 Isvalid 判断养成习惯,基本不会踩坑。
页:
[1]