当前位置: > > > Swift - 解析XML格式数据(分别使用GDataXML和KissXML)

Swift - 解析XML格式数据(分别使用GDataXML和KissXML)

在做一些应用的时候经常需要用到XML解析,比如获取Web Service数据,读取RSS新闻或者博客数据源。下面演示了两个非常方便高效的XML库在Swift里的调用方法。
假设需要被解析的XML数据文件users.xml如下:
<?xml version="1.0" encoding="utf-8"?>  
<Users>  
    <User id="101">
        <name>航歌</name>
        <tel>
        	<mobile>1234567</mobile>
        	<home>025-8100000</home>
        </tel>  
    </User>  
    <User id="102">
        <name>hangge</name>
        <tel>
        	<mobile>8989889</mobile>
        	<home>025-8122222</home>
        </tel> 
    </User>  
</Users>
我们需要实现的功能是解析并打印出需要的数据:
User: uid:101,uname:航歌,mobile:1234567,home:025-8100000
User: uid:102,uname:hangge,mobile:8989889,home:025-8122222

一,使用GDataXML(这个是google出品的) 
1,GitHub主页地址:https://github.com/google/gdata-objectivec-client

2,在build phases -> Link Binary With Libraries中,点击“+”添加“libxml2.2.tbd”
 


3,在build setting -> Header Search Paths里添加 ${SDK_DIR}/usr/include/libxml2
 

4,在build setting里的Objective-C编译选项里,把自动引用计数改为No,否则在应用GDataXML库时会编译有错
 

5,添加bridge.h头文件并设置到编译参数里
#import "GDataXMLNode.h"
6,导入GDataXML库文件(GDataXMLNode.h和GDataXMLNode.m),代码结构如下:
 

7,开始解析(ViewController.swift)
import UIKit

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let label:UILabel = UILabel(frame:CGRect(x: 100, y: 100, width: 300, height: 100))
        label.text = "输出结果在控制台"
        self.view.addSubview(label)
        //测试Swift调用Object的XML库功能
        testXML()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    func testXML() {
        //获取xml文件路径

        let file = Bundle.main.path(forResource: "users", ofType: "xml")
        let url = URL(fileURLWithPath: file!)
        //获取xml文件内容
        let xmlData = try! Data(contentsOf: url)
        //可以转换为字符串输出查看
        //print(String(data:xmlData, encoding:String.Encoding.utf8))
        
        //使用NSData对象初始化文档对象
        //这里的语法已经把OC的初始化函数直接转换过来了
        let doc:GDataXMLDocument = try! GDataXMLDocument(data:xmlData, options : 0)
        
        //获取Users节点下的所有User节点,显式转换为element类型编译器就不会警告了
        //let users = doc.rootElement().elements(forName: "User") as! [GDataXMLElement]
        
        //通过XPath方式获取Users节点下的所有User节点,在路径复杂时特别方便
        let users = try! doc.nodes(forXPath: "//User", namespaces:nil) as! [GDataXMLElement]
        
        for user in users {
            //User节点的id属性
            let uid = user.attribute(forName: "id").stringValue()
            //获取name节点元素
            let nameElement = user.elements(forName: "name")[0] as! GDataXMLElement
            //获取元素的值
            let uname =  nameElement.stringValue()
            //获取tel子节点
            let telElement = user.elements(forName: "tel")[0] as! GDataXMLElement
            //获取tel节点下mobile和home节点
            let mobile = (telElement.elements(forName: "mobile")[0]
                as! GDataXMLElement).stringValue()
            let home = (telElement.elements(forName: "home")[0]
                as! GDataXMLElement).stringValue()
            //输出调试信息
            print("User: uid:\(uid!),uname:\(uname!),mobile:\(mobile!),home:\(home!)")
        }
    }
}
示例下载:
GDataXML.zip(2015-12-02 Swift2)
hangge_646_1.zip(2016-09-23 Swift3最新版)


二,使用KissXML(DDXML) 
GitHub主页地址:https://github.com/robbiehanson/KissXML

