Tam*_*isk 15 xml parsing swift
我是一个全新的解析,找不到任何没有过时的教程,也没有提出更多的问题.我有一个简单的xml文件url我试图解析.xml非常简单:
<xml>
<record>
<EmpName>A Employee</EmpName>
<EmpPhone>111-222-3333</EmpPhone>
<EmpEmail>a@employee.com</EmpEmail>
<EmpAddress>12345 Fake Street</EmpAddress>
<EmpAddress1>MyTown, Mystate ZIP</EmpAddress1>
</record>
</xml>
Run Code Online (Sandbox Code Playgroud)
并且只想将其保存为NSDictionary(标记为键和数据作为值).到目前为止,我能够成功完成的是在控制台中打印xml字符串:
let url = NSURL(string: "http://www.urlexample.com/file.xml")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
println(NSString(data: data, encoding: NSUTF8StringEncoding))
}
print(task)
task.resume()
Run Code Online (Sandbox Code Playgroud)
我已经浏览过任何我发现的在线教程,这些教程要么过时,要么过于复杂.任何帮助表示赞赏.
Rob*_*Rob 21
过程很简单:
XMLParser对象,将数据传递给它.delegate解析器.所以,在Swift 3/4中,它看起来像:
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
print(error ?? "Unknown error")
return
}
let parser = XMLParser(data: data)
parser.delegate = self
if parser.parse() {
print(self.results ?? "No results")
}
}
task.resume()
Run Code Online (Sandbox Code Playgroud)
问题是你如何实现这些XMLParserDelegate方法.三个关键方法是didStartElement(准备接收字符的地方),foundCharacters(处理解析的实际值)和didEndElement(保存结果的地方).
您询问了如何解析单个记录(即单个字典),但我将向您展示一个更一般的模式来解析它们的一系列,这是XML的一个更常见的情况.如果你不需要一个值数组(或者只是抓住第一个值),你显然可以看到如何简化它.
// a few constants that identify what element names we're looking for inside the XML
// a few constants that identify what element names we're looking for inside the XML
let recordKey = "record"
let dictionaryKeys = Set<String>(["EmpName", "EmpPhone", "EmpEmail", "EmpAddress", "EmpAddress1"])
// a few variables to hold the results as we parse the XML
var results: [[String: String]]? // the whole array of dictionaries
var currentDictionary: [String: String]? // the current dictionary
var currentValue: String? // the current value for one of the keys in the dictionary
Run Code Online (Sandbox Code Playgroud)
和
extension ViewController: XMLParserDelegate {
// initialize results structure
func parserDidStartDocument(_ parser: XMLParser) {
results = []
}
// start element
//
// - If we're starting a "record" create the dictionary that will hold the results
// - If we're starting one of our dictionary keys, initialize `currentValue` (otherwise leave `nil`)
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
if elementName == recordKey {
currentDictionary = [:]
} else if dictionaryKeys.contains(elementName) {
currentValue = ""
}
}
// found characters
//
// - If this is an element we care about, append those characters.
// - If `currentValue` still `nil`, then do nothing.
func parser(_ parser: XMLParser, foundCharacters string: String) {
currentValue? += string
}
// end element
//
// - If we're at the end of the whole dictionary, then save that dictionary in our array
// - If we're at the end of an element that belongs in the dictionary, then save that value in the dictionary
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == recordKey {
results!.append(currentDictionary!)
currentDictionary = nil
} else if dictionaryKeys.contains(elementName) {
currentDictionary![elementName] = currentValue
currentValue = nil
}
}
// Just in case, if there's an error, report it. (We don't want to fly blind here.)
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
print(parseError)
currentValue = nil
currentDictionary = nil
results = nil
}
}
Run Code Online (Sandbox Code Playgroud)
对于Swift 2的演绎,请参阅此答案的上一版本.
Rob 对Swift 3 / 4的回答
func getDataFrom(url: URL, completion: @escaping (_ data: Data?, _ error: Error?)->()) {
let session = URLSession(configuration: .default)
let download = session.dataTask(with: url) { data, response, error in
completion(data, error)
}
download.resume()
}
getDataFrom(url: url) { data, error in
guard let data = data else {
return
}
let parser = XMLParser(data: data)
parser.delegate = self
if parser.parse() {
print(self.results)
}
}
// a few constants that identify what element names we're looking for inside the XML
let recordKey = "record"
let dictionaryKeys = ["EmpName", "EmpPhone", "EmpEmail", "EmpAddress", "EmpAddress1"]
// a few variables to hold the results as we parse the XML
var results: [[String: String]]! // the whole array of dictionaries
var currentDictionary: [String: String]! // the current dictionary
var currentValue: String?
// start element
//
// - If we're starting a "record" create the dictionary that will hold the results
// - If we're starting one of our dictionary keys, initialize `currentValue` (otherwise leave `nil`)
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
if elementName == recordKey {
self.currentDictionary = [String : String]()
} else if dictionaryKeys.contains(elementName) {
self.currentValue = String()
}
}
// found characters
//
// - If this is an element we care about, append those characters.
// - If `currentValue` still `nil`, then do nothing.
func parser(_ parser: XMLParser, foundCharacters string: String) {
self.currentValue? += string
}
// end element
//
// - If we're at the end of the whole dictionary, then save that dictionary in our array
// - If we're at the end of an element that belongs in the dictionary, then save that value in the dictionary
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == self.recordKey {
self.results.append(self.currentDictionary)
self.currentDictionary = nil
} else if dictionaryKeys.contains(elementName) {
self.currentDictionary[elementName] = currentValue
self.currentValue = nil
}
}
// Just in case, if there's an error, report it. (We don't want to fly blind here.)
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
print(parseError)
self.currentValue = nil
self.currentDictionary = nil
self.results = nil
}
Run Code Online (Sandbox Code Playgroud)
我编写了一个用于将XML映射到对象的pod,称为XMLMapper。(使用与ObjectMapper相同的技术)
对于您想要实现的目标,您可以简单地使用以下XMLSerialization类:
let url = URL(string: "http://www.urlexample.com/file.xml")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
do{
let xmlDictionary = try XMLSerialization.xmlObject(with: data!) as? [String: Any]
} catch {
print("Serialization error occurred: \(error.localizedDescription)")
}
}
task.resume()
Run Code Online (Sandbox Code Playgroud)
您还可以实现以下XMLMappable协议:
class XMLResponse: XMLMappable {
var nodeName: String!
var record: Record?
required init(map: XMLMap) {
}
func mapping(map: XMLMap) {
record <- map["record"]
}
}
class Record: XMLMappable {
var nodeName: String!
var empName: String!
var empPhone: String!
var empEmail: String?
var empAddress: String?
var empAddress1: String?
required init(map: XMLMap) {
}
func mapping(map: XMLMap) {
empName <- map["EmpName"]
empPhone <- map["EmpPhone"]
empEmail <- map["EmpEmail"]
empAddress <- map["EmpAddress"]
empAddress1 <- map["EmpAddress1"]
}
}
Run Code Online (Sandbox Code Playgroud)
并使用XMLMapper类将响应XML映射到该对象:
let xmlResponse = XMLMapper<XMLResponse>().map(XMLObject: xmlDictionary)
Run Code Online (Sandbox Code Playgroud)
更新:掩盖@ fahim-parkar的评论。
要映射一个对象(或数组中的许多对象),请使用相同的技术。
例如,映射以下XML:
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>Apple Hot News</title>
<link>http://www.apple.com/hotnews/</link>
<description>Hot News provided by Apple.</description>
<language>en-us</language>
<copyright>Copyright 2016, Apple Inc.</copyright>
<pubDate>Tue, 26 Apr 2016 15:53:26 PDT</pubDate>
<lastBuildDate>Tue, 26 Apr 2016 15:53:26 PDT</lastBuildDate>
<category>Apple</category>
<generator>In house</generator>
<docs>http://blogs.law.harvard.edu/tech/rss/</docs>
<item>
<title>Apple Reports Second Quarter Results</title>
<link>http://www.apple.com/pr/library/2016/04/26Apple-Reports-Second-Quarter-Results.html?sr=hotnews.rss</link>
<description>Apple today announced financial results for its fiscal 2016 second quarter ended March 26. The company posted quarterly revenue of $50.6 billion and quarterly net income of $10.5 billion, or $1.90 per diluted share. These results compare to revenue of $58 billion and net income of $13.6 billion, or $2.33 per diluted share, in the year-ago quarter. Gross margin was 39.4 percent compared to 40.8 percent in the year-ago quarter. International sales accounted for 67 percent of the quarter’s revenue. “Our team executed extremely well in the face of strong macroeconomic headwinds,” said Tim Cook, Apple’s CEO. “We are very happy with the continued strong growth in revenue from Services, thanks to the incredible strength of the Apple ecosystem and our growing base of over 1 billion active devices.”</description>
<pubDate>Tue, 26 Apr 2016 14:44:21 PDT</pubDate>
</item>
<item>
<title>Final Cut Pro X helps small company delight world’s biggest clients</title>
<link>http://www.apple.com/final-cut-pro/in-action/trim-editing/?sr=hotnews.rss</link>
<description>When Trim Editing started creating music videos over a decade ago, just paying the rent was a huge accomplishment. Now, the small East London company is crafting award-winning visuals for big brands — like Audi, Nike, Adidas, and Guinness — propelled by the power of Final Cut Pro X. The video editing software’s comprehensive features allow Trim Editing to organize film and audio clips, pull together compelling projects, and make changes on the fly. “When I’m playing back an edit for a director, they’ll say, ‘Okay, let’s go and make those changes I talked about.’ I’ll say, ‘Oh, no, they’re already done,’ and we’ll jump back and watch it again. People can’t believe that I’ve magically done the change before we even finish playback,” says editor Thomas Grove Carter. </description>
<pubDate>Wed, 20 Apr 2016 10:05:59 PDT</pubDate>
</item>
<item>
<title>Apple Introduces 9.7-inch iPad Pro</title>
<link>http://www.apple.com/ipad-pro/?sr=hotnews.rss</link>
<description>Apple today introduced the 9.7-inch iPad Pro, which at just under one pound features a new pro Retina display with greater brightness, wider color gamut, lower reflectivity, Night Shift mode, and new True Tone display technology. The new iPad Pro also has a 64-bit A9X chip that rivals most portable PCs. “iPad Pro is a new generation of iPad that is indispensable and immersive, enabling people to be more productive and more creative. It’s incredibly fast, extremely portable, and completely natural to use with your fingers, Apple Pencil, and Smart Keyboard. And now it comes in two sizes,” said Philip Schiller, Apple’s senior vice president of Worldwide Marketing.</description>
<pubDate>Mon, 21 Mar 2016 12:00:03 PDT</pubDate>
</item>
</channel>
</rss>
Run Code Online (Sandbox Code Playgroud)
您需要创建以下模型类:
class RSSFeed: XMLMappable {
var nodeName: String!
var channel: Channel?
required init?(map: XMLMap) {}
func mapping(map: XMLMap) {
channel <- map["channel"]
}
}
class Channel: XMLMappable {
var nodeName: String!
var title: String?
var link: URL?
var description: String?
var language: String?
var copyright: String?
var pubDate: Date?
var lastBuildDate: Date?
var category: String?
var generator: String?
var docs: URL?
var items: [Item]?
private static var dateFormatter: DateFormatter = {
var dateFormatter = DateFormatter()
dateFormatter,dateFormat = "E, d MMM yyyy HH:mm:ss zzz"
return dateFormatter
}()
required init?(map: XMLMap) {}
func mapping(map: XMLMap) {
title <- map["title"]
link <- (map["link"], XMLURLTransform())
description <- map["description"]
language <- map["language"]
copyright <- map["copyright"]
pubDate <- (map["pubDate"], XMLDateFormatterTransform(dateFormatter: Channel.dateFormatter))
lastBuildDate <- (map["lastBuildDate"], XMLDateFormatterTransform(dateFormatter: Channel.dateFormatter))
category <- map["category"]
generator <- map["generator"]
docs <- (map["docs"], XMLURLTransform())
items <- map["item"]
}
}
class Item: XMLMappable {
var nodeName: String!
var title: String?
var link: URL?
var description: String?
var pubDate: Date?
private static var dateFormatter: DateFormatter = {
var dateFormatter = DateFormatter()
dateFormatter,dateFormat = "E, d MMM yyyy HH:mm:ss zzz"
return dateFormatter
}()
required init?(map: XMLMap) {}
func mapping(map: XMLMap) {
title <- map["title"]
link <- (map["link"], XMLURLTransform())
description <- map["description"]
pubDate <- (map["pubDate"], XMLDateFormatterTransform(dateFormatter: Item.dateFormatter))
}
}
Run Code Online (Sandbox Code Playgroud)
使用本机,URLSession您可以使用XMLSerialization和映射RSS XML响应XMLMapper:
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
let xmlDictionary = try XMLSerialization.xmlObject(with: data!) as? [String: Any]
let rssFeed = XMLMapper<RSSFeed>().map(XMLObject: xmlDictionary)
print(rssFeed?.items?.first?.title ?? "nil")
} catch {
print("Serialization error occurred: \(error.localizedDescription)")
}
}
task.resume()
Run Code Online (Sandbox Code Playgroud)
如果您不介意使用Alamofire进行请求,则使用以下代码进行映射会发现XMLMapper / Requests子规范要容易得多:
Alamofire.request(url).responseXMLObject { (response: DataResponse<RSSFeed>) in
let rssFeed = response.result.value
print(rssFeed?.items?.first?.title ?? "nil")
}
Run Code Online (Sandbox Code Playgroud)
我希望这是有帮助的。