У меня есть XML-файл, содержащий следующие данные:
<Book>
<title>Some Random Book</title>
<author>Someone Someone</author>
<book_type>
<FICT />
<REF />
</book_type>
</Book>
Я пытаюсь написать для него структуру, но не знаю, как бороться с самозакрывающимися тегами внутри "book_type". Это перечисления? Не все книги будут иметь «book_type», а если и есть, то их может быть несколько, как в примере выше.
Вот что у меня есть, не уверен, что это правильно:
struct Book {
var title: String?
var author: String?
var book_type: BookType?
}
enum BookType {
case fiction
case nonfiction
case reference
case autobiography
}
Спасибо!
Каждый тег в XML можно рассматривать как представляющий объект. В данном случае Book
— это сложный объект с несколькими атрибутами. title
и author
являются простыми объектами и могут быть представлены существующим типом данных (String). Из примера видно, что Book
может иметь несколько типов книг, поэтому book_type
можно представить как массив. Это может быть массив строк или перечислений, это зависит от вас и от того, как эти значения будут использоваться в другом месте кода.
Что касается самозакрывающихся тегов, я считаю, что имя тега обычно представляет собой имя атрибута, а не значение тега. Самозакрывающийся тег аналогичен объекту со значениями по умолчанию. В случае наличия нескольких значений может быть сложно увидеть разницу, поскольку вы не даете имена элементам в списке.
Я бы организовал данные XML примерно так:
<Book>
<title>Some Random Book</title>
<author>Someone Someone</author>
<book_type>
<type>FICT</type>
<type>REF</type>
</book_type>
</Book>
И структура модели Swift:
struct Book {
var title: String?
var author: String?
var book_type: [BookType]
}
enum BookType {
case fiction
case nonfiction
case reference
case autobiography
}
Надеюсь, это поможет.
Спасибо! К сожалению, я не создаю XML — я получаю его с самозакрывающимися тегами через API.
Проблема, с которой вы столкнулись, не имеет ничего общего с анализом XML. Это действительный XML; разобрать его достаточно легко. Проблема в том, что вам нужно придумать какой-то способ представления XML как объекта. То, как вы это сделаете, полностью зависит от вас; нет никаких волшебных правил. Следует иметь в виду, что не каждая топология XML легко преобразуется в объект Swift — и это пример такой топологии.
Если вы точно знаете, что четыре типа книг, которые вы перечислили в перечислении BookType, — единственные, которые когда-либо будут встречаться, то мне кажется, что book_type
Book наиболее удобно было бы набором BookType, поскольку порядок не меняется. t имеет значение и данный тип либо есть, либо его нет в списке (а если book_type
нет, то набор может быть просто пустым):
struct Book {
let title: String
let author: String
let book_type: Set<BookType>
}
enum BookType {
case fiction
case nonfiction
case reference
case autobiography
}
Вы заметите, что я не сделал ничего необязательного, поскольку предполагаю, что у каждой книги есть название и автор. Но это только предположение; вам предстоит спроектировать эти объекты на основе известных вам способов легальной структуризации XML.
Если вам не ясно, как перейти от исходного XML к заданному объекту Book, вот полный (но игрушечный) пример, в котором мы создаем ваш пример XML и анализируем его в viewDidLoad
контроллера представления:
import UIKit
class ViewController: UIViewController {
let parserDelegate = BookParserDelegate()
override func viewDidLoad() {
super.viewDidLoad()
let xml = """
<Book>
<title>Some Random Book</title>
<author>Someone Someone</author>
<book_type>
<FICT />
<REF />
</book_type>
</Book>
"""
let xmlData = xml.data(using: .utf8)!
let parser = XMLParser(data: xmlData)
parser.delegate = parserDelegate
parser.parse()
if let book = parserDelegate.theBook {
print(book)
}
}
}
class BookParserDelegate: NSObject, XMLParserDelegate {
struct Collector {
var title = ""
var author = ""
var book_types = Set<BookType>()
}
var collector = Collector()
var currentPath: WritableKeyPath<Collector, String>?
var doingBookTypes = false
var theBook: Book?
func parser(
_ parser: XMLParser,
didStartElement elementName: String,
namespaceURI: String?,
qualifiedName qName: String?,
attributes attributeDict: [String : String] = [:]
) {
if elementName == "title" {
currentPath = \.title
} else if elementName == "author" {
currentPath = \.author
} else if elementName == "book_type" {
doingBookTypes = true
} else if doingBookTypes {
if let type = BookType(rawValue: elementName) {
collector.book_types.insert(type)
}
}
}
func parser(
_ parser: XMLParser,
didEndElement elementName: String,
namespaceURI: String?,
qualifiedName qName: String?
) {
currentPath = nil
if elementName == "book_type" {
doingBookTypes = false
} else if elementName == "Book" {
theBook = Book(
title: collector.title,
author: collector.author,
book_type: collector.book_types
)
}
}
func parser(
_ parser: XMLParser,
foundCharacters string: String
) {
if let currentPath {
collector[keyPath: currentPath] = string
}
}
}
struct Book {
let title: String
let author: String
let book_type: Set<BookType>
}
enum BookType: String {
case fiction = "FICT"
case reference = "REF"
}
Результат:
Book(
title: "Some Random Book",
author: "Someone Someone",
book_type: Set([.reference, .fiction])
)
что я считаю правильным для данного XML. Обратите внимание: я не использовал все четыре случая вашего BookType, поскольку они не встречаются в примере XML, и вы не сказали мне, как они будут выглядеть, если они встречаются. В любом случае, это всего лишь игрушечный пример; очистка кода, его настройка и т. д. оставлены в качестве упражнения для читателя.
Найдите в Интернете парсеры XML; у любого из них есть документация, в которой указано, что делать.