7
7
//
8
8
9
9
import Foundation
10
- import FileKit
11
10
import HandySwift
12
11
13
12
/// An enum to represent the possible line endings of CSV files.
14
13
public enum LineEnding : String {
15
- case NL = " \n "
16
- case CR = " \r "
17
- case CRLF = " \r \n "
18
- case Unknown = " "
14
+ case nl = " \n "
15
+ case cr = " \r "
16
+ case crlf = " \r \n "
17
+ case unknown = " "
19
18
}
20
19
21
20
private let chunkSize = 4096
22
21
23
22
/// Importer for CSV files that maps your lines to a specified data structure.
24
- open class CSVImporter < T> {
25
-
23
+ public class CSVImporter < T> {
26
24
// MARK: - Stored Instance Properties
27
25
28
26
let csvFile : TextFile
29
27
let delimiter : String
30
28
var lineEnding : LineEnding
29
+ let encoding : String . Encoding
31
30
32
31
var lastProgressReport : Date ?
33
32
@@ -39,10 +38,7 @@ open class CSVImporter<T> {
39
38
// MARK: - Computed Instance Properties
40
39
41
40
var shouldReportProgress : Bool {
42
- get {
43
- return self . progressClosure != nil &&
44
- ( self . lastProgressReport == nil || Date ( ) . timeIntervalSince ( self . lastProgressReport!) > 0.1 )
45
- }
41
+ return self . progressClosure != nil && ( self . lastProgressReport == nil || Date ( ) . timeIntervalSince ( self . lastProgressReport!) > 0.1 )
46
42
}
47
43
48
44
@@ -54,10 +50,11 @@ open class CSVImporter<T> {
54
50
/// - path: The path to the CSV file to import.
55
51
/// - delimiter: The delimiter used within the CSV file for separating fields. Defaults to ",".
56
52
/// - lineEnding: The lineEnding of the file. If not specified will be determined automatically.
57
- public init ( path: String , delimiter: String = " , " , lineEnding: LineEnding = . Unknown ) {
58
- self . csvFile = TextFile ( path: Path ( path) )
53
+ public init ( path: String , delimiter: String = " , " , lineEnding: LineEnding = . unknown , encoding : String . Encoding = . utf8 ) {
54
+ self . csvFile = TextFile ( path: path, encoding : encoding )
59
55
self . delimiter = delimiter
60
56
self . lineEnding = lineEnding
57
+ self . encoding = encoding
61
58
62
59
delimiterQuoteDelimiter = " \( delimiter) \" \" \( delimiter) "
63
60
delimiterDelimiter = delimiter+ delimiter
@@ -70,9 +67,9 @@ open class CSVImporter<T> {
70
67
/// - Parameters:
71
68
/// - url: File URL for the CSV file to import.
72
69
/// - delimiter: The delimiter used within the CSV file for separating fields. Defaults to ",".
73
- public convenience init ? ( url: URL , delimiter: String = " , " , lineEnding: LineEnding = . Unknown ) {
70
+ public convenience init ? ( url: URL , delimiter: String = " , " , lineEnding: LineEnding = . unknown , encoding : String . Encoding = . utf8 ) {
74
71
guard url. isFileURL else { return nil }
75
- self . init ( path: url. path, delimiter: delimiter, lineEnding: lineEnding)
72
+ self . init ( path: url. path, delimiter: delimiter, lineEnding: lineEnding, encoding : encoding )
76
73
}
77
74
78
75
// MARK: - Instance Methods
@@ -82,7 +79,7 @@ open class CSVImporter<T> {
82
79
/// - Parameters:
83
80
/// - mapper: A closure to map the data received in a line to your data structure.
84
81
/// - Returns: `self` to enable consecutive method calls (e.g. `importer.startImportingRecords {...}.onProgress {...}`).
85
- open func startImportingRecords( mapper closure: @escaping ( _ recordValues: [ String ] ) -> T ) -> Self {
82
+ public func startImportingRecords( mapper closure: @escaping ( _ recordValues: [ String ] ) -> T ) -> Self {
86
83
DispatchQueue . global ( qos: DispatchQoS . QoSClass. userInitiated) . async {
87
84
var importedRecords : [ T ] = [ ]
88
85
@@ -109,7 +106,8 @@ open class CSVImporter<T> {
109
106
/// - structure: A closure for doing something with the found structure within the first line of the CSV file.
110
107
/// - recordMapper: A closure to map the dictionary data interpreted from a line to your data structure.
111
108
/// - Returns: `self` to enable consecutive method calls (e.g. `importer.startImportingRecords {...}.onProgress {...}`).
112
- open func startImportingRecords( structure structureClosure: @escaping ( _ headerValues: [ String ] ) -> Void , recordMapper closure: @escaping ( _ recordValues: [ String : String ] ) -> T ) -> Self {
109
+ public func startImportingRecords( structure structureClosure: @escaping ( _ headerValues: [ String ] ) -> Void ,
110
+ recordMapper closure: @escaping ( _ recordValues: [ String : String ] ) -> T ) -> Self {
113
111
DispatchQueue . global ( qos: DispatchQoS . QoSClass. userInitiated) . async {
114
112
var recordStructure : [ String ] ?
115
113
var importedRecords : [ T ] = [ ]
@@ -147,37 +145,35 @@ open class CSVImporter<T> {
147
145
/// - valuesInLine: The values found within a line.
148
146
/// - Returns: `true` on finish or `false` if can't read file.
149
147
func importLines( _ closure: ( _ valuesInLine: [ String ] ) -> Void ) -> Bool {
150
- if lineEnding == . Unknown {
148
+ if lineEnding == . unknown {
151
149
lineEnding = lineEndingForFile ( )
152
150
}
153
- if let csvStreamReader = self . csvFile. streamReader ( lineEnding. rawValue) {
154
- for line in csvStreamReader {
155
- autoreleasepool {
156
- let valuesInLine = readValuesInLine ( line)
157
- closure ( valuesInLine)
158
- }
159
- }
151
+ guard let csvStreamReader = self . csvFile. streamReader ( lineEnding: lineEnding, chunkSize: chunkSize) else { return false }
160
152
161
- return true
162
- } else {
163
- return false
153
+ for line in csvStreamReader {
154
+ autoreleasepool {
155
+ let valuesInLine = readValuesInLine ( line)
156
+ closure ( valuesInLine)
157
+ }
164
158
}
159
+
160
+ return true
165
161
}
166
162
167
163
/// Determines the line ending for the CSV file
168
164
///
169
165
/// - Returns: the lineEnding for the CSV file or default of NL.
170
166
fileprivate func lineEndingForFile( ) -> LineEnding {
171
- var lineEnding : LineEnding = . NL
167
+ var lineEnding : LineEnding = . nl
172
168
if let fileHandle = self . csvFile. handleForReading {
173
169
if let data = ( fileHandle. readData ( ofLength: chunkSize) as NSData ) . mutableCopy ( ) as? NSMutableData {
174
- if let contents = NSString ( bytesNoCopy: data. mutableBytes, length: data. length, encoding: String . Encoding . utf8 . rawValue, freeWhenDone: false ) {
175
- if contents. contains ( LineEnding . CRLF . rawValue) {
176
- lineEnding = . CRLF
177
- } else if contents. contains ( LineEnding . NL . rawValue) {
178
- lineEnding = . NL
179
- } else if contents. contains ( LineEnding . CR . rawValue) {
180
- lineEnding = . CR
170
+ if let contents = NSString ( bytesNoCopy: data. mutableBytes, length: data. length, encoding: encoding . rawValue, freeWhenDone: false ) {
171
+ if contents. contains ( LineEnding . crlf . rawValue) {
172
+ lineEnding = . crlf
173
+ } else if contents. contains ( LineEnding . nl . rawValue) {
174
+ lineEnding = . nl
175
+ } else if contents. contains ( LineEnding . cr . rawValue) {
176
+ lineEnding = . cr
181
177
}
182
178
}
183
179
}
@@ -248,7 +244,7 @@ open class CSVImporter<T> {
248
244
/// - Parameters:
249
245
/// - closure: The closure to be called on failure.
250
246
/// - Returns: `self` to enable consecutive method calls (e.g. `importer.startImportingRecords {...}.onProgress {...}`).
251
- open func onFail( _ closure: @escaping ( ) -> Void ) -> Self {
247
+ public func onFail( _ closure: @escaping ( ) -> Void ) -> Self {
252
248
self . failClosure = closure
253
249
return self
254
250
}
@@ -259,7 +255,7 @@ open class CSVImporter<T> {
259
255
/// - Parameters:
260
256
/// - closure: The closure to be called on progress. Takes the current count of imported lines as argument.
261
257
/// - Returns: `self` to enable consecutive method calls (e.g. `importer.startImportingRecords {...}.onProgress {...}`).
262
- open func onProgress( _ closure: @escaping ( _ importedDataLinesCount: Int ) -> Void ) -> Self {
258
+ public func onProgress( _ closure: @escaping ( _ importedDataLinesCount: Int ) -> Void ) -> Self {
263
259
self . progressClosure = closure
264
260
return self
265
261
}
@@ -268,7 +264,7 @@ open class CSVImporter<T> {
268
264
///
269
265
/// - Parameters:
270
266
/// - closure: The closure to be called on finish. Takes the array of all imported records mapped to as its argument.
271
- open func onFinish( _ closure: @escaping ( _ importedRecords: [ T ] ) -> Void ) {
267
+ public func onFinish( _ closure: @escaping ( _ importedRecords: [ T ] ) -> Void ) {
272
268
self . finishClosure = closure
273
269
}
274
270
@@ -293,7 +289,6 @@ open class CSVImporter<T> {
293
289
}
294
290
}
295
291
}
296
-
297
292
}
298
293
299
294
func reportFinish( _ importedRecords: [ T ] ) {
@@ -303,8 +298,6 @@ open class CSVImporter<T> {
303
298
}
304
299
}
305
300
}
306
-
307
-
308
301
}
309
302
310
303
0 commit comments