Skip to content

Add NSURL init method #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Aug 16, 2016
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ xcuserdata
*.moved-aside
*.xcuserstate
*.xcscmblueprint
.DS_Store

## Obj-C/Swift specific
*.hmap
Expand Down
55 changes: 50 additions & 5 deletions Sources/Code/CSVImporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,24 @@ import Foundation
import FileKit
import HandySwift

/// An enum to represent the possible line endings of CSV files.
public enum LineEnding : String {
case NL = "\n"
case CR = "\r"
case CRLF = "\r\n"
case Unknown = ""
}

private let chunkSize = 4096

/// Importer for CSV files that maps your lines to a specified data structure.
public class CSVImporter<T> {

// MARK: - Stored Instance Properties

let csvFile: TextFile
let delimiter: String
var lineEnding: LineEnding

var lastProgressReport: NSDate?

Expand All @@ -25,7 +36,7 @@ public class CSVImporter<T> {
var failClosure: (() -> Void)?


// MARK: - Computes Instance Properties
// MARK: - Computed Instance Properties

var shouldReportProgress: Bool {
get {
Expand All @@ -42,11 +53,23 @@ public class CSVImporter<T> {
/// - Parameters:
/// - path: The path to the CSV file to import.
/// - delimiter: The delimiter used within the CSV file for separating fields. Defaults to ",".
public init(path: String, delimiter: String = ",") {
/// - lineEnding: The lineEnding of the file. If not specified will be determined automatically.
public init(path: String, delimiter: String = ",", lineEnding: LineEnding = .Unknown) {
self.csvFile = TextFile(path: Path(path))
self.delimiter = delimiter
self.lineEnding = lineEnding
}

/// Creates a `CSVImporter` object with required configuration options.
///
/// - Parameters:
/// - url: File URL for the CSV file to import.
/// - delimiter: The delimiter used within the CSV file for separating fields. Defaults to ",".
public convenience init?(url: NSURL, delimiter: String = ",", lineEnding: LineEnding = .Unknown) {
guard url.fileURL else { return nil }
guard url.path != nil else { return nil }
self.init(path: url.path!, delimiter: delimiter, lineEnding: lineEnding)
}

// MARK: - Instance Methods

Expand Down Expand Up @@ -120,7 +143,10 @@ public class CSVImporter<T> {
/// - valuesInLine: The values found within a line.
/// - Returns: `true` on finish or `false` if can't read file.
func importLines(closure: (valuesInLine: [String]) -> Void) -> Bool {
if let csvStreamReader = self.csvFile.streamReader() {
if lineEnding == .Unknown {
lineEnding = lineEndingForFile()
}
if let csvStreamReader = self.csvFile.streamReader(lineEnding.rawValue) {
for line in csvStreamReader {
let valuesInLine = readValuesInLine(line)
closure(valuesInLine: valuesInLine)
Expand All @@ -132,19 +158,38 @@ public class CSVImporter<T> {
}
}

/// Determines the line ending for the CSV file
///
/// - Returns: the lineEnding for the CSV file or default of NL.
private func lineEndingForFile() -> LineEnding {
var lineEnding: LineEnding = .NL
if let fileHandle = self.csvFile.handleForReading {
let data = fileHandle.readDataOfLength(chunkSize).mutableCopy()
if let contents = NSString(bytesNoCopy: data.mutableBytes, length: data.length, encoding: NSUTF8StringEncoding, freeWhenDone: false) {
if contents.containsString(LineEnding.CRLF.rawValue) {
lineEnding = .CRLF
} else if contents.containsString(LineEnding.NL.rawValue) {
lineEnding = .NL
} else if contents.containsString(LineEnding.CR.rawValue) {
lineEnding = .CR
}
}
}
return lineEnding
}

/// Reads the line and returns the fields found. Handles double quotes according to RFC 4180.
///
/// - Parameters:
/// - line: The line to read values from.
/// - Returns: An array of values found in line.
func readValuesInLine(line: String) -> [String] {
var correctedLine = line.stringByReplacingOccurrencesOfString("\(delimiter)\"\"\(delimiter)", withString: delimiter+delimiter)
correctedLine = correctedLine.stringByReplacingOccurrencesOfString("\r\n", withString: "\n")

if correctedLine.hasPrefix("\"\"\(delimiter)") {
correctedLine = correctedLine.substringFromIndex(correctedLine.startIndex.advancedBy(2))
}
if correctedLine.hasSuffix("\(delimiter)\"\"") || correctedLine.hasSuffix("\(delimiter)\"\"\n") {
if correctedLine.hasSuffix("\(delimiter)\"\"") {
correctedLine = correctedLine.substringToIndex(correctedLine.startIndex.advancedBy(correctedLine.utf16.count - 2))
}

Expand Down
Loading