1,使用方法
(1)前面配置同GDataXML 的1,2,3步一样
(2)添加bridge.h头文件并设置到编译参数里
#import "DDXML.h"
#import "DDXMLElementAdditions.h"
(3)导入DDXML库文件

(4)开始解析(ViewController.swift)
import UIKit

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let label:UILabel = UILabel(frame:CGRect(x: 100, y: 100, width: 300, height: 100));
        label.text = "输出结果在控制台"
        self.view.addSubview(label)
        //测试Swift调用Object的XML库功能
        testXML()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    func testXML() {
        //获取xml文件路径
        let file = Bundle.main.path(forResource: "users", ofType: "xml")
        let url = URL(fileURLWithPath: file!)
        //获取xml文件内容
        let xmlData = try! Data(contentsOf: url)
        
        //构造XML文档
        let doc = try! DDXMLDocument(data: xmlData, options:0)
        
        //利用XPath来定位节点(XPath是XML语言中的定位语法,类似于数据库中的SQL功能)
        let users = try! doc.nodes(forXPath: "//User") as! [DDXMLElement]
        for user in users {
            let uid = user.attribute(forName: "id")!.stringValue
            //DDXMLElementAdditions提供了elementForName获取单个节点,不用获取数组了
            let uname = user.forName("name")!.stringValue
            //获取tel节点的子节点
            let telElement = user.forName("tel")! as DDXMLElement
            let mobile = (telElement.forName("mobile")! as DDXMLElement).stringValue
            let home = (telElement.forName("home")! as DDXMLElement).stringValue
            print("User: uid:\(uid!),uname:\(uname!),mobile:\(mobile!),home:\(home!)")
        }
    }
}
示例下载:
DDXML.zip(2015-12-02 Swift2)
hangge_646_2.zip(2016-09-23 Swift3最新版)

2,DDXML获取子元素:forName(name: String)与elements(forName: String)
forName(name: String) 是根据name取同名的子结点,如果有多个 ,就只取第一个。
elements(forName: String) 是把所有的同名子节点都返回。
比如把上面XML数据文件users.xml每个人都添加多个mobile节点:
<?xml version="1.0" encoding="utf-8"?>  
<Users>  
    <User id="101">
        <name>航歌</name>
        <tel>
        	<mobile>0000001</mobile>
        	<mobile>1234567</mobile>
        	<mobile>9999999</mobile>
        	<home>025-8100000</home>
        </tel>  
    </User>  
    <User id="102">
        <name>hangge</name>
        <tel>
        	<mobile>8989889</mobile>
        	<mobile>1345678</mobile>
        	<home>025-8122222</home>
        </tel> 
    </User>  
</Users>
如果想把所有的mobile节点都遍历出来,就要使用elements(forName: String)
//let mobile = (telElement.forName("mobile")! as DDXMLElement).stringValue
for element in telElement.elements(forName:"mobile"){
    let mobile = element.stringValue
    print("mobile:\(mobile!)")
}

3,扩展DDXML方便使用
使用 DDXML elementForName 方法访问子节点时,要不断地拆箱装箱,如果节点层级深的话写起来就会比较麻烦。
这里对 DDXMLElement 类做了扩展,使其通过索引即可访问子节点。同时添加了个 attributeValue() 方法,便于获取属性值。
extension DDXMLElement {
    //通过索引获取子节点
    subscript(key: String) -> DDXMLElement {
        get {
            let r = self.forName(key)
            return r!
        }
    }
}
可以比较下原来的取值方式和现在的方式:
/********* 原始的 ****************/
let uid = user.attribute(forName: "id")!.stringValue
let uname = user.forName("name")!.stringValue

let telElement = user.forName("tel")! as DDXMLElement
let mobile = (telElement.forName("mobile")! as DDXMLElement).stringValue
let home = (telElement.forName("home")! as DDXMLElement).stringValue

print("User: uid:\(uid!),uname:\(uname!),mobile:\(mobile!),home:\(home!)")


/******** 扩展后的 *****************/
let uid = user.attribute(forName: "id")!.stringValue
let uname = user["name"].stringValue

let mobile = user["tel"]["mobile"].stringValue
let home = user["tel"]["home"].stringValue

