Работа с 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 предоставляет несколько методов делегата для обнаружения проблем:
parser(_:parseErrorOccurred:)— фатальная ошибка парсинга-
Также в библиотеке
Рекомендаций