import Foundation class BooruPostXMLParser: NSObject, XMLParserDelegate { private var posts: [BooruPost] = [] private var currentPost: BooruPost? private var parser: XMLParser private let provider: BooruProvider private var currentPostData: [String: String] = [:] private var currentElementName: String? private var currentText: String = "" init(data: Data, provider: BooruProvider) { parser = XMLParser(data: data) self.provider = provider super.init() parser.delegate = self } func parse() -> [BooruPost] { parser.parse() return posts } private func makePost(from dict: [String: String]) -> BooruPost? { guard let id = dict["id"], let heightStr = dict["height"], let height = Int(heightStr), let score = dict["score"], let fileUrlString = dict["file_url"], let fileUrl = URL(string: fileUrlString), let parentId = dict["parent_id"], let sampleUrlString = dict["sample_url"], let sampleUrl = URL(string: sampleUrlString), let sampleWidthString = dict["sample_width"], let sampleWidth = Int(sampleWidthString), let sampleHeightString = dict["sample_height"], let sampleHeight = Int(sampleHeightString), let previewUrlString = dict["preview_url"], let previewUrl = URL(string: previewUrlString), let rating = dict["rating"], let tagsString = dict["tags"], let widthString = dict["width"], let width = Int(widthString), let change = dict["change"], let md5 = dict["md5"], let creatorId = dict["creator_id"], let createdAtString = dict["created_at"], let createdAt = parseCreatedAt(createdAtString), let status = dict["status"], let source = dict["source"], let previewWidthString = dict["preview_width"], let previewWidth = Int(previewWidthString), let previewHeightString = dict["preview_height"], let previewHeight = Int(previewHeightString) else { return nil } return BooruPost( id: id, height: height, score: score, fileURL: fileUrl, parentID: parentId, sampleURL: sampleUrl, sampleWidth: sampleWidth, sampleHeight: sampleHeight, previewURL: previewUrl, rating: BooruRating(rating), tags: tagsString.components(separatedBy: CharacterSet.whitespacesAndNewlines) .filter { !$0.isEmpty }, width: width, change: change, md5: md5, creatorID: creatorId, authorID: dict["author_id"], createdAt: createdAt, status: status, source: source, previewWidth: previewWidth, previewHeight: previewHeight ) } func parser( _ parser: XMLParser, // swiftlint:disable:this unused_parameter didStartElement elementName: String, namespaceURI: String?, // swiftlint:disable:this unused_parameter qualifiedName qName: String?, // swiftlint:disable:this unused_parameter attributes attributeDict: [String: String] = [:] ) { if provider == .gelbooru { if elementName == "post" { currentPostData = [:] } else { currentElementName = elementName currentText = "" } } else { if elementName == "post" { if let post = makePost(from: attributeDict) { currentPost = post } } } } func parser( _ parser: XMLParser, // swiftlint:disable:this unused_parameter foundCharacters string: String ) { if provider == .gelbooru, currentElementName != nil { currentText += string } } func parser( _ parser: XMLParser, // swiftlint:disable:this unused_parameter didEndElement elementName: String, namespaceURI: String?, // swiftlint:disable:this unused_parameter qualifiedName qName: String? // swiftlint:disable:this unused_parameter ) { if provider == .gelbooru { if elementName == "post" { if let post = makePost(from: currentPostData) { posts.append(post) } } else if let currentElement = currentElementName { currentPostData[currentElement] = currentText.trimmingCharacters( in: .whitespacesAndNewlines ) currentElementName = nil currentText = "" } } else { if elementName == "post", let post = currentPost { posts.append(post) currentPost = nil } } } #if DEBUG func parser(_: XMLParser, parseErrorOccurred parseError: Error) { print("BooruPostXMLParser.parser: \(parseError)") } #endif func parseCreatedAt(_ input: String) -> Date? { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "EEE MMM dd HH:mm:ss Z yyyy" dateFormatter.locale = Locale(identifier: "en_US_POSIX") if let date = dateFormatter.date(from: input) { return date } if let timestamp = Double(input) { return Date(timeIntervalSince1970: timestamp) } return nil } }