Adjust csv parsing
This commit is contained in:
parent
3d092ab2c2
commit
86753bf3ed
|
@ -5,13 +5,21 @@
|
|||
|
||||
This version adjusts the library for Xcode 14 and deprecates some things.
|
||||
|
||||
This version contains a few breaking changes, that should be easy to fix.
|
||||
|
||||
### ✨ New features
|
||||
|
||||
* `DateFormatter+Init` has a new convenience initializer.
|
||||
* `CsvParser` can now parse CSV files at urls as well.
|
||||
|
||||
### 💡 Behavior changes
|
||||
|
||||
* `StandardCsvParser` now throws native errors for file parsing.
|
||||
|
||||
### 💥 Breaking changes
|
||||
|
||||
* Due to conflicting with TagKit, `String+Slugifies` has been removed.
|
||||
* `CsvParserError` has a new convenience initializer.
|
||||
* `String+Slugifies` has been removed due to conflicts with TagKit.
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// CsvParser.swift
|
||||
// SwiftKit
|
||||
//
|
||||
// Created by Daniel Saidi on 2018-10-23.
|
||||
// Copyright © 2020 Daniel Saidi. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
This protocol can be implemented by classes that can handle
|
||||
parsing of comma-separated value files and strings.
|
||||
|
||||
When parsing a csv file or string, every line will be split
|
||||
up into components using the provided `componentSeparator`.
|
||||
*/
|
||||
public protocol CsvParser {
|
||||
|
||||
/**
|
||||
Parse a csv file in a certain bundle.
|
||||
|
||||
- Parameters:
|
||||
- fileName: The name of the file to parse.
|
||||
- fileExtension: The extension of the file to parse.
|
||||
- bundle: The bundle in which the file is located.
|
||||
- componentSeparator: The separator that separates components on each line.
|
||||
*/
|
||||
func parseCsvFile(
|
||||
named fileName: String,
|
||||
withExtension fileExtension: String,
|
||||
in bundle: Bundle,
|
||||
componentSeparator: Character
|
||||
) throws -> [[String]]
|
||||
|
||||
/**
|
||||
Parse a csv file at a certain url.
|
||||
|
||||
- Parameters:
|
||||
- url: The url of the file to parse.
|
||||
- componentSeparator: The separator that separates components on each line.
|
||||
*/
|
||||
func parseCsvFile(
|
||||
at url: URL,
|
||||
componentSeparator: Character
|
||||
) throws -> [[String]]
|
||||
|
||||
/**
|
||||
Parse the provided csv string.
|
||||
|
||||
- Parameters:
|
||||
- string: The string to parse.
|
||||
- componentSeparator: The separator that separates components on each line.
|
||||
*/
|
||||
func parseCsvString(
|
||||
_ string: String,
|
||||
componentSeparator: Character
|
||||
) -> [[String]]
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// CsvParserError.swift
|
||||
// SwiftKit
|
||||
//
|
||||
// Created by Daniel Saidi on 2018-10-23.
|
||||
// Copyright © 2018 Daniel Saidi. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
This error can be thrown while parsing a csv string or file.
|
||||
*/
|
||||
public enum CsvParserError: Error {
|
||||
|
||||
/// The requested file doesn't exist.
|
||||
case noFileWithName(_ fileName: String, andExtension: String, inBundle: Bundle)
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// StandardCsvParser.swift
|
||||
// SwiftKit
|
||||
//
|
||||
// Created by Daniel Saidi on 2018-10-23.
|
||||
// Copyright © 2020 Daniel Saidi. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
This class can be used to parse comma-separated value files
|
||||
and strings.
|
||||
|
||||
When parsing a csv file or string, every line will be split
|
||||
up into components using the provided `componentSeparator`.
|
||||
*/
|
||||
public class StandardCsvParser: CsvParser {
|
||||
|
||||
/**
|
||||
Create a parser instance.
|
||||
*/
|
||||
public init(fileManager: FileManager = .default) {
|
||||
self.fileManager = fileManager
|
||||
}
|
||||
|
||||
private let fileManager: FileManager
|
||||
|
||||
/**
|
||||
Parse a csv file in a certain bundle.
|
||||
|
||||
- Parameters:
|
||||
- fileName: The name of the file to parse.
|
||||
- fileExtension: The extension of the file to parse.
|
||||
- bundle: The bundle in which the file is located.
|
||||
- componentSeparator: The separator that separates components on each line.
|
||||
*/
|
||||
public func parseCsvFile(
|
||||
named fileName: String,
|
||||
withExtension ext: String,
|
||||
in bundle: Bundle,
|
||||
componentSeparator: Character
|
||||
) throws -> [[String]] {
|
||||
guard let path = bundle.path(forResource: fileName, ofType: ext) else {
|
||||
throw CsvParserError.noFileWithName(fileName, andExtension: ext, inBundle: bundle)
|
||||
}
|
||||
let string = try String(contentsOfFile: path, encoding: .utf8)
|
||||
return parseCsvString(string, componentSeparator: componentSeparator)
|
||||
}
|
||||
|
||||
/**
|
||||
Parse a csv file at a certain url.
|
||||
|
||||
- Parameters:
|
||||
- url: The url of the file to parse.
|
||||
- componentSeparator: The separator that separates components on each line.
|
||||
*/
|
||||
public func parseCsvFile(
|
||||
at url: URL,
|
||||
componentSeparator: Character
|
||||
) throws -> [[String]] {
|
||||
let string = try String(contentsOf: url, encoding: .utf8)
|
||||
return parseCsvString(string, componentSeparator: componentSeparator)
|
||||
}
|
||||
|
||||
/**
|
||||
Parse the provided csv string.
|
||||
|
||||
- Parameters:
|
||||
- string: The string to parse.
|
||||
- componentSeparator: The separator that separates components on each line.
|
||||
*/
|
||||
public func parseCsvString(
|
||||
_ string: String,
|
||||
componentSeparator: Character
|
||||
) -> [[String]] {
|
||||
string
|
||||
.components(separatedBy: .newlines)
|
||||
.map { $0.trimmingCharacters(in: .whitespaces) }
|
||||
.filter { !$0.isEmpty }
|
||||
.map { $0.split(separator: componentSeparator)
|
||||
.map { String($0).trimmingCharacters(in: .whitespaces) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
//
|
||||
// CsvParser.swift
|
||||
// SwiftKit
|
||||
//
|
||||
// Created by Daniel Saidi on 2018-10-23.
|
||||
// Copyright © 2020 Daniel Saidi. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
This protocol can be implemented by classes that can handle
|
||||
parsing of comma-separated value files and strings.
|
||||
*/
|
||||
public protocol CsvParser {
|
||||
|
||||
/**
|
||||
Parse a CVS file with a certain name and extension in a
|
||||
certain bundle.
|
||||
|
||||
The file content will be parsed line by line, splitting
|
||||
each line using the provided `separator`.
|
||||
*/
|
||||
func parseCvsFile(named fileName: String, withExtension ext: String, in bundle: Bundle, separator: Character) throws -> [[String]]
|
||||
|
||||
/**
|
||||
Parse a CVS string line by line, splitting up each line
|
||||
using the provided `separator`.
|
||||
*/
|
||||
func parseCvsString(_ string: String, separator: Character) -> [[String]]
|
||||
}
|
||||
|
||||
public enum CsvParserError: Error {
|
||||
|
||||
case invalidDataInFile(_ fileName: String, fileExtension: String)
|
||||
case noSuchFile(_ fileName: String, fileExtension: String)
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
//
|
||||
// StandardCsvParser.swift
|
||||
// SwiftKit
|
||||
//
|
||||
// Created by Daniel Saidi on 2018-10-23.
|
||||
// Copyright © 2020 Daniel Saidi. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class StandardCsvParser: CsvParser {
|
||||
|
||||
public init() {}
|
||||
|
||||
/**
|
||||
Parse a CVS file with a certain name and extension in a
|
||||
certain bundle.
|
||||
|
||||
The file content will be parsed line by line, splitting
|
||||
each line using the provided `separator`.
|
||||
*/
|
||||
public func parseCvsFile(
|
||||
named fileName: String,
|
||||
withExtension ext: String,
|
||||
in bundle: Bundle,
|
||||
separator: Character) throws -> [[String]] {
|
||||
let missingFile = CsvParserError.noSuchFile(fileName, fileExtension: ext)
|
||||
let invalidData = CsvParserError.invalidDataInFile(fileName, fileExtension: ext)
|
||||
guard let path = bundle.path(forResource: fileName, ofType: ext) else { throw missingFile }
|
||||
guard let string = try? String(contentsOfFile: path, encoding: .utf8) else { throw invalidData }
|
||||
return parseCvsString(string, separator: separator)
|
||||
}
|
||||
|
||||
/**
|
||||
Parse a CVS string line by line, splitting up each line
|
||||
using the provided `separator`.
|
||||
*/
|
||||
public func parseCvsString(
|
||||
_ string: String,
|
||||
separator: Character) -> [[String]] {
|
||||
let allRows = string.components(separatedBy: .newlines)
|
||||
let rows = allRows.filter { $0.trimmingCharacters(in: .whitespaces).count > 0 }
|
||||
return rows.map { $0.split(separator: separator).map { String($0) } }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// StandardCsvParserTests.swift
|
||||
// SwiftKitTests
|
||||
//
|
||||
// Created by Daniel Saidi on 2018-10-23.
|
||||
// Copyright © 2020 Daniel Saidi. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftKit
|
||||
import XCTest
|
||||
|
||||
class StandardCsvParserTests: XCTestCase {
|
||||
|
||||
let parser = StandardCsvParser()
|
||||
|
||||
func testCanParseSemicolonSeparatedString() {
|
||||
let result = parser.parseCsvString("foo;bar;baz\nenough", componentSeparator: ";")
|
||||
XCTAssertEqual(result.count, 2)
|
||||
XCTAssertEqual(result[0], ["foo", "bar", "baz"])
|
||||
XCTAssertEqual(result[1], ["enough"])
|
||||
}
|
||||
|
||||
func testCanParseCommaSeparatedString() {
|
||||
let result = parser.parseCsvString("a,b,c", componentSeparator: ",")
|
||||
XCTAssertEqual(result.count, 1)
|
||||
XCTAssertEqual(result[0], ["a", "b", "c"])
|
||||
}
|
||||
|
||||
func testTrimsComponents() {
|
||||
let result = parser.parseCsvString(" a , b , c ", componentSeparator: ",")
|
||||
XCTAssertEqual(result.count, 1)
|
||||
XCTAssertEqual(result[0], ["a", "b", "c"])
|
||||
}
|
||||
|
||||
func testIncludesEmptyComponents() {
|
||||
let result = parser.parseCsvString(" a , , c ", componentSeparator: ",")
|
||||
XCTAssertEqual(result.count, 1)
|
||||
XCTAssertEqual(result[0], ["a", "", "c"])
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
//
|
||||
// StandardCsvParserTests.swift
|
||||
// SwiftKitTests
|
||||
//
|
||||
// Created by Daniel Saidi on 2018-10-23.
|
||||
// Copyright © 2020 Daniel Saidi. All rights reserved.
|
||||
//
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
import SwiftKit
|
||||
|
||||
class StandardCsvParserTests: QuickSpec {
|
||||
|
||||
override func spec() {
|
||||
|
||||
var parser: CsvParser!
|
||||
|
||||
beforeEach {
|
||||
parser = StandardCsvParser()
|
||||
}
|
||||
|
||||
describe("parsing csv strings") {
|
||||
|
||||
it("can parse semicolon-separated string") {
|
||||
let result = parser.parseCvsString("foo;bar;baz\nenough", separator: ";")
|
||||
expect(result.count).to(equal(2))
|
||||
expect(result[0]).to(equal(["foo", "bar", "baz"]))
|
||||
expect(result[1]).to(equal(["enough"]))
|
||||
}
|
||||
|
||||
it("can parse comma-separated string") {
|
||||
let result = parser.parseCvsString("a,b,c", separator: ",")
|
||||
expect(result.count).to(equal(1))
|
||||
expect(result[0]).to(equal(["a", "b", "c"]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue