Работа с XML в Swift (iOS/macOS): парсинг, создание и обработка

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Работа с XML в Swift (iOS/macOS)

XML (Extensible Markup Language) остаётся востребованным форматом для обмена данными, конфигурационных файлов и хранения структурированной информации. В экосистеме Apple (iOS, macOS, watchOS, tvOS) встроенные средства Foundation предоставляют два основных подхода к работе с XML: потоковый парсинг (SAX-подобный) с помощью XMLParser и древовидный DOM-подход через XMLDocument (доступен на macOS). В этой статье мы подробно разберём оба метода, приведём практические примеры и рассмотрим типичные сценарии использования.

1. Потоковый парсинг с XMLParser

XMLParser — это событийно-ориентированный парсер, входящий в Foundation. Он не загружает весь XML в память, а последовательно разбирает документ, вызывая методы делегата при обнаружении элементов, атрибутов, текста и т.д. Это идеальное решение для больших файлов или потоковых данных.

Базовый пример парсинга RSS-ленты

import Foundation

// 1. Создаём класс-делегат
class RSSParserDelegate: NSObject, XMLParserDelegate {
    var currentElement = ""
    var currentTitle = ""
    var currentLink = ""
    var items: [(title: String, link: String)] = []
    
    // Вызывается при открытии тега
    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
        currentElement = elementName
        if elementName == "item" {
            currentTitle = ""
            currentLink = ""
        }
    }
    
    // Вызывается при обнаружении текстового содержимого
    func parser(_ parser: XMLParser, foundCharacters string: String) {
        switch currentElement {
        case "title": currentTitle += string
        case "link": currentLink += string
        default: break
        }
    }
    
    // Вызывается при закрытии тега
    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        if elementName == "item" {
            items.append((title: currentTitle.trimmingCharacters(in: .whitespacesAndNewlines),
                          link: currentLink.trimmingCharacters(in: .whitespacesAndNewlines)))
        }
    }
}

// 2. Использование
let xmlString = """
<rss>
    <channel>
        <item>
            <title>Swift 5.9 Features</title>
            <link>https://example.com/swift-5-9</link>
        </item>
    </channel>
</rss>
"""

guard let data = xmlString.data(using: .utf8) else { fatalError() }
let parser = XMLParser(data: data)
let delegate = RSSParserDelegate()
parser.delegate = delegate

if parser.parse() {
    for item in delegate.items {
        print("Title: \(item.title), Link: \(item.link)")
    }
} else {
    print("Parse error: \(parser.parserError?.localizedDescription ?? "unknown")")
}

2. Древовидная модель: XMLDocument (только macOS)

На macOS (и в приложениях, использующих Swift Package Manager с фреймворком Foundation в режиме macOS) доступен класс XMLDocument, наследующий от XMLNode. Он позволяет загрузить весь XML в память и манипулировать им как деревом. Это удобно для небольших документов, когда нужен случайный доступ к узлам.

Создание и изменение XML-документа

import Foundation

// Создаём корневой элемент
let rootElement = XMLElement(name: "catalog")
let document = XMLDocument(rootElement: rootElement)

// Добавляем дочерний элемент с атрибутом
let book = XMLElement(name: "book")
book.addAttribute(XMLNode.attribute(withName: "id", stringValue: "101") as! XMLNode)
let title = XMLElement(name: "title", stringValue: "Swift Programming")
let author = XMLElement(name: "author", stringValue: "Apple Inc.")
book.addChild(title)
book.addChild(author)
rootElement.addChild(book)

// Преобразуем в строку
let xmlData = document.xmlData(options: .nodePrettyPrint)
if let xmlString = String(data: xmlData, encoding: .utf8) {
    print(xmlString)
}

Поиск элементов с XPath

// Предположим, у нас есть загруженный XMLDocument
let xpath = "//book[@id='101']/title"
if let nodes = try? document.nodes(forXPath: xpath) {
    for node in nodes {
        print("Title: \(node.stringValue ?? "")")
    }
}

3. Обработка ошибок и валидация

При работе с XML важно корректно обрабатывать ошибки. XMLParser предоставляет несколько методов делегата для обнаружения проблем:

Рекомендаций