马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
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_element | 2 | 普通元素节点(最常用) | | node_pcdata | 3 | 文本节点 | | node_cdata | 4 | CDATA 段 | | node_comment | 5 | 注释 | | node_document | 1 | 文档根 |
取"兄弟/首尾"节点时用到的常量:
| 常量 | 值 | 含义 | | NODE_FIRST | 1 | 第一个子节点 | | NODE_LAST | 2 | 最后一个子节点 | | NODE_NEXT | 3 | 下一个兄弟 | | NODE_PREV | 4 | 上一个兄弟 |
属性遍历常量(在 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] // 取第 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 数组) 填充后从 数组[1] 取起,循环写成 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 判断养成习惯,基本不会踩坑。 |