You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中如何用XMLParser解析XML元素的属性值?

解决Swift中XMLParserDelegate提取元素属性值的问题

问题描述

需要解析METAR格式的XML数据,目前已通过XMLParserDelegate成功获取元素标签内的文本内容(例如<flight_category>IFR</flight_category>中的IFR),但无法提取元素属性的值,比如从<sky_condition sky_cover="OVC" cloud_base_ft_agl="2100"/>中获取sky_cover对应的OVC值。

待解析XML示例

<METAR>
   <sky_condition sky_cover="OVC" cloud_base_ft_agl="2100"/> <!-- 需提取sky_cover的OVC -->
   <flight_category>IFR</flight_category>
   <metar_type>METAR</metar_type>
</METAR>

现有代码

FeedParser类

import Foundation

class FeedParser: NSObject, XMLParserDelegate {
    private var airportItems: [FlightCategory] = [] // 跟踪解析进度
    private var currentAirportElement: String = "" // 跟踪当前解析的元素
    private var parserCompletiongHandler: (([FlightCategory]) -> Void)? // 解析完成回调
    
    private var currentAirportFlightCategory: String = "" {
        didSet {
            // 去除空白字符
            currentAirportFlightCategory = currentAirportFlightCategory.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
        }
    }
    
    private var currenAirportSkyCondition: String = "" {
        didSet {
            currenAirportSkyCondition = currenAirportSkyCondition.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
        }
    }
    
    // 解析XML Feed
    func parseFeed(url: String, completionHandler: (([FlightCategory]) -> Void)?) {
        self.parserCompletiongHandler = completionHandler
        
        let request = URLRequest(url: URL(string: url)!)
        let session = URLSession.shared
        
        let task = session.dataTask(with: request) { data, response, error in
            guard let data = data else {
                if let error = error {
                    print(error.localizedDescription)
                }
                return
            }
            
            let parser = XMLParser(data: data)
            parser.delegate = self
            parser.parse()
        }
        task.resume()
    }
    
    // MARK: - XML Parser Delegate
    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
        currentAirportElement = elementName
        if currentAirportElement == "METAR" {
            currentAirportFlightCategory = ""
            currenAirportSkyCondition = ""
        }
    }
    
    func parser(_ parser: XMLParser, foundCharacters string: String) {
        switch currentAirportElement {
        case "flight_category": currentAirportFlightCategory += string
        default: break
        }
    }
    
    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        if elementName == "METAR" {
            let airportItem = FlightCategory(flight_category: currentAirportFlightCategory, sky_condition: currenAirportSkyCondition)
            self.airportItems.append(airportItem)
        }
    }
    
    func parserDidEndDocument(_ parser: XMLParser) {
        parserCompletiongHandler?(airportItems)
    }
    
    func parser(_ parser: XMLParser, parseErrorOccurred parseError: any Error) {
        print(parseError.localizedDescription)
    }
}

XML加载代码

private func loadXMLData() async {
    print("hmmmmm")
    let acwXMLURL = "https://aviationweather.gov/api/data/dataserver?requestType=retrieve&dataSource=metars&stationString=KVNY&startTime=2024-03-10T21%3A44%3A22Z&format=xml&mostRecent=true"
    
    let feeder = FeedParser()
    feeder.parseFeed(url: acwXMLURL) { data in
        self.detaileHomeAirportXML = data
    }
}

private func handleXMLData() async {
    detaileHomeAirportXML?.forEach { item in
        currentFltCat = item.flight_category
        model.airportFltCat = item.flight_category
        print("XML data handled: \(currentFltCat)")
        
        if currentFltCat == "VFR" {
            color = Color(.green)
        } else if currentFltCat == "MVFR" {
            color = Color(.blue)
        } else if currentFltCat == "IFR" {
            color = Color(.red)
        } else if currentFltCat == "LIFR" {
            color = Color(red: 208 / 255, green: 45 / 255, blue: 208 / 255)
        }
    }
}

解决方法

核心是利用XMLParserDelegate的parser(_:didStartElement:namespaceURI:qualifiedName:attributes:)方法中的attributeDict参数,该参数包含了当前元素的所有属性键值对。对于自闭合的<sky_condition>标签,直接在这个方法中提取属性值即可,无需通过foundCharacters处理。

修改后的关键代码部分

修改parser(_:didStartElement:namespaceURI:qualifiedName:attributes:)方法:

func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
    currentAirportElement = elementName
    if currentAirportElement == "METAR" {
        currentAirportFlightCategory = ""
        currenAirportSkyCondition = ""
    } else if currentAirportElement == "sky_condition" {
        // 从属性字典中提取sky_cover的值,为空则设为空字符串
        currenAirportSkyCondition = attributeDict["sky_cover"] ?? ""
    }
}

修改后的完整FeedParser类

import Foundation

class FeedParser: NSObject, XMLParserDelegate {
    private var airportItems: [FlightCategory] = [] // 跟踪解析进度
    private var currentAirportElement: String = "" // 跟踪当前解析的元素
    private var parserCompletiongHandler: (([FlightCategory]) -> Void)? // 解析完成回调
    
    private var currentAirportFlightCategory: String = "" {
        didSet {
            // 去除空白字符
            currentAirportFlightCategory = currentAirportFlightCategory.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
        }
    }
    
    private var currenAirportSkyCondition: String = "" {
        didSet {
            currenAirportSkyCondition = currenAirportSkyCondition.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
        }
    }
    
    // 解析XML Feed
    func parseFeed(url: String, completionHandler: (([FlightCategory]) -> Void)?) {
        self.parserCompletiongHandler = completionHandler
        
        let request = URLRequest(url: URL(string: url)!)
        let session = URLSession.shared
        
        let task = session.dataTask(with: request) { data, response, error in
            guard let data = data else {
                if let error = error {
                    print(error.localizedDescription)
                }
                return
            }
            
            let parser = XMLParser(data: data)
            parser.delegate = self
            parser.parse()
        }
        task.resume()
    }
    
    // MARK: - XML Parser Delegate
    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
        currentAirportElement = elementName
        if currentAirportElement == "METAR" {
            currentAirportFlightCategory = ""
            currenAirportSkyCondition = ""
        } else if currentAirportElement == "sky_condition" {
            // 提取sky_cover属性值,为空则设为空字符串
            currenAirportSkyCondition = attributeDict["sky_cover"] ?? ""
        }
    }
    
    func parser(_ parser: XMLParser, foundCharacters string: String) {
        switch currentAirportElement {
        case "flight_category": currentAirportFlightCategory += string
        default: break
        }
    }
    
    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        if elementName == "METAR" {
            let airportItem = FlightCategory(flight_category: currentAirportFlightCategory, sky_condition: currenAirportSkyCondition)
            self.airportItems.append(airportItem)
        }
    }
    
    func parserDidEndDocument(_ parser: XMLParser) {
        parserCompletiongHandler?(airportItems)
    }
    
    func parser(_ parser: XMLParser, parseErrorOccurred parseError: any Error) {
        print(parseError.localizedDescription)
    }
}

说明

  • <sky_condition>是自闭合标签,没有内部文本内容,所以不需要在foundCharacters方法中处理它的内容。
  • 通过attributeDict["sky_cover"]直接获取属性值,使用?? ""处理属性不存在的情况,避免空值。

内容的提问来源于stack exchange,提问作者Yuda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 16:45:56