diff --git a/Sources/PentabarfKit/DataParser.swift b/Sources/PentabarfKit/DataParser.swift index 4406dca..176ea77 100644 --- a/Sources/PentabarfKit/DataParser.swift +++ b/Sources/PentabarfKit/DataParser.swift @@ -15,13 +15,9 @@ struct DataParser { data.element?.childElements.first?.childElements.forEach { element in switch element.name { - case Conference.elementname: + case "conference": conference = Conference.from(element) - case Track.elementname: - element.childElements.forEach { trackElement in - conference?.tracks.append(Track.from(trackElement)) - } - case ConferenceDay.elementname: + case "day": conference?.days.append(ConferenceDay.from(element)) default: break diff --git a/Sources/PentabarfKit/ImportExtensions.swift b/Sources/PentabarfKit/ImportExtensions.swift index 8f21abf..56b1bc3 100644 --- a/Sources/PentabarfKit/ImportExtensions.swift +++ b/Sources/PentabarfKit/ImportExtensions.swift @@ -43,7 +43,7 @@ struct ImportHelper { return nil } - return intervalTime.distance(to: beginTime!) + return beginTime!.distance(to: intervalTime) } } @@ -81,7 +81,7 @@ extension Track { metadata[attribute.key] = attribute.value } - return Self.init(name: element.text!, metadata: metadata) + return Self.init(name: element.text!) } } @@ -103,16 +103,24 @@ extension Room { static func from(_ element: XML.Element, date: Date) -> Self { var events: [Event] = [] element.childElements.forEach { eventElement in - events.append(Event.from(eventElement, date: date)) + if let event = try? Event.from(eventElement, date: date) { + events.append(event) + } } return Self.init(name: element.attributes["name"]!, events: events) } } +enum EventParsingError: Error { + case missingElement(String) +} + extension Event { private static let errorMessage: String = "ERROR" - static func from(_ element: XML.Element, date: Date) -> Self { + private static var tracks: [String:Track] = [:] + + static func from(_ element: XML.Element, date: Date) throws -> Self { let language = ImportHelper.filterElementByName(element, name: "language") ?? "en" let locale = Locale(languageComponents: .init(identifier: language)) var authors: [Person] = [] @@ -130,6 +138,16 @@ extension Event { element.childElements.filter { $0.name == "attachments" }.first?.childElements.forEach { attachmentElement in attachments.append(Attachment.from(attachmentElement)) } + + guard let trackString = ImportHelper.filterElementByName(element, name: "track") else { + throw EventParsingError.missingElement("No Track specified!") + } + + if tracks.keys.contains(trackString) == false { + let track = Track(name: trackString) + tracks[trackString] = track + } + return Self.init(id: Int(element.attributes["id"]!)!, start: ImportHelper.timeFromElementByName(element, name: "start", date: date)!, duration: ImportHelper.intervalFromElementByName(element, name: "duration")!, @@ -137,11 +155,11 @@ extension Event { slug: ImportHelper.filterElementByName(element, name: "slug") ?? Self.errorMessage, title: ImportHelper.filterElementByName(element, name: "title") ?? Self.errorMessage, subtitle: ImportHelper.filterElementByName(element, name: "subtitle"), - track: ImportHelper.filterElementByName(element, name: "track") ?? Self.errorMessage, type: ImportHelper.filterElementByName(element, name: "type") ?? Self.errorMessage, language: locale, abstract: ImportHelper.filterElementByName(element, name: "abstract") ?? Self.errorMessage, description: ImportHelper.filterElementByName(element, name: "description") ?? "", + track: tracks[trackString]!, authors: authors, attachments: attachments, links: links) diff --git a/Sources/PentabarfKit/Models/Conference.swift b/Sources/PentabarfKit/Models/Conference.swift index 703d7ca..02fa014 100644 --- a/Sources/PentabarfKit/Models/Conference.swift +++ b/Sources/PentabarfKit/Models/Conference.swift @@ -8,8 +8,6 @@ import Foundation public struct Conference { - static let elementname: String = "conference" - public let title: String public let subtitle: String? @@ -22,6 +20,31 @@ public struct Conference { public let dayChange: String public let timeslotDuration: TimeInterval - public var tracks: [Track] = [] public var days: [ConferenceDay] = [] + + public lazy var events: [Event] = { + days.flatMap { dayL in + var day = dayL + return day.events + } + }() + + public lazy var rooms: [Room] = { + Array(Set(days.flatMap { dayL in + var day = dayL + return day.rooms + })) + }() + + public lazy var tracks: [Track] = { + Array(Set(events.map { event -> Track in + return event.track + })) + }() + + public lazy var authors: [Person] = { + Array(Set(events.flatMap { event -> [Person] in + return event.authors + })) + }() } diff --git a/Sources/PentabarfKit/Models/ConferenceDay.swift b/Sources/PentabarfKit/Models/ConferenceDay.swift index d15df0d..b38d531 100644 --- a/Sources/PentabarfKit/Models/ConferenceDay.swift +++ b/Sources/PentabarfKit/Models/ConferenceDay.swift @@ -8,10 +8,13 @@ import Foundation public struct ConferenceDay { - static let elementname: String = "day" - public let index: Int public let date: Date public let rooms: [Room] + public lazy var events: [Event] = { + return rooms.flatMap { room -> [Event] in + return room.events + } + }() } diff --git a/Sources/PentabarfKit/Models/Event.swift b/Sources/PentabarfKit/Models/Event.swift index c2b651a..f814566 100644 --- a/Sources/PentabarfKit/Models/Event.swift +++ b/Sources/PentabarfKit/Models/Event.swift @@ -15,11 +15,11 @@ public struct Event { public let slug: String public let title: String public let subtitle: String? - public let track: String public let type: String public let language: Locale public let abstract: String public let description: String + public let track: Track public let authors: [Person] public let attachments: [Attachment] public let links: [Link] diff --git a/Sources/PentabarfKit/Models/Person.swift b/Sources/PentabarfKit/Models/Person.swift index 62cac08..7249c98 100644 --- a/Sources/PentabarfKit/Models/Person.swift +++ b/Sources/PentabarfKit/Models/Person.swift @@ -11,3 +11,14 @@ public struct Person { public let id: Int public let name: String } + +extension Person: Hashable { + public static func == (lhs: Person, rhs: Person) -> Bool { + return lhs.id == rhs.id + } + + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + } +} diff --git a/Sources/PentabarfKit/Models/Room.swift b/Sources/PentabarfKit/Models/Room.swift index fc3f55b..f58fe27 100644 --- a/Sources/PentabarfKit/Models/Room.swift +++ b/Sources/PentabarfKit/Models/Room.swift @@ -11,3 +11,14 @@ public struct Room { public let name: String public let events: [Event] } + +extension Room: Hashable { + public static func == (lhs: Room, rhs: Room) -> Bool { + return lhs.name == rhs.name + } + + + public func hash(into hasher: inout Hasher) { + hasher.combine(name) + } +} diff --git a/Sources/PentabarfKit/Models/Track.swift b/Sources/PentabarfKit/Models/Track.swift index 9cfe126..af99170 100644 --- a/Sources/PentabarfKit/Models/Track.swift +++ b/Sources/PentabarfKit/Models/Track.swift @@ -8,8 +8,17 @@ import Foundation public struct Track { - static let elementname: String = "tracks" - public let name: String - public let metadata: [String: Any] + public let metadata: [String: Any] = [:] +} + +extension Track: Hashable { + public static func == (lhs: Track, rhs: Track) -> Bool { + return lhs.name == rhs.name + } + + + public func hash(into hasher: inout Hasher) { + hasher.combine(name) + } } diff --git a/Tests/PentabarfKitTests/PentabarfKitTests.swift b/Tests/PentabarfKitTests/PentabarfKitTests.swift index 782ba7e..92135e6 100644 --- a/Tests/PentabarfKitTests/PentabarfKitTests.swift +++ b/Tests/PentabarfKitTests/PentabarfKitTests.swift @@ -4,25 +4,76 @@ import XCTest final class PentabarfKitTests: XCTestCase { func test2019() async throws { let path = Bundle.module.path(forResource: "schedule_2019", ofType: "xml") - let event = try await PentabarfKit.loadConference(URL(fileURLWithPath: path!)) + var event = try await PentabarfLoader.fetchConference(URL(fileURLWithPath: path!)) XCTAssertNotNil(event) XCTAssertEqual(event?.city, "Brussels") + XCTAssertEqual(event?.days.count, 2) + XCTAssertEqual(event?.tracks.count, 62) + XCTAssertEqual(event?.events.count, 775) + XCTAssertEqual(event?.rooms.count, 34) + XCTAssertEqual(event?.authors.count, 730) + } + + func test2019Details() async throws { + let path = Bundle.module.path(forResource: "schedule_2019", ofType: "xml") + var conference = try await PentabarfLoader.fetchConference(URL(fileURLWithPath: path!)) + + XCTAssertNotNil(conference) + + let event = conference?.events.first { !$0.links.isEmpty } + XCTAssertNotNil(event) + + XCTAssertFalse(event!.abstract.isEmpty) + XCTAssertNotNil(event?.language) + XCTAssertFalse(event!.duration <= 0) + + let author = event?.authors.first + XCTAssertNotNil(author?.name) + + let link = event?.links.first + XCTAssertEqual(link?.title, "Video recording (WebM/VP9)") + XCTAssertEqual(link?.url.absoluteString, "https://video.fosdem.org/2019/Janson/keynotes_welcome.webm") } func test2022Virtual() async throws { let path = Bundle.module.path(forResource: "schedule_2022", ofType: "xml") - let event = try await PentabarfKit.loadConference(URL(fileURLWithPath: path!)) + var event = try await PentabarfLoader.fetchConference(URL(fileURLWithPath: path!)) XCTAssertNotNil(event) XCTAssertEqual(event?.city, "Brussels") + XCTAssertEqual(event?.days.count, 2) + XCTAssertEqual(event?.tracks.count, 100) + XCTAssertEqual(event?.events.count, 730) + XCTAssertEqual(event?.rooms.count, 102) + XCTAssertEqual(event?.authors.count, 655) + } + + func test2022Details() async throws { + let path = Bundle.module.path(forResource: "schedule_2022", ofType: "xml") + var conference = try await PentabarfLoader.fetchConference(URL(fileURLWithPath: path!)) + + XCTAssertNotNil(conference) + + let event = conference?.events.first { !$0.attachments.isEmpty } + XCTAssertNotNil(event) + + let attachment = event!.attachments.first + XCTAssertEqual(attachment?.title, "Making a community-managed FOSS project sustainable in the medium- to long-term") + XCTAssertEqual(attachment?.url.absoluteString, "https://fosdem.org/2022/schedule/event/community_sustainable/attachments/audio/5230/export/events/attachments/community_sustainable/audio/5230/makingacommunitymanagedfossprojectsustainable.pdf") + XCTAssertEqual(attachment?.type, "audio") } func test2023() async throws { let path = Bundle.module.path(forResource: "schedule_2023", ofType: "xml") - let event = try await PentabarfKit.loadConference(URL(fileURLWithPath: path!)) + var event = try await PentabarfLoader.fetchConference(URL(fileURLWithPath: path!)) XCTAssertNotNil(event) XCTAssertEqual(event?.city, "Brussels") + XCTAssertEqual(event?.days.count, 2) + XCTAssertEqual(event?.tracks.count, 0) + XCTAssertEqual(event?.events.count, 0) + XCTAssertEqual(event?.rooms.count, 25) + XCTAssertEqual(event?.authors.count, 0) } }