print("User: uid:\(uid!),uname:\(uname!),mobile:\(mobile!),home:\(home!)")
评论7
  • 7楼
    2018-02-02 17:20
    尾巴

    import UIKit

    struct uuui {
    var uid: String?
    var uname: String?
    var mobile: String?
    var home: String?
    }
    class main3ViewController: UIViewController,XMLParserDelegate {
    var users:[uuui] = []
    //........

    航哥,我用了Xcode自带的XMLParserDelegate 方法,感觉会方便些,您看下,Xcode9.2做的,解析的也是这个xml

    站长回复

    谢谢你的提醒,使用原生库确实也有省事的地方,至少不用引入其他第三方库了。后面我考虑再写篇XMLParserDelegate的文章作为补充,最后再次感谢你提供的建议和代码。

  • 6楼
    2016-08-02 12:41
    504755784@qq.com

    我的XML是这样的:
    <?xml version="1.0" encoding="utf-8"?>
    <地球>
    <亚洲>
    <国家>
    <name>中国</name>
    <desc>中国是一个古代文明的国家</desc>
    </国家>
    <国家>
    <name>韩国</name>
    <desc>韩国的女人很丑</desc>
    </国家>
    <国家>
    <name>朝鲜</name>
    <desc>朝鲜我不知道什么样的国家</desc>
    </国家>
    </亚洲>
    <欧洲>
    <国家>
    <name>英国</name>
    <desc>英国是一个古代文明的国家</desc>
    </国家>
    <国家>
    <name>德国</name>
    <desc>德国战车队</desc>
    </国家>
    </欧洲>
    </地球>

    目前我遇到的问题是:
    1.比如:亚洲有三个国家,但是我解析的时候它只显示整个亚洲一个,没有显示不到里面的国家,欧洲也如此。怎么解决洲和对应国家的关系?

    站长回复

    不确定你代码是怎么写的。我这里以使用KissXML解析为例,如果要解析出亚洲下所有的国家,代码是:

    let countrys = try! doc.nodes(forXPath: "//亚洲/国家") as! [DDXMLElement]

    for country in countrys {
        let name = country.forName("name")!.stringValue
        let desc = country.forName("desc")!.stringValue
        print("\(name):\(desc)")
    }

  • 5楼
    2016-07-24 17:22
    yaruqmen

    博主,我要用XML实现这样一个功能:第一页要呈现亚洲,欧洲等各州的国家数,然后选择一个洲,第二页要呈现对应这个洲的国家。然后选择一个国家,第三页要呈现选择国家的基本情况。能不能帮我一个忙。

    站长回复

    你可以做三个VC页面,第一个页面表格加载xml第一层的数据(各个州),点击单元格跳到第二个页面。第二个页面表格加载对应洲节点下数据(各个国家),点击单元格跳到第最后一个页面。也就是把对应国家的节点数据加载显示即可。

  • 4楼
    2016-03-11 10:55
    chaozhang

    请问一下怎么把objective-c那个引用计数改为no之后报错
    SDWebImage/SDWebImagePrefetcher.m:28:1: @synthesize of 'weak' property is only allowed in ARC or GC mode

    站长回复

    这个我也不知道什么原因,我这边测试了下没问题的。

  • 3楼
    2015-12-02 12:21
    Albert

    请问下载的文件里libxml2.2.dylib怎么都不见了的啊

    站长回复

    libxml2.2.dylib现在改成libxml2.2.tbd了,文章相关代码已修改。

  • 2楼
    2015-12-01 22:10
    Albert

    站长,能把DDXML改一下向swiftyjson那样用么?
    let mobile = (telElement.elementForName("mobile") as DDXMLElement).stringValue()
    改成
    let mobile = user["tel"]["mobile"] as string
    这样

    站长回复

    想要用这种方式应该是没问题的,只要扩展下就好了。我周末抽空写个。
    (2015-12-05:已给DDXMLElement添加扩展,文章已更新,你可以看下。)

  • 1楼
    2015-11-18 14:24
    香蕉

    很好用

    站长回复

    ^_^