77//
88
99import Foundation
10- import FileKit
1110import HandySwift
1211
1312/// An enum to represent the possible line endings of CSV files.
1413public 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 = " "
1918}
2019
2120private let chunkSize = 4096
2221
2322/// Importer for CSV files that maps your lines to a specified data structure.
24- open class CSVImporter < T> {
25-
23+ public class CSVImporter < T> {
2624 // MARK: - Stored Instance Properties
2725
2826 let csvFile : TextFile
2927 let delimiter : String
3028 var lineEnding : LineEnding
29+ let encoding : String . Encoding
3130
3231 var lastProgressReport : Date ?
3332
@@ -39,10 +38,7 @@ open class CSVImporter<T> {
3938 // MARK: - Computed Instance Properties
4039
4140 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 )
4642 }
4743
4844
@@ -54,10 +50,11 @@ open class CSVImporter<T> {
5450 /// - path: The path to the CSV file to import.
5551 /// - delimiter: The delimiter used within the CSV file for separating fields. Defaults to ",".
5652 /// - 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 )
5955 self . delimiter = delimiter
6056 self . lineEnding = lineEnding
57+ self . encoding = encoding
6158
6259 delimiterQuoteDelimiter = " \( delimiter) \" \" \( delimiter) "
6360 delimiterDelimiter = delimiter+ delimiter
@@ -70,9 +67,9 @@ open class CSVImporter<T> {
7067 /// - Parameters:
7168 /// - url: File URL for the CSV file to import.
7269 /// - 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 ) {
7471 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 )
7673 }
7774
7875 // MARK: - Instance Methods
@@ -82,7 +79,7 @@ open class CSVImporter<T> {
8279 /// - Parameters:
8380 /// - mapper: A closure to map the data received in a line to your data structure.
8481 /// - 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 {
8683 DispatchQueue . global ( qos: DispatchQoS . QoSClass. userInitiated) . async {
8784 var importedRecords : [ T ] = [ ]
8885
@@ -109,7 +106,8 @@ open class CSVImporter<T> {
109106 /// - structure: A closure for doing something with the found structure within the first line of the CSV file.
110107 /// - recordMapper: A closure to map the dictionary data interpreted from a line to your data structure.
111108 /// - 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 {
113111 DispatchQueue . global ( qos: DispatchQoS . QoSClass. userInitiated) . async {
114112 var recordStructure : [ String ] ?
115113 var importedRecords : [ T ] = [ ]
@@ -147,37 +145,35 @@ open class CSVImporter<T> {
147145 /// - valuesInLine: The values found within a line.
148146 /// - Returns: `true` on finish or `false` if can't read file.
149147 func importLines( _ closure: ( _ valuesInLine: [ String ] ) -> Void ) -> Bool {
150- if lineEnding == . Unknown {
148+ if lineEnding == . unknown {
151149 lineEnding = lineEndingForFile ( )
152150 }
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 }
160152
161- return true
162- } else {
163- return false
153+ for line in csvStreamReader {
154+ autoreleasepool {
155+ let valuesInLine = readValuesInLine ( line)
156+ closure ( valuesInLine)
157+ }
164158 }
159+
160+ return true
165161 }
166162
167163 /// Determines the line ending for the CSV file
168164 ///
169165 /// - Returns: the lineEnding for the CSV file or default of NL.
170166 fileprivate func lineEndingForFile( ) -> LineEnding {
171- var lineEnding : LineEnding = . NL
167+ var lineEnding : LineEnding = . nl
172168 if let fileHandle = self . csvFile. handleForReading {
173169 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
181177 }
182178 }
183179 }
@@ -248,7 +244,7 @@ open class CSVImporter<T> {
248244 /// - Parameters:
249245 /// - closure: The closure to be called on failure.
250246 /// - 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 {
252248 self . failClosure = closure
253249 return self
254250 }
@@ -259,7 +255,7 @@ open class CSVImporter<T> {
259255 /// - Parameters:
260256 /// - closure: The closure to be called on progress. Takes the current count of imported lines as argument.
261257 /// - 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 {
263259 self . progressClosure = closure
264260 return self
265261 }
@@ -268,7 +264,7 @@ open class CSVImporter<T> {
268264 ///
269265 /// - Parameters:
270266 /// - 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 ) {
272268 self . finishClosure = closure
273269 }
274270
@@ -293,7 +289,6 @@ open class CSVImporter<T> {
293289 }
294290 }
295291 }
296-
297292 }
298293
299294 func reportFinish( _ importedRecords: [ T ] ) {
@@ -303,8 +298,6 @@ open class CSVImporter<T> {
303298 }
304299 }
305300 }
306-
307-
308301}
309302
310303
0 commit comments