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 reset(with data: Data) { posts.removeAll() currentPost = nil currentPostData.removeAll() currentElementName = nil currentText = "" parser = XMLParser(data: data) parser.delegate = self } func parse() -> [BooruPost] { parser.parse() return posts } private func makePost(from dict: [String: String]) -> BooruPost? { guard let id = dict["id"], let score = dict["score"], let parentId = dict["parent_id"], let rating = dict["rating"], let tagsString = dict["tags"], let change = dict["change"], let md5 = dict["md5"], let creatorId = dict["creator_id"], let createdAtString = dict["created_at"], let status = dict["status"], let source = dict["source"] else { return nil } guard let height = Int(dict["height"] ?? ""), let sampleWidth = Int(dict["sample_width"] ?? ""), let sampleHeight = Int(dict["sample_height"] ?? ""), let width = Int(dict["width"] ?? ""), let previewWidth = Int(dict["preview_width"] ?? ""), let previewHeight = Int(dict["preview_height"] ?? "") else { return nil } guard let fileUrl = URL(string: dict["file_url"] ?? ""), let sampleUrl = URL(string: dict["sample_url"] ?? ""), let previewUrl = URL(string: dict["preview_url"] ?? ""), let createdAt = parseCreatedAt(createdAtString) else { return nil } let tags = tagsString.components(separatedBy: .whitespacesAndNewlines) .filter { !$0.isEmpty } return BooruPost( id: id, height: height, score: score, fileURL: fileUrl, parentID: parentId, sampleURL: sampleUrl, sampleWidth: sampleWidth, sampleHeight: sampleHeight, previewURL: previewUrl, rating: BooruRating(rating), tags: tags, 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 private static let dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "EEE MMM dd HH:mm:ss Z yyyy" formatter.locale = Locale(identifier: "en_US_POSIX") return formatter }() func parseCreatedAt(_ input: String) -> Date? { if let date = Self.dateFormatter.date(from: input) { return date } if let timestamp = Double(input) { return Date(timeIntervalSince1970: timestamp) } return nil } }