私は構文解析に慣れていないので、時代遅れではなく、問題を提起しないチュートリアルを見つけることができません。解析しようとしている単純なxmlファイルのURLがあります。 xmlは非常に単純です。
<xml>
<record>
<EmpName>A Employee</EmpName>
<EmpPhone>111-222-3333</EmpPhone>
<EmpEmail>[email protected]</EmpEmail>
<EmpAddress>12345 Fake Street</EmpAddress>
<EmpAddress1>MyTown, Mystate Zip</EmpAddress1>
</record>
</xml>
そして、これを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()
私が見つけたオンラインチュートリアルを経験しましたが、古くなっているか、複雑すぎます。どんな助けでもありがたいです。
プロセスは簡単です:
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()
問題は、XMLParserDelegate
メソッドをどのように実装するかです。 3つの重要なメソッドは、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
そして
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
}
}
Swift 2レンディションについては、 この回答の以前のリビジョン を参照してください。
XMLMapper と呼ばれる、XMLをオブジェクトにマッピングするポッドを作成しました。 ( 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()
次のように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"]
}
}
そして、XMLMapper
クラスを使用して、応答XMLをそのオブジェクトにマッピングします。
let xmlResponse = XMLMapper<XMLResponse>().map(XMLObject: xmlDictionary)
[〜#〜] update [〜#〜]:@ 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>
次のようなモデルクラスを作成する必要があります。
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))
}
}
ネイティブURLSession
を使用すると、XMLSerialization
およびXMLMapper
を使用してRSS XML応答をマップできます。
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()
リクエストに Alamofire を使用してもかまわない場合、このコードを使用してマッピングする XMLMapper/Requests サブスペックがはるかに簡単になります。
Alamofire.request(url).responseXMLObject { (response: DataResponse<RSSFeed>) in
let rssFeed = response.result.value
print(rssFeed?.items?.first?.title ?? "nil")
}
これがお役に立てば幸いです。
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
}
Swift 5の場合、XML形式のコード:
これが私のXMLフォーマットです:
<?xml version="1.0" encoding="EUC-KR" ?>
<OUTPUT>
<Result>OK</Result>
<Record>
<Item>
<RecDate>2019-05-07</RecDate>
<iCount>1</iCount>
</Item>
<Item>
<RecDate>2019-05-17</RecDate>
<iCount>1</iCount>
</Item>
<Item>
<RecDate>2019-05-19</RecDate>
<iCount>1</iCount>
</Item>
<Item>
<RecDate>2019-05-20</RecDate>
<iCount>2</iCount>
<sTitle>혈당</sTitle>
</Item>
<Item>
<RecDate>2019-05-22</RecDate>
<iCount>1</iCount>
</Item>
</Record>
APIからのデータ応答:
let xml = XML.parse(data)
if let resultText = xml.OUTPUT.Result.text {
if resultText == "OK" {
// self.parsingDataXML(data: data)
var _dic:[String:String] = [:]
for item in xml.OUTPUT.Record.Item {
print(iCount.RecDate.text!)
}
}
}
接続APIにAlamofireを使用しています
Alamofire.request(_url,
method: .post,
parameters: _parameters,
encoding: URLEncoding.default,
headers: [:])
.validate()
.responseData { (response) in
if let data = response.data {}
}