Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/iCalendarParser/Constant/Constant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ extension Constant {
static let cname = "CN"

static let attachment: String = "ATTACH"
static let categories: String = "CATEGORIES"
static let created: String = "CREATED"
static let classification: String = "CLASS"
static let description: String = "DESCRIPTION"
Expand Down
4 changes: 3 additions & 1 deletion Sources/iCalendarParser/Models/ICEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public struct ICEvent: ICComponentable {
// https://www.rfc-editor.org/rfc/rfc5545#section-3.8.1.1))
// var attachment: [ICAttachment]?

// var categories: [String]?
var categories: [String]?

/// Defines the access classification for a calendar component.
///
Expand Down Expand Up @@ -195,6 +195,7 @@ public struct ICEvent: ICComponentable {

public init(
attendees: [ICAttendee]? = nil,
categories: [String]? = nil,
classification: String? = nil,
description: String? = nil,
dtCreated: Date? = nil,
Expand All @@ -215,6 +216,7 @@ public struct ICEvent: ICComponentable {
url: URL? = nil
) {
self.attendees = attendees
self.categories = categories
self.classification = classification
self.description = description
self.dtCreated = dtCreated
Expand Down
22 changes: 15 additions & 7 deletions Sources/iCalendarParser/Parser/ICParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,29 @@ public struct ICParser {
)
}

func getProperties(
from ics: String
) -> [ICProperty] {
return ics
.replacingOccurrences(of: "\r\n ", with: "")
.components(separatedBy: "\r\n")
func getProperties(from ics: String) -> [ICProperty] {
// Normalize line endings to \n
let normalizedICS = ics
.replacingOccurrences(of: "\r\n", with: "\n")
.replacingOccurrences(of: "\r", with: "\n")
// Replace line folding sequences
let unfoldedICS = normalizedICS.replacingOccurrences(of: "\n ", with: "")

// Split by \n
let properties: [ICProperty] = unfoldedICS
.components(separatedBy: "\n")
.map { $0.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: true) }
.filter { $0.count > 1 }
.map { (String($0[0]), String($0[1])) }

return properties
}

private func getProperty(
name: String,
from elements: [ICProperty]
) -> ICProperty? {
return elements
elements
.filter { $0.name.hasPrefix(name) }
.first
}
Expand Down Expand Up @@ -144,6 +151,7 @@ public struct ICParser {
var event = ICEvent()

event.attendees = component.buildAttendees(of: Constant.Property.attendee)
event.categories = component.buildProperty(of: Constant.Property.categories)?.components(separatedBy: ",")
event.classification = component.buildProperty(of: Constant.Property.classification)
event.description = component.buildProperty(of: Constant.Property.description)
event.dtCreated = component.buildProperty(of: Constant.Property.created)?.date
Expand Down
36 changes: 36 additions & 0 deletions Tests/iCalendarParserTests/Extensions/XCTestCase+Extension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// XCTestCase+Extension.swift
//
//
// Created by Adrian Bolinger on 7/28/24.
//

import XCTest

extension XCTestCase {
/// Reads the contents of a file located in the test directory's "Resources" folder.
///
/// - Parameters:
/// - filename: The name of the file (without extension).
/// - ext: The extension of the file.
/// - Returns: The contents of the file as a String.
/// - Throws: An error if the file cannot be read.
func getContents(of filename: String, ext: String, file: StaticString = #filePath) throws -> String {
// Get the directory of the current file
let currentFilePath = String(describing: file)
let currentDirectoryPath = URL(fileURLWithPath: currentFilePath).deletingLastPathComponent()

// Construct the full file path
let fileURL = currentDirectoryPath.appendingPathComponent("Resources/\(filename).\(ext)")

// Ensure the file exists
guard FileManager.default.fileExists(atPath: fileURL.path) else {
throw NSError(domain: "FileNotFoundError", code: 1, userInfo: [NSLocalizedDescriptionKey: "File \(filename).\(ext) not found at path \(fileURL.path)."])
}

// Read the contents of the file
let fileContents = try String(contentsOf: fileURL, encoding: .utf8)

return fileContents
}
}
50 changes: 50 additions & 0 deletions Tests/iCalendarParserTests/ICParserTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// ICParserTests.swift
//
//
// Created by Adrian Bolinger on 7/28/24.
//

import XCTest

@testable import iCalendarParser

final class ICParserTests: XCTestCase {

var sut: ICParser!

override func setUpWithError() throws {
try super.setUpWithError()
sut = ICParser()
}

override func tearDownWithError() throws {
sut = nil
try super.tearDownWithError()
}

func testICParser() throws {
let iCalString = try getContents(of: "uf-full-calendar", ext: "txt")
let calendar = sut.calendar(from: iCalString)
XCTAssertEqual(calendar?.events.count, 295)
}

func testICParserCategories() throws {
let iCalString = try getContents(of: "ufl-all-events", ext: "txt")
guard let calendar = sut.calendar(from: iCalString) else {
XCTFail("could not create calendar")
return
}

let uniqueCategories = calendar.events
.compactMap { $0.categories }
.flatMap { $0 }
.reduce(into: [String]()) { result, category in
if !result.contains(category) {
result.append(category)
}
}

XCTAssertEqual(uniqueCategories.count, 19)
}
}
Loading