在移动开发中,XML 作为一种经典的数据交换格式,依然广泛应用于接口通信、配置文件存储等场景。本文将深入解析两个自定义 XML 处理类 ------CNXMLParser
(XML 解析器)与CNXMLDocument
(XML 构建器),从核心功能到设计思路,带你掌握 iOS 平台下高效处理 XML 数据的实现方案。
一、核心功能概览
两个类分工明确,共同覆盖 XML "解析 - 构建" 全流程,解决系统原生 API 使用繁琐、数据转换不直观的问题。
类名 | 核心作用 | 关键能力 |
---|---|---|
CNXMLParser |
XML 解析(XML → 字典) | 1. 支持字符串 / 二进制两种输入格式2. 自动识别数组节点(重复子节点)3. 递归解析嵌套节点结构4. 输出易于操作的[String: Any] 字典 |
CNXMLDocument |
XML 构建(对象 → XML) | 1. 面向对象创建节点(支持父 / 子节点层级)2. 灵活添加节点属性与文本值3. 自动生成标准 XML 标签结构4. 支持嵌套节点链式构建 |
二、CNXMLParser:XML 解析器深度解析
CNXMLParser
基于系统XMLParser
(SAX 解析模式)封装,通过自定义数据结构和递归逻辑,将 XML 的树形结构转换为开发者更熟悉的字典 / 数组组合格式。
2.1 核心数据结构:CNXMLParserElement
解析过程的 "临时容器",用于存储单个 XML 节点的完整信息,是连接 SAX 事件与最终字典的关键桥梁。
swift
fileprivate class CNXMLParserElement: Codable {
var parent: CNXMLParserElement? // 父节点(维护层级关系)
var name: String = "" // 节点名称(XML标签名)
var child: [CNXMLParserElement] = [] // 子节点数组(嵌套结构)
var content: String = "" // 节点文本内容(非嵌套节点)
}
设计亮点:
- 采用
fileprivate
访问控制,仅在解析器内部可见,避免外部误修改; - 通过
parent
指针维护节点层级,解决 SAX 解析 "无状态" 的痛点; - 同时存储
child
(子节点)与content
(文本),兼容两种节点类型(嵌套节点 / 文本节点)。
2.2 核心解析流程
步骤 1:入口方法设计(多输入支持)
提供两个重载的cn_parser
方法,覆盖实际开发中常见的 XML 输入场景:
swift
// 1. 输入XML字符串(如接口返回的字符串数据)
func cn_parser(xmlString: String) -> [String: Any] {
guard let xmlData = xmlString.data(using: .utf8) else { return [:] }
return commonParse(xmlData: xmlData) // 复用二进制解析逻辑
}
// 2. 输入XML二进制数据(如本地文件读取的Data)
func cn_parser(xmlData: Data) -> [String: Any] {
return commonParse(xmlData: xmlData)
}
设计亮点:通过 "字符串转二进制"+"通用解析逻辑" 的封装,避免代码重复,符合 DRY(Don't Repeat Yourself)原则。
步骤 2:SAX 事件回调(节点生命周期管理)
通过实现XMLParserDelegate
协议,监听 XML 解析的三个核心事件,完成CNXMLParserElement
节点的创建与组装:
回调方法 | 触发时机 | 核心逻辑 |
---|---|---|
didStartElement |
解析到 XML 开始标签(如<user> ) |
1. 根节点:创建首个CNXMLParserElement ,初始化根节点信息2. 子节点:创建新节点,关联父节点,添加到父节点的child 数组3. 更新currentElement (当前活跃节点) |
foundCharacters |
解析到节点文本内容(如<name>张三</name> 中的 "张三") |
1. 去除文本中的空格和换行符(避免无效空白字符)2. 将有效文本赋值给currentElement?.content |
didEndElement |
解析到 XML 结束标签(如</user> ) |
1. 校验当前节点名称与结束标签一致2. 将currentElement 切换为父节点(回溯层级) |
关键代码解析(didStartElement) :
ini
func parser(_ parser: XMLParser, didStartElement elementName: String, ...) {
if currentElement == nil {
// 根节点初始化
currentElement = CNXMLParserElement()
currentElement?.parent = nil
currentElement?.name = elementName
} else {
// 子节点创建与关联
let childElement = CNXMLParserElement()
childElement.parent = currentElement
childElement.name = elementName
currentElement?.child.append(childElement)
currentElement = childElement // 切换到子节点,准备接收文本/子节点
}
}
步骤 3:数据转换(CNXMLParserElement → 字典)
通过私有方法cn_getElementInfo
,递归遍历CNXMLParserElement
树,将节点结构转换为字典 / 数组,核心是自动识别数组节点。
swift
private func cn_getElementInfo(_ element: CNXMLParserElement) -> Any {
// 1. 无嵌套子节点:直接返回文本内容
if element.child.isEmpty {
return element.content
}
// 2. 有嵌套子节点:判断是否为数组(所有子节点名称相同)
guard let firstChildName = element.child.first?.name else { return [:] }
let isArray = element.child.filter { $0.name == firstChildName }.count == element.child.count
if isArray {
// 数组节点:返回 [节点名: [子节点数组]]
return [firstChildName: element.child.map { cn_getElementInfo($0) }]
} else {
// 普通字典节点:返回 [子节点名: 子节点数据]
var dict: [String: Any] = [:]
element.child.forEach { dict[$0.name] = cn_getElementInfo($0) }
return dict
}
}
场景示例:
假设解析如下 XML:
xml
<userList>
<user><name>张三</name><age>20</age></user>
<user><name>李四</name><age>22</age></user>
</userList>
解析结果(字典):
css
[ "userList": [ "user": [ ["name": "张三", "age": "20"],
["name": "李四", "age": "22"]
]
]
]
设计亮点:通过 "子节点名称一致性校验" 自动识别数组,无需开发者手动指定数组节点,解决了原生解析中 "重复节点无法区分" 的痛点。
三、CNXMLDocument:XML 构建器深度解析
CNXMLDocument
采用 "面向对象" 的设计思路,将 XML 节点抽象为对象,通过链式调用的方式快速构建复杂 XML 结构,避免手动拼接 XML 字符串易出错的问题。
3.1 核心属性设计
每个CNXMLDocument
实例代表一个 XML 节点,包含节点的所有关键信息:
kotlin
class CNXMLDocument: NSObject {
private var name: String = "" // 节点名称(标签名)
private var value: String? // 节点文本值(非嵌套节点)
private var children: [CNXMLDocument] = [] // 子节点数组
private var attributes: [CNXMLDocument] = [] // 属性数组(特殊节点:仅存name和value)
}
特殊设计 :属性存储为CNXMLDocument
数组,利用现有类结构复用逻辑,避免额外定义 "属性模型",简化代码。
3.2 核心构建方法
提供直观的 API 用于配置节点信息,支持链式调用,提升开发效率:
方法 | 作用 | 使用示例 |
---|---|---|
init(name:value:) |
初始化节点(指定名称和可选文本值) | let userNode = CNXMLDocument(name: "user") |
cn_addChild(_:) |
添加子节点 | userNode.cn_addChild(nameNode) |
cn_addAttribute(_:) |
添加节点属性 | userNode.cn_addAttribute(CNXMLDocument(name: "id", value: "1001")) |
cn_setValue(_:) |
动态设置节点文本值 | nameNode.cn_setValue("张三") |
cn_getXMLString() |
生成最终 XML 字符串 | let xml = rootNode.cn_getXMLString() |
3.3 XML 生成逻辑(cn_getXMLString)
递归拼接 XML 标签,自动处理 "属性 - 子节点 - 文本值" 的顺序,生成标准 XML 格式:
swift
func cn_getXMLString() -> String {
var xmlString = ""
// 1. 拼接属性(如 id="1001")
let attributeString = attributes.map { " \($0.name)=\"\($0.value ?? "")\"" }.joined()
// 2. 拼接开始标签(如 <user id="1001">)
xmlString += "<\(name)\(attributeString)>"
// 3. 递归拼接子节点(处理嵌套结构)
xmlString += children.map { $0.cn_getXMLString() }.joined()
// 4. 拼接文本值(如 <name>张三</name> 中的"张三")
if let value = value {
xmlString += value
}
// 5. 拼接结束标签(如 </user>)
xmlString += "</\(name)>"
return xmlString
}
使用示例:
构建上述 "用户列表" XML:
less
// 1. 创建根节点
let root = CNXMLDocument(name: "userList")
// 2. 创建第一个用户节点(带属性+子节点)
let user1 = CNXMLDocument(name: "user")
user1.cn_addAttribute(CNXMLDocument(name: "id", value: "1001"))
user1.cn_addChild(CNXMLDocument(name: "name", value: "张三"))
user1.cn_addChild(CNXMLDocument(name: "age", value: "20"))
// 3. 创建第二个用户节点
let user2 = CNXMLDocument(name: "user")
user2.cn_addAttribute(CNXMLDocument(name: "id", value: "1002"))
user2.cn_addChild(CNXMLDocument(name: "name", value: "李四"))
user2.cn_addChild(CNXMLDocument(name: "age", value: "22"))
// 4. 组装根节点并生成XML
root.cn_addChild(user1)
root.cn_addChild(user2)
let xmlString = root.cn_getXMLString()
生成的 XML 字符串:
xml
<userList>
<user id="1001"><name>张三</name><age>20</age></user>
<user id="1002"><name>李四</name><age>22</age></user>
</userList>
四、整体设计思路总结
两个类的设计围绕 "简洁、易用、高效" 三个核心目标,体现了以下技术思路:
1. 封装原生 API,降低使用成本
- 基于系统
XMLParser
(SAX 模式)封装,屏蔽 SAX 解析的 "事件驱动" 复杂性,开发者无需关注解析过程,只需关心 "输入 - 输出"; - 避免手动拼接 XML 字符串,通过对象化 API 降低语法错误风险。
2. 数据结构适配,提升开发效率
- 解析结果转换为
[String: Any]
(字典 / 数组),符合 iOS 开发者的日常数据处理习惯,可直接用于模型转换(如配合HandyJSON
、Codable
); - 构建过程采用 "节点对象" 抽象,支持链式调用,代码可读性更高。
3. 递归逻辑处理嵌套结构
- 无论是解析时的
cn_getElementInfo
,还是构建时的cn_getXMLString
,均采用递归处理嵌套节点,确保支持任意深度的 XML 结构; - 自动识别数组节点,无需开发者手动配置,适配多数业务场景。
4. 访问控制与扩展性平衡
- 核心数据结构(如
CNXMLParserElement
)采用fileprivate
,避免外部修改导致的解析异常; - 暴露的 API(如
cn_parser
、cn_addChild
)设计简洁,同时预留扩展空间(如可新增 "XML 格式化""特殊字符转义" 等功能)。
五、优化建议与扩展方向
当前实现已覆盖基础场景,可根据业务需求进一步优化:
- 特殊字符转义 :在
CNXMLDocument
的cn_getXMLString
中,添加对&
、<
、>
等 XML 特殊字符的转义,避免生成非法 XML; - 错误处理 :在
CNXMLParser
中添加解析错误回调(如parser(_:didFailWithError:)
),返回具体错误信息(如标签不匹配、编码错误); - XML 格式化 :在
cn_getXMLString
中添加缩进、换行逻辑,生成格式化的 XML,便于调试; - 模型绑定 :扩展
CNXMLParser
,支持直接将 XML 解析为自定义模型(而非字典),减少中间转换步骤。
六、结语
CNXMLParser
与CNXMLDocument
通过合理的封装与设计,解决了 iOS 平台下 XML 处理的核心痛点,实现了 "解析 - 构建" 全流程的高效支持。无论是接口数据解析、本地配置文件读写,还是自定义 XML 格式的处理,这两个类都能提供简洁、可靠的解决方案。
希望本文的解析能帮助你更深入地理解 XML 处理的实现思路,同时也能为你的项目提供有价值的参考。如果有进一步的需求(如添加错误处理、扩展模型绑定),可以基于现有代码快速迭代,打造更贴合业务场景的 XML 处理工具。