From 44b485739666ee0e2ab64d98fa199c06c6d6bb43 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 23 Oct 2016 00:02:31 +0900 Subject: [PATCH 01/36] Add `struct CSV.Row` --- CSV.xcodeproj/project.pbxproj | 10 ++ CSVConfiguration.swift | 45 ++++++ Sources/CSV+init.swift | 43 +++++- Sources/CSV+subscript.swift | 14 +- Sources/CSV.swift | 233 +++++++++++++++++++++++--------- Tests/CSV/CSVTests.swift | 91 +++++++++---- Tests/CSV/LineBreakTests.swift | 2 +- Tests/CSV/ReadmeTests.swift | 41 ++++-- Tests/CSV/TrimFieldsTests.swift | 69 ++++++---- Tests/CSV/UnicodeTests.swift | 2 +- 10 files changed, 399 insertions(+), 151 deletions(-) create mode 100644 CSVConfiguration.swift diff --git a/CSV.xcodeproj/project.pbxproj b/CSV.xcodeproj/project.pbxproj index 8111c06..ac99638 100755 --- a/CSV.xcodeproj/project.pbxproj +++ b/CSV.xcodeproj/project.pbxproj @@ -17,6 +17,10 @@ 0E3CE3701DB529D700FA45CF /* TrimFieldsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CE36F1DB529D700FA45CF /* TrimFieldsTests.swift */; }; 0E3CE3711DB529D700FA45CF /* TrimFieldsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CE36F1DB529D700FA45CF /* TrimFieldsTests.swift */; }; 0E3CE3721DB529D700FA45CF /* TrimFieldsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CE36F1DB529D700FA45CF /* TrimFieldsTests.swift */; }; + 0E47EEB91DBB268000EBF783 /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */; }; + 0E47EEBA1DBB268000EBF783 /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */; }; + 0E47EEBB1DBB268000EBF783 /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */; }; + 0E47EEBC1DBB268000EBF783 /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */; }; 0E7E8C8C1D0BC7BB0057A1C1 /* CSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E7E8C811D0BC7BB0057A1C1 /* CSV.framework */; }; 0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; }; 0E7E8CA21D0BC7F10057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; }; @@ -87,6 +91,7 @@ 0E0F160D1D197DB800C92580 /* Endian.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Endian.swift; sourceTree = ""; }; 0E3CE36B1DB5281E00FA45CF /* UnicodeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeTests.swift; sourceTree = ""; }; 0E3CE36F1DB529D700FA45CF /* TrimFieldsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrimFieldsTests.swift; sourceTree = ""; }; + 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVConfiguration.swift; sourceTree = SOURCE_ROOT; }; 0E7E8C811D0BC7BB0057A1C1 /* CSV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CSV.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8C8B1D0BC7BB0057A1C1 /* CSVTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CSVTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSV.swift; sourceTree = ""; }; @@ -195,6 +200,7 @@ 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */, 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */, 0E9317D81D0DB30800AC20A0 /* CSV+subscript.swift */, + 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */, 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */, 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */, 0E0F160D1D197DB800C92580 /* Endian.swift */, @@ -514,6 +520,7 @@ 0E9317DA1D0DB30800AC20A0 /* CSV+subscript.swift in Sources */, 0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */, 0E0F160F1D197DB800C92580 /* Endian.swift in Sources */, + 0E47EEBA1DBB268000EBF783 /* CSVConfiguration.swift in Sources */, 0E7E8CA21D0BC7F10057A1C1 /* CSVError.swift in Sources */, 0EA2AB7D1D183B45003EC967 /* BinaryReader.swift in Sources */, ); @@ -540,6 +547,7 @@ 0E9317DC1D0DB30800AC20A0 /* CSV+subscript.swift in Sources */, 0E7E8CBE1D0BC9D70057A1C1 /* CSV.swift in Sources */, 0E0F16111D197DB800C92580 /* Endian.swift in Sources */, + 0E47EEBC1DBB268000EBF783 /* CSVConfiguration.swift in Sources */, 0E7E8CBF1D0BC9D70057A1C1 /* CSVError.swift in Sources */, 0EA2AB7F1D183B45003EC967 /* BinaryReader.swift in Sources */, ); @@ -554,6 +562,7 @@ 0E9317D91D0DB30800AC20A0 /* CSV+subscript.swift in Sources */, 0E7E8CE01D0BCA8E0057A1C1 /* CSV.swift in Sources */, 0E0F160E1D197DB800C92580 /* Endian.swift in Sources */, + 0E47EEB91DBB268000EBF783 /* CSVConfiguration.swift in Sources */, 0E7E8CE11D0BCA8E0057A1C1 /* CSVError.swift in Sources */, 0EA2AB7C1D183B45003EC967 /* BinaryReader.swift in Sources */, ); @@ -580,6 +589,7 @@ 0E9317DB1D0DB30800AC20A0 /* CSV+subscript.swift in Sources */, 0E7E8D001D0BCDCF0057A1C1 /* CSV.swift in Sources */, 0E0F16101D197DB800C92580 /* Endian.swift in Sources */, + 0E47EEBB1DBB268000EBF783 /* CSVConfiguration.swift in Sources */, 0E7E8D011D0BCDCF0057A1C1 /* CSVError.swift in Sources */, 0EA2AB7E1D183B45003EC967 /* BinaryReader.swift in Sources */, ); diff --git a/CSVConfiguration.swift b/CSVConfiguration.swift new file mode 100644 index 0000000..39e4b08 --- /dev/null +++ b/CSVConfiguration.swift @@ -0,0 +1,45 @@ +// +// CSVConfiguration.swift +// CSV +// +// Created by Yasuhiro Hatta on 2016/10/22. +// Copyright © 2016 yaslab. All rights reserved. +// + +import Foundation + +internal let defaultHasHeaderRow = false +internal let defaultTrimFields = false +internal let defaultDelimiter = UnicodeScalar(UInt8(0x2c)) // "," +internal let defaultWhitespaces = CharacterSet.whitespaces + +// TODO: Documentation +/// No overview available. +public struct CSVConfiguration { + + /// `true` if the CSV has a header row, otherwise `false`. Default: `false`. + public let hasHeaderRow: Bool + /// No overview available. + public let trimFields: Bool + /// Default: `","`. + public let delimiter: UnicodeScalar + /// No overview available. + public let whitespaces: CharacterSet + + /// No overview available. + public init( + hasHeaderRow: Bool = defaultHasHeaderRow, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter, + whitespaces: CharacterSet = defaultWhitespaces) + { + self.hasHeaderRow = hasHeaderRow + self.trimFields = trimFields + self.delimiter = delimiter + + var whitespaces = whitespaces + _ = whitespaces.remove(delimiter) + self.whitespaces = whitespaces + } + +} diff --git a/Sources/CSV+init.swift b/Sources/CSV+init.swift index 31e3f8b..263c738 100755 --- a/Sources/CSV+init.swift +++ b/Sources/CSV+init.swift @@ -9,9 +9,23 @@ import Foundation extension CSV { + + /// Create an instance with `InputStream`. + /// + /// - parameter stream: An `InputStream` object. If the stream is not open, initializer opens automatically. + /// - parameter config: CSV configuration. + public init( + stream: InputStream, + config: CSVConfiguration = CSVConfiguration()) + throws + { + try self.init(stream: stream, codecType: UTF8.self, config: config) + } - // TODO: Documentation - /// No overview available. + // MARK: - deprecated + + /// Unavailable. + @available(*, unavailable, message: "Use init(stream:config:) instead") public init( stream: InputStream, hasHeaderRow: Bool = defaultHasHeaderRow, @@ -19,15 +33,31 @@ extension CSV { delimiter: UnicodeScalar = defaultDelimiter) throws { - try self.init(stream: stream, codecType: UTF8.self, hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter) + let config = CSVConfiguration(hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter, whitespaces: defaultWhitespaces) + try self.init(stream: stream, codecType: UTF8.self, config: config) } } extension CSV { + + /// Create an instance with CSV string. + /// + /// - parameter string: An CSV string. + /// - parameter config: CSV configuration. + public init( + string: String, + config: CSVConfiguration = CSVConfiguration()) + throws + { + let iterator = string.unicodeScalars.makeIterator() + try self.init(iterator: iterator, config: config) + } - // TODO: Documentation - /// No overview available. + // MARK: - deprecated + + /// Unavailable. + @available(*, unavailable, message: "Use init(string:config:) instead") public init( string: String, hasHeaderRow: Bool = defaultHasHeaderRow, @@ -36,7 +66,8 @@ extension CSV { throws { let iterator = string.unicodeScalars.makeIterator() - try self.init(iterator: iterator, hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter) + let config = CSVConfiguration(hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter, whitespaces: defaultWhitespaces) + try self.init(iterator: iterator, config: config) } } diff --git a/Sources/CSV+subscript.swift b/Sources/CSV+subscript.swift index 0f4be01..0f68b80 100755 --- a/Sources/CSV+subscript.swift +++ b/Sources/CSV+subscript.swift @@ -10,19 +10,9 @@ extension CSV { // TODO: Documentation /// No overview available. + @available(*, deprecated, message: "Use CSV.Row.subscript(String) instead") public subscript(key: String) -> String? { - get { - guard let headerRow = headerRow, let currentRow = currentRow else { - return nil - } - guard let index = headerRow.index(of: key) else { - return nil - } - if index >= currentRow.count { - return nil - } - return currentRow[index] - } + return currentRow?[key] } } diff --git a/Sources/CSV.swift b/Sources/CSV.swift index 2d66557..5b5b462 100755 --- a/Sources/CSV.swift +++ b/Sources/CSV.swift @@ -8,13 +8,9 @@ import Foundation -private let LF = UnicodeScalar("\n")! -private let CR = UnicodeScalar("\r")! -private let DQUOTE = UnicodeScalar("\"")! - -internal let defaultHasHeaderRow = false -internal let defaultTrimFields = false -internal let defaultDelimiter = UnicodeScalar(",")! +private let LF = UnicodeScalar(UInt8(0x0a)) // "\n" +private let CR = UnicodeScalar(UInt8(0x0d)) // "\r" +private let DQUOTE = UnicodeScalar(UInt8(0x22)) // "\"" extension CSV: Sequence { } @@ -22,8 +18,12 @@ extension CSV: IteratorProtocol { // TODO: Documentation /// No overview available. - public mutating func next() -> [String]? { - return readRow() + public mutating func next() -> Row? { + guard let row = readRow() else { + return nil + } + currentRow = Row(data: row, headerRow: headerRow) + return currentRow } } @@ -32,50 +32,105 @@ extension CSV: IteratorProtocol { /// No overview available. public struct CSV { + /// No overview available. + public typealias HeaderRow = [String] + + /// No overview available. + public struct Row: RandomAccessCollection { + + private let data: [String] + private let headerRow: HeaderRow? + + internal init(data: [String], headerRow: HeaderRow?) { + self.data = data + self.headerRow = headerRow + } + + // MARK: - RandomAccessCollection + + /// No overview available. + public var startIndex: Int { + return data.startIndex + } + + /// No overview available. + public var endIndex: Int { + return data.endIndex + } + + /// No overview available. + public func index(before i: Int) -> Int { + return data.index(before: i) + } + + /// No overview available. + public func index(after i: Int) -> Int { + return data.index(after: i) + } + + /// No overview available. + public subscript(index: Int) -> String { + return data[index] + } + + // MARK: - Public method + + /// No overview available. + public subscript(key: String) -> String? { + assert(headerRow != nil, "CSVConfiguration.hasHeaderRow must be true") + guard let index = headerRow!.index(of: key) else { + return nil + } + return data[index] + } + + /// No overview available. + public func toArray() -> [String] { + return data + } + + /// No overview available. + public func toDictionary() -> [String : String] { + assert(headerRow != nil, "CSVConfiguration.hasHeaderRow must be true") + var dictionary: [String : String] = [:] + for (key, value) in zip(headerRow!, data) { + dictionary[key] = value + } + return dictionary + } + + } + private var iterator: AnyIterator - private let trimFields: Bool - private let delimiter: UnicodeScalar - + private let config: CSVConfiguration + private var back: UnicodeScalar? = nil - - internal var currentRow: [String]? = nil + + // TODO: deprecated + internal var currentRow: Row? = nil /// CSV header row. To set a value for this property, you set `true` to `hasHeaerRow` in initializer. - public var headerRow: [String]? { return _headerRow } - private var _headerRow: [String]? = nil - - private let whitespaces: CharacterSet + public private(set) var headerRow: HeaderRow? = nil internal init( iterator: T, - hasHeaderRow: Bool, - trimFields: Bool, - delimiter: UnicodeScalar) + config: CSVConfiguration) throws where T.Element == UnicodeScalar { self.iterator = AnyIterator(iterator) - self.trimFields = trimFields - self.delimiter = delimiter - - var whitespaces = CharacterSet.whitespaces - whitespaces.remove(delimiter) - self.whitespaces = whitespaces + self.config = config - if hasHeaderRow { - guard let headerRow = next() else { + if config.hasHeaderRow { + guard let headerRow = readRow() else { throw CSVError.cannotReadHeaderRow } - _headerRow = headerRow + self.headerRow = headerRow } } - /// Create an instance with `InputStream`. - /// - /// - parameter stream: An `InputStream` object. If the stream is not open, initializer opens automatically. - /// - parameter codecType: A `UnicodeCodec` type for `stream`. - /// - parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`. - /// - parameter delimiter: Default: `","`. + /// Unavailable. + @available(*, unavailable, message: "Use init(stream:codecType:config:) instead") public init( stream: InputStream, codecType: T.Type, @@ -87,16 +142,12 @@ public struct CSV { { let reader = try BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true) let iterator = UnicodeIterator(input: reader.makeUInt8Iterator(), inputEncodingType: codecType) - try self.init(iterator: iterator, hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter) + let config = CSVConfiguration(hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter, whitespaces: defaultWhitespaces) + try self.init(iterator: iterator, config: config) } - /// Create an instance with `InputStream`. - /// - /// - parameter stream: An `InputStream` object. If the stream is not open, initializer opens automatically. - /// - parameter codecType: A `UnicodeCodec` type for `stream`. - /// - parameter endian: Endian to use when reading a stream. Default: `.big`. - /// - parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`. - /// - parameter delimiter: Default: `","`. + /// Unavailable. + @available(*, unavailable, message: "Use init(stream:codecType:config:) instead") public init( stream: InputStream, codecType: T.Type, @@ -109,16 +160,12 @@ public struct CSV { { let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) let iterator = UnicodeIterator(input: reader.makeUInt16Iterator(), inputEncodingType: codecType) - try self.init(iterator: iterator, hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter) + let config = CSVConfiguration(hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter, whitespaces: defaultWhitespaces) + try self.init(iterator: iterator, config: config) } - /// Create an instance with `InputStream`. - /// - /// - parameter stream: An `InputStream` object. If the stream is not open, initializer opens automatically. - /// - parameter codecType: A `UnicodeCodec` type for `stream`. - /// - parameter endian: Endian to use when reading a stream. Default: `.big`. - /// - parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`. - /// - parameter delimiter: Default: `","`. + /// Unavailable. + @available(*, unavailable, message: "Use init(stream:codecType:config:) instead") public init( stream: InputStream, codecType: T.Type, @@ -131,12 +178,69 @@ public struct CSV { { let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) let iterator = UnicodeIterator(input: reader.makeUInt32Iterator(), inputEncodingType: codecType) - try self.init(iterator: iterator, hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter) + let config = CSVConfiguration(hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter, whitespaces: defaultWhitespaces) + try self.init(iterator: iterator, config: config) } + /// Create an instance with `InputStream`. + /// + /// - parameter stream: An `InputStream` object. If the stream is not open, initializer opens automatically. + /// - parameter codecType: A `UnicodeCodec` type for `stream`. + /// - parameter endian: Endian to use when reading a stream. Default: `.big`. + /// - parameter config: CSV configuration. + public init( + stream: InputStream, + codecType: T.Type, + config: CSVConfiguration = CSVConfiguration()) + throws + where T.CodeUnit == UInt8 + { + let reader = try BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true) + let iterator = UnicodeIterator(input: reader.makeUInt8Iterator(), inputEncodingType: codecType) + try self.init(iterator: iterator, config: config) + } + + /// Create an instance with `InputStream`. + /// + /// - parameter stream: An `InputStream` object. If the stream is not open, initializer opens automatically. + /// - parameter codecType: A `UnicodeCodec` type for `stream`. + /// - parameter endian: Endian to use when reading a stream. Default: `.big`. + /// - parameter config: CSV configuration. + public init( + stream: InputStream, + codecType: T.Type, + endian: Endian = .big, + config: CSVConfiguration = CSVConfiguration()) + throws + where T.CodeUnit == UInt16 + { + let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) + let iterator = UnicodeIterator(input: reader.makeUInt16Iterator(), inputEncodingType: codecType) + try self.init(iterator: iterator, config: config) + } + + /// Create an instance with `InputStream`. + /// + /// - parameter stream: An `InputStream` object. If the stream is not open, initializer opens automatically. + /// - parameter codecType: A `UnicodeCodec` type for `stream`. + /// - parameter endian: Endian to use when reading a stream. Default: `.big`. + /// - parameter config: CSV configuration. + public init( + stream: InputStream, + codecType: T.Type, + endian: Endian = .big, + config: CSVConfiguration = CSVConfiguration()) + throws + where T.CodeUnit == UInt32 + { + let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) + let iterator = UnicodeIterator(input: reader.makeUInt32Iterator(), inputEncodingType: codecType) + try self.init(iterator: iterator, config: config) + } + + // MARK: - Parse CSV + fileprivate mutating func readRow() -> [String]? { - currentRow = nil - var next = moveNext() if next == nil { return nil @@ -146,9 +250,9 @@ public struct CSV { var field: String var end: Bool while true { - if trimFields { + if config.trimFields { // Trim the leading spaces - while next != nil && whitespaces.contains(next!) { + while next != nil && config.whitespaces.contains(next!) { next = moveNext() } } @@ -163,9 +267,9 @@ public struct CSV { back = next (field, end) = readField(quoted: false) - if trimFields { + if config.trimFields { // Trim the trailing spaces - field = field.trimmingCharacters(in: whitespaces) + field = field.trimmingCharacters(in: config.whitespaces) } } row.append(field) @@ -175,7 +279,6 @@ public struct CSV { next = moveNext() } - currentRow = row return row } @@ -188,9 +291,9 @@ public struct CSV { if c == DQUOTE { var cNext = moveNext() - if trimFields { + if config.trimFields { // Trim the trailing spaces - while cNext != nil && whitespaces.contains(cNext!) { + while cNext != nil && config.whitespaces.contains(cNext!) { cNext = moveNext() } } @@ -205,7 +308,7 @@ public struct CSV { // END ROW return (field, true) } - else if cNext == delimiter { + else if cNext == config.delimiter { // END FIELD return (field, false) } @@ -233,7 +336,7 @@ public struct CSV { // END ROW return (field, true) } - else if c == delimiter { + else if c == config.delimiter { // END FIELD return (field, false) } @@ -256,5 +359,5 @@ public struct CSV { } return iterator.next() } - + } diff --git a/Tests/CSV/CSVTests.swift b/Tests/CSV/CSVTests.swift index bcdb279..6528d42 100755 --- a/Tests/CSV/CSVTests.swift +++ b/Tests/CSV/CSVTests.swift @@ -16,7 +16,7 @@ class CSVTests: XCTestCase { var i = 0 for row in try! CSV(string: csv) { switch i { - case 0: XCTAssertEqual(row, ["abc", "1", "2"]) + case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"]) default: break } i += 1 @@ -29,8 +29,8 @@ class CSVTests: XCTestCase { var i = 0 for row in try! CSV(string: csv) { switch i { - case 0: XCTAssertEqual(row, ["abc", "1", "2"]) - case 1: XCTAssertEqual(row, ["cde", "3", "4"]) + case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"]) + case 1: XCTAssertEqual(row.toArray(), ["cde", "3", "4"]) default: break } i += 1 @@ -43,8 +43,8 @@ class CSVTests: XCTestCase { var i = 0 for row in try! CSV(string: csv) { switch i { - case 0: XCTAssertEqual(row, ["abc", "1", "2"]) - case 1: XCTAssertEqual(row, ["cde", "3", "4"]) + case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"]) + case 1: XCTAssertEqual(row.toArray(), ["cde", "3", "4"]) default: break } i += 1 @@ -57,9 +57,9 @@ class CSVTests: XCTestCase { var i = 0 for row in try! CSV(string: csv) { switch i { - case 0: XCTAssertEqual(row, ["abc", "1", "2"]) - case 1: XCTAssertEqual(row, ["cde", "3", "4"]) - case 2: XCTAssertEqual(row, [" "]) + case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"]) + case 1: XCTAssertEqual(row.toArray(), ["cde", "3", "4"]) + case 2: XCTAssertEqual(row.toArray(), [" "]) default: break } i += 1 @@ -72,9 +72,9 @@ class CSVTests: XCTestCase { var i = 0 for row in try! CSV(string: csv) { switch i { - case 0: XCTAssertEqual(row, ["abc", "1", "2"]) - case 1: XCTAssertEqual(row, [""]) - case 2: XCTAssertEqual(row, ["cde", "3", "4"]) + case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"]) + case 1: XCTAssertEqual(row.toArray(), [""]) + case 2: XCTAssertEqual(row.toArray(), ["cde", "3", "4"]) default: break } i += 1 @@ -86,34 +86,34 @@ class CSVTests: XCTestCase { let csvString = "abab,\"cd,cd\",efef" var csv = try! CSV(string: csvString) let row = csv.next()! - XCTAssertEqual(row, ["abab", "cd,cd", "efef"]) + XCTAssertEqual(row.toArray(), ["abab", "cd,cd", "efef"]) } func testEscapedQuotationMark1() { let csvString = "abab,\"\"\"cdcd\",efef\r\nzxcv,asdf,qwer" var csv = try! CSV(string: csvString) var row = csv.next()! - XCTAssertEqual(row, ["abab", "\"cdcd", "efef"]) + XCTAssertEqual(row.toArray(), ["abab", "\"cdcd", "efef"]) row = csv.next()! - XCTAssertEqual(row, ["zxcv", "asdf", "qwer"]) + XCTAssertEqual(row.toArray(), ["zxcv", "asdf", "qwer"]) } func testEscapedQuotationMark2() { let csvString = "abab,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\"" var csv = try! CSV(string: csvString) var row = csv.next()! - XCTAssertEqual(row, ["abab", "cdcd", "efef"]) + XCTAssertEqual(row.toArray(), ["abab", "cdcd", "efef"]) row = csv.next()! - XCTAssertEqual(row, ["zxcv", "asdf", "qw\"er"]) + XCTAssertEqual(row.toArray(), ["zxcv", "asdf", "qw\"er"]) } func testEmptyField() { let csvString = "abab,,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\"," var csv = try! CSV(string: csvString) var row = csv.next()! - XCTAssertEqual(row, ["abab", "", "cdcd", "efef"]) + XCTAssertEqual(row.toArray(), ["abab", "", "cdcd", "efef"]) row = csv.next()! - XCTAssertEqual(row, ["zxcv", "asdf", "qw\"er", ""]) + XCTAssertEqual(row.toArray(), ["zxcv", "asdf", "qw\"er", ""]) } func testDoubleQuoteBeforeLineBreak() { @@ -121,9 +121,9 @@ class CSVTests: XCTestCase { var i = 0 for row in try! CSV(string: csv) { switch i { - case 0: XCTAssertEqual(row, ["abc", "1", "2"]) - case 1: XCTAssertEqual(row, [""]) - case 2: XCTAssertEqual(row, ["cde", "3", "4"]) + case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"]) + case 1: XCTAssertEqual(row.toArray(), [""]) + case 2: XCTAssertEqual(row.toArray(), ["cde", "3", "4"]) default: break } i += 1 @@ -133,7 +133,8 @@ class CSVTests: XCTestCase { func testSubscript() { let csvString = "id,name\n001,hoge\n002,fuga" - var csv = try! CSV(string: csvString, hasHeaderRow: true) + let config = CSVConfiguration(hasHeaderRow: true) + var csv = try! CSV(string: csvString, config: config) var i = 0 while csv.next() != nil { switch i { @@ -153,16 +154,58 @@ class CSVTests: XCTestCase { func testCSVState1() { let it = "あ,い1,\"う\",えお\n,,x,".unicodeScalars.makeIterator() - var csv = try! CSV(iterator: it, hasHeaderRow: defaultHasHeaderRow, trimFields: defaultTrimFields, delimiter: defaultDelimiter) + var csv = try! CSV(iterator: it, config: CSVConfiguration()) var rows = [[String]]() while let row = csv.next() { - rows.append(row) + rows.append(row.toArray()) } XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0], ["あ", "い1", "う", "えお"]) XCTAssertEqual(rows[1], ["", "", "x", ""]) } + func testSubscriptInt() { + let csvString = "a,bb,ccc" + let csv = try! CSV(string: csvString) + for row in csv { + XCTAssertEqual(row[0], "a") + XCTAssertEqual(row[1], "bb") + XCTAssertEqual(row[2], "ccc") + } + } + + func testSubscriptString() { + let csvString = "key1,key2\nvalue1,value2" + let config = CSVConfiguration(hasHeaderRow: true) + let csv = try! CSV(string: csvString, config: config) + for row in csv { + XCTAssertEqual(row["key1"], "value1") + XCTAssertEqual(row["key2"], "value2") + XCTAssertNil(row["key9"]) + } + } + + func testToArray() { + let csvString = "1,2,3,4,5\n6,7,8,9,0" + let csv = try! CSV(string: csvString) + let rows = csv.map { $0.toArray() } + XCTAssertEqual(rows[0], ["1", "2", "3", "4", "5"]) + XCTAssertEqual(rows[1], ["6", "7", "8", "9", "0"]) + } + + func testToDictionary() { + let csvString = "id,name\n1,name1\n2,name2" + let config = CSVConfiguration(hasHeaderRow: true) + let csv = try! CSV(string: csvString, config: config) + let rows = csv.map { $0.toDictionary() } + XCTAssertEqual(rows[0]["id"], "1") + XCTAssertEqual(rows[0]["name"], "name1") + XCTAssertNil(rows[0]["xxx"]) + XCTAssertEqual(rows[1]["id"], "2") + XCTAssertEqual(rows[1]["name"], "name2") + XCTAssertNil(rows[1]["yyy"]) + } + } diff --git a/Tests/CSV/LineBreakTests.swift b/Tests/CSV/LineBreakTests.swift index 040b0fe..6cf52e3 100644 --- a/Tests/CSV/LineBreakTests.swift +++ b/Tests/CSV/LineBreakTests.swift @@ -112,7 +112,7 @@ class LineBreakTests: XCTestCase { let reader = try! CSV(string: csv) var records = [[String]]() for row in reader { - records.append(row) + records.append(row.toArray()) } return records } diff --git a/Tests/CSV/ReadmeTests.swift b/Tests/CSV/ReadmeTests.swift index 46124c9..e11517b 100755 --- a/Tests/CSV/ReadmeTests.swift +++ b/Tests/CSV/ReadmeTests.swift @@ -12,24 +12,26 @@ import XCTest class ReadmeTests: XCTestCase { func testFromCSVString() { - for row in try! CSV(string: "1,foo\n2,bar") { + let csv = try! CSV(string: "1,foo\n2,bar") + for row in csv { print("\(row)") // => ["1", "foo"] // => ["2", "bar"] } } - func testFromFilePath() { + func testFromFile() { // let stream = InputStream(fileAtPath: "/path/to/file.csv")! -// for row in try! CSV(stream: stream) { +// let csv = try! CSV(stream: stream) +// for row in csv { // print("\(row)") // } } func testGettingTheHeaderRow() { - let csv = try! CSV( - string: "id,name\n1,foo\n2,bar", - hasHeaderRow: true) // default: false + let csvString = "id,name\n1,foo\n2,bar" + let config = CSVConfiguration(hasHeaderRow: true) // It must be true. + let csv = try! CSV(string: csvString, config: config) let headerRow = csv.headerRow! print("\(headerRow)") // => ["id", "name"] @@ -40,15 +42,26 @@ class ReadmeTests: XCTestCase { // => ["2", "bar"] } } - - func testGetTheFieldValueUsingSubscript() { - var csv = try! CSV( - string: "id,name\n1,foo", - hasHeaderRow: true) // It must be true. + + func testGetTheFieldValueUsingIndex() { + let csvString = "1,foo" + let csv = try! CSV(string: csvString) - while let _ = csv.next() { - print("\(csv["id"]!)") // => "1" - print("\(csv["name"]!)") // => "foo" + for row in csv { + print("\(row[0])") // => "1" + print("\(row[1])") // => "foo" + } + } + + + func testGetTheFieldValueUsingKey() { + let csvString = "id,name\n1,foo" + let config = CSVConfiguration(hasHeaderRow: true) // It must be true. + let csv = try! CSV(string: csvString, config: config) + + for row in csv { + print("\(row["id"]!)") // => "1" + print("\(row["name"]!)") // => "foo" } } diff --git a/Tests/CSV/TrimFieldsTests.swift b/Tests/CSV/TrimFieldsTests.swift index cbfd4ec..dd7d24c 100644 --- a/Tests/CSV/TrimFieldsTests.swift +++ b/Tests/CSV/TrimFieldsTests.swift @@ -13,109 +13,122 @@ class TrimFieldsTests: XCTestCase { func testTrimFields1() { let csvString = "abc,def,ghi" - let csv = try! CSV(string: csvString, trimFields: true) + let config = CSVConfiguration(trimFields: true) + let csv = try! CSV(string: csvString, config: config) for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } func testTrimFields2() { let csvString = " abc, def, ghi" - let csv = try! CSV(string: csvString, trimFields: true) + let config = CSVConfiguration(trimFields: true) + let csv = try! CSV(string: csvString, config: config) for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } func testTrimFields3() { let csvString = "abc ,def ,ghi " - let csv = try! CSV(string: csvString, trimFields: true) + let config = CSVConfiguration(trimFields: true) + let csv = try! CSV(string: csvString, config: config) for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } func testTrimFields4() { let csvString = " abc , def , ghi " - let csv = try! CSV(string: csvString, trimFields: true) + let config = CSVConfiguration(trimFields: true) + let csv = try! CSV(string: csvString, config: config) for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } func testTrimFields5() { let csvString = "\"abc\",\"def\",\"ghi\"" - let csv = try! CSV(string: csvString, trimFields: true) + let config = CSVConfiguration(trimFields: true) + let csv = try! CSV(string: csvString, config: config) for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } func testTrimFields6() { let csvString = " \"abc\", \"def\", \"ghi\"" - let csv = try! CSV(string: csvString, trimFields: true) + let config = CSVConfiguration(trimFields: true) + let csv = try! CSV(string: csvString, config: config) for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } func testTrimFields7() { let csvString = "\"abc\" ,\"def\" ,\"ghi\" " - let csv = try! CSV(string: csvString, trimFields: true) + let config = CSVConfiguration(trimFields: true) + let csv = try! CSV(string: csvString, config: config) for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } func testTrimFields8() { let csvString = " \"abc\" , \"def\" , \"ghi\" " - let csv = try! CSV(string: csvString, trimFields: true) + let config = CSVConfiguration(trimFields: true) + let csv = try! CSV(string: csvString, config: config) for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } func testTrimFields9() { let csvString = "\" abc \",\" def \",\" ghi \"" - let csv = try! CSV(string: csvString, trimFields: true) + let config = CSVConfiguration(trimFields: true) + let csv = try! CSV(string: csvString, config: config) for row in csv { - XCTAssertEqual(row, [" abc ", " def ", " ghi "]) + XCTAssertEqual(row.toArray(), [" abc ", " def ", " ghi "]) } } func testTrimFields10() { let csvString = "\tabc,\t\tdef\t,ghi\t" - let csv = try! CSV(string: csvString, trimFields: true) + let config = CSVConfiguration(trimFields: true) + let csv = try! CSV(string: csvString, config: config) for row in csv { - XCTAssertEqual(row, ["abc", "def", "ghi"]) + XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } func testTrimFields11() { let csvString = " abc \n def " - var csv = try! CSV(string: csvString, trimFields: true) + let config = CSVConfiguration(trimFields: true) + var csv = try! CSV(string: csvString, config: config) let row1 = csv.next()! - XCTAssertEqual(row1, ["abc"]) + XCTAssertEqual(row1.toArray(), ["abc"]) let row2 = csv.next()! - XCTAssertEqual(row2, ["def"]) + XCTAssertEqual(row2.toArray(), ["def"]) } func testTrimFields12() { let csvString = " \"abc \" \n \" def\" " - var csv = try! CSV(string: csvString, trimFields: true) + let config = CSVConfiguration(trimFields: true) + var csv = try! CSV(string: csvString, config: config) let row1 = csv.next()! - XCTAssertEqual(row1, ["abc "]) + XCTAssertEqual(row1.toArray(), ["abc "]) let row2 = csv.next()! - XCTAssertEqual(row2, [" def"]) + XCTAssertEqual(row2.toArray(), [" def"]) } func testTrimFields13() { let csvString = " abc \t\tdef\t ghi " - let csv = try! CSV(string: csvString, trimFields: true, delimiter: UnicodeScalar("\t")!) + let config = CSVConfiguration(trimFields: true, delimiter: UnicodeScalar("\t")!) + let csv = try! CSV(string: csvString, config: config) for row in csv { - XCTAssertEqual(row, ["abc", "", "def", "ghi"]) + XCTAssertEqual(row.toArray(), ["abc", "", "def", "ghi"]) } } diff --git a/Tests/CSV/UnicodeTests.swift b/Tests/CSV/UnicodeTests.swift index a75c800..12bb418 100644 --- a/Tests/CSV/UnicodeTests.swift +++ b/Tests/CSV/UnicodeTests.swift @@ -103,7 +103,7 @@ class UnicodeTests: XCTestCase { private func getRecords(csv: CSV) -> [[String]] { var records = [[String]]() for row in csv { - records.append(row) + records.append(row.toArray()) } return records } From 3bd51605e7da26f1c82a1e0c23c9d1cfb0a5539d Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 23 Oct 2016 00:22:04 +0900 Subject: [PATCH 02/36] Add .swiftlint.yml --- .swiftlint.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .swiftlint.yml diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..94bb8a2 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,3 @@ +disabled_rules: + - force_cast + - force_try From 345264273c0a4833fab382c2cd0d24e2d31c6448 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 23 Oct 2016 00:47:25 +0900 Subject: [PATCH 03/36] Update .swiftlint.yml --- .swiftlint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.swiftlint.yml b/.swiftlint.yml index 94bb8a2..270cbaf 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,3 +1,4 @@ disabled_rules: - force_cast - force_try + - variable_name From 3834bbbb4fde0984bd633aa3cd67347f0acdf556 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 23 Oct 2016 01:06:26 +0900 Subject: [PATCH 04/36] Run swiftlint autocorrect --- CSV.xcodeproj/project.pbxproj | 18 +++ CSVConfiguration.swift | 6 +- Sources/BinaryReader.swift | 49 ++++---- Sources/CSV+init.swift | 47 ++++---- Sources/CSV+subscript.swift | 4 +- Sources/CSV.swift | 194 +++++++++++++++++--------------- Sources/UnicodeIterator.swift | 8 +- Tests/CSV/CSVTests.swift | 26 ++--- Tests/CSV/LineBreakTests.swift | 6 +- Tests/CSV/ReadmeTests.swift | 19 ++-- Tests/CSV/TrimFieldsTests.swift | 30 ++--- Tests/CSV/UnicodeTests.swift | 16 +-- 12 files changed, 232 insertions(+), 191 deletions(-) diff --git a/CSV.xcodeproj/project.pbxproj b/CSV.xcodeproj/project.pbxproj index ac99638..eb8c4f9 100755 --- a/CSV.xcodeproj/project.pbxproj +++ b/CSV.xcodeproj/project.pbxproj @@ -327,6 +327,7 @@ isa = PBXNativeTarget; buildConfigurationList = 0E7E8CD71D0BCA2A0057A1C1 /* Build configuration list for PBXNativeTarget "CSV-OSX" */; buildPhases = ( + 0E47EEBF1DBBC05700EBF783 /* Run Script () */, 0E7E8CC11D0BCA2A0057A1C1 /* Sources */, 0E7E8CC21D0BCA2A0057A1C1 /* Frameworks */, 0E7E8CC31D0BCA2A0057A1C1 /* Headers */, @@ -510,6 +511,23 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 0E47EEBF1DBBC05700EBF783 /* Run Script () */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script ()"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\n swiftlint autocorrect\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 0E7E8C7C1D0BC7BB0057A1C1 /* Sources */ = { isa = PBXSourcesBuildPhase; diff --git a/CSVConfiguration.swift b/CSVConfiguration.swift index 39e4b08..5fbf379 100644 --- a/CSVConfiguration.swift +++ b/CSVConfiguration.swift @@ -31,8 +31,8 @@ public struct CSVConfiguration { hasHeaderRow: Bool = defaultHasHeaderRow, trimFields: Bool = defaultTrimFields, delimiter: UnicodeScalar = defaultDelimiter, - whitespaces: CharacterSet = defaultWhitespaces) - { + whitespaces: CharacterSet = defaultWhitespaces) { + self.hasHeaderRow = hasHeaderRow self.trimFields = trimFields self.delimiter = delimiter @@ -41,5 +41,5 @@ public struct CSVConfiguration { _ = whitespaces.remove(delimiter) self.whitespaces = whitespaces } - + } diff --git a/Sources/BinaryReader.swift b/Sources/BinaryReader.swift index 8e086ac..10e5cf2 100755 --- a/Sources/BinaryReader.swift +++ b/Sources/BinaryReader.swift @@ -50,8 +50,12 @@ internal class BinaryReader { private var tempBuffer = [UInt8](repeating: 0, count: 4) private let tempBufferSize = 4 private var tempBufferOffset = 0 - - internal init(stream: InputStream, endian: Endian = .unknown, closeOnDeinit: Bool = true) throws { + + internal init( + stream: InputStream, + endian: Endian = .unknown, + closeOnDeinit: Bool = true) throws { + var endian = endian if stream.streamStatus == .notOpen { @@ -74,7 +78,7 @@ internal class BinaryReader { self.endian = endian self.closeOnDeinit = closeOnDeinit } - + deinit { if closeOnDeinit && stream.streamStatus != .closed { stream.close() @@ -101,7 +105,7 @@ internal class BinaryReader { } return stream.read(buffer + i, maxLength: maxLength - i) } - + internal func readUInt8() throws -> UInt8 { let bufferSize = 1 let length = try readStream(&buffer, maxLength: bufferSize) @@ -113,7 +117,7 @@ internal class BinaryReader { } return buffer[0] } - + internal func readUInt16() throws -> UInt16 { let bufferSize = 2 let length = try readStream(&buffer, maxLength: bufferSize) @@ -134,7 +138,7 @@ internal class BinaryReader { } } } - + internal func readUInt32() throws -> UInt32 { let bufferSize = 4 let length = try readStream(&buffer, maxLength: bufferSize) @@ -155,33 +159,32 @@ internal class BinaryReader { } } } - + } extension BinaryReader { internal struct UInt8Iterator: Sequence, IteratorProtocol { - + private let reader: BinaryReader - + fileprivate init(reader: BinaryReader) { self.reader = reader } - + internal mutating func next() -> UInt8? { if !reader.hasBytesAvailable { return nil } do { return try reader.readUInt8() - } - catch /*let error*/ { + } catch { return nil } } - + } - + internal func makeUInt8Iterator() -> UInt8Iterator { return UInt8Iterator(reader: self) } @@ -189,33 +192,32 @@ extension BinaryReader { } extension BinaryReader { - + internal struct UInt16Iterator: Sequence, IteratorProtocol { - + private let reader: BinaryReader - + fileprivate init(reader: BinaryReader) { self.reader = reader } - + internal mutating func next() -> UInt16? { if !reader.hasBytesAvailable { return nil } do { return try reader.readUInt16() - } - catch /*let error*/ { + } catch { return nil } } } - + internal func makeUInt16Iterator() -> UInt16Iterator { return UInt16Iterator(reader: self) } - + } extension BinaryReader { @@ -234,8 +236,7 @@ extension BinaryReader { } do { return try reader.readUInt32() - } - catch /*let error*/ { + } catch { return nil } } diff --git a/Sources/CSV+init.swift b/Sources/CSV+init.swift index 263c738..a0a5f03 100755 --- a/Sources/CSV+init.swift +++ b/Sources/CSV+init.swift @@ -12,31 +12,34 @@ extension CSV { /// Create an instance with `InputStream`. /// - /// - parameter stream: An `InputStream` object. If the stream is not open, initializer opens automatically. + /// - parameter stream: An `InputStream` object. If the stream is not open, + /// initializer opens automatically. /// - parameter config: CSV configuration. public init( stream: InputStream, - config: CSVConfiguration = CSVConfiguration()) - throws - { + config: CSVConfiguration = CSVConfiguration()) throws { + try self.init(stream: stream, codecType: UTF8.self, config: config) } - + // MARK: - deprecated - + /// Unavailable. @available(*, unavailable, message: "Use init(stream:config:) instead") public init( stream: InputStream, hasHeaderRow: Bool = defaultHasHeaderRow, trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter) - throws - { - let config = CSVConfiguration(hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter, whitespaces: defaultWhitespaces) + delimiter: UnicodeScalar = defaultDelimiter) throws { + + let config = CSVConfiguration( + hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter + ) try self.init(stream: stream, codecType: UTF8.self, config: config) } - + } extension CSV { @@ -47,27 +50,29 @@ extension CSV { /// - parameter config: CSV configuration. public init( string: String, - config: CSVConfiguration = CSVConfiguration()) - throws - { + config: CSVConfiguration = CSVConfiguration()) throws { + let iterator = string.unicodeScalars.makeIterator() try self.init(iterator: iterator, config: config) } - + // MARK: - deprecated - + /// Unavailable. @available(*, unavailable, message: "Use init(string:config:) instead") public init( string: String, hasHeaderRow: Bool = defaultHasHeaderRow, trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter) - throws - { + delimiter: UnicodeScalar = defaultDelimiter) throws { + let iterator = string.unicodeScalars.makeIterator() - let config = CSVConfiguration(hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter, whitespaces: defaultWhitespaces) + let config = CSVConfiguration( + hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter + ) try self.init(iterator: iterator, config: config) } - + } diff --git a/Sources/CSV+subscript.swift b/Sources/CSV+subscript.swift index 0f68b80..8361815 100755 --- a/Sources/CSV+subscript.swift +++ b/Sources/CSV+subscript.swift @@ -7,12 +7,12 @@ // extension CSV { - + // TODO: Documentation /// No overview available. @available(*, deprecated, message: "Use CSV.Row.subscript(String) instead") public subscript(key: String) -> String? { return currentRow?[key] } - + } diff --git a/Sources/CSV.swift b/Sources/CSV.swift index 5b5b462..335a3a0 100755 --- a/Sources/CSV.swift +++ b/Sources/CSV.swift @@ -37,44 +37,44 @@ public struct CSV { /// No overview available. public struct Row: RandomAccessCollection { - + private let data: [String] private let headerRow: HeaderRow? - + internal init(data: [String], headerRow: HeaderRow?) { self.data = data self.headerRow = headerRow } - + // MARK: - RandomAccessCollection /// No overview available. public var startIndex: Int { return data.startIndex } - + /// No overview available. public var endIndex: Int { return data.endIndex } - + /// No overview available. public func index(before i: Int) -> Int { return data.index(before: i) } - + /// No overview available. public func index(after i: Int) -> Int { return data.index(after: i) } - + /// No overview available. public subscript(index: Int) -> String { return data[index] } // MARK: - Public method - + /// No overview available. public subscript(key: String) -> String? { assert(headerRow != nil, "CSVConfiguration.hasHeaderRow must be true") @@ -83,12 +83,12 @@ public struct CSV { } return data[index] } - + /// No overview available. public func toArray() -> [String] { return data } - + /// No overview available. public func toDictionary() -> [String : String] { assert(headerRow != nil, "CSVConfiguration.hasHeaderRow must be true") @@ -98,29 +98,29 @@ public struct CSV { } return dictionary } - + } - + private var iterator: AnyIterator private let config: CSVConfiguration - + private var back: UnicodeScalar? = nil - + // TODO: deprecated internal var currentRow: Row? = nil - /// CSV header row. To set a value for this property, you set `true` to `hasHeaerRow` in initializer. + /// CSV header row. To set a value for this property, + /// you set `true` to `hasHeaerRow` in initializer. public private(set) var headerRow: HeaderRow? = nil internal init( iterator: T, - config: CSVConfiguration) - throws - where T.Element == UnicodeScalar - { + config: CSVConfiguration + ) throws where T.Element == UnicodeScalar { + self.iterator = AnyIterator(iterator) self.config = config - + if config.hasHeaderRow { guard let headerRow = readRow() else { throw CSVError.cannotReadHeaderRow @@ -136,13 +136,19 @@ public struct CSV { codecType: T.Type, hasHeaderRow: Bool = defaultHasHeaderRow, trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter) - throws - where T.CodeUnit == UInt8 - { + delimiter: UnicodeScalar = defaultDelimiter + ) throws where T.CodeUnit == UInt8 { + let reader = try BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true) - let iterator = UnicodeIterator(input: reader.makeUInt8Iterator(), inputEncodingType: codecType) - let config = CSVConfiguration(hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter, whitespaces: defaultWhitespaces) + let iterator = UnicodeIterator( + input: reader.makeUInt8Iterator(), + inputEncodingType: codecType + ) + let config = CSVConfiguration( + hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter + ) try self.init(iterator: iterator, config: config) } @@ -154,13 +160,19 @@ public struct CSV { endian: Endian = .big, hasHeaderRow: Bool = defaultHasHeaderRow, trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter) - throws - where T.CodeUnit == UInt16 - { + delimiter: UnicodeScalar = defaultDelimiter + ) throws where T.CodeUnit == UInt16 { + let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) - let iterator = UnicodeIterator(input: reader.makeUInt16Iterator(), inputEncodingType: codecType) - let config = CSVConfiguration(hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter, whitespaces: defaultWhitespaces) + let iterator = UnicodeIterator( + input: reader.makeUInt16Iterator(), + inputEncodingType: codecType + ) + let config = CSVConfiguration( + hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter + ) try self.init(iterator: iterator, config: config) } @@ -172,37 +184,47 @@ public struct CSV { endian: Endian = .big, hasHeaderRow: Bool = defaultHasHeaderRow, trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter) - throws - where T.CodeUnit == UInt32 - { + delimiter: UnicodeScalar = defaultDelimiter + ) throws where T.CodeUnit == UInt32 { + let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) - let iterator = UnicodeIterator(input: reader.makeUInt32Iterator(), inputEncodingType: codecType) - let config = CSVConfiguration(hasHeaderRow: hasHeaderRow, trimFields: trimFields, delimiter: delimiter, whitespaces: defaultWhitespaces) + let iterator = UnicodeIterator( + input: reader.makeUInt32Iterator(), + inputEncodingType: codecType + ) + let config = CSVConfiguration( + hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter + ) try self.init(iterator: iterator, config: config) } - + /// Create an instance with `InputStream`. /// - /// - parameter stream: An `InputStream` object. If the stream is not open, initializer opens automatically. + /// - parameter stream: An `InputStream` object. If the stream is not open, + /// initializer opens automatically. /// - parameter codecType: A `UnicodeCodec` type for `stream`. /// - parameter endian: Endian to use when reading a stream. Default: `.big`. /// - parameter config: CSV configuration. public init( stream: InputStream, codecType: T.Type, - config: CSVConfiguration = CSVConfiguration()) - throws - where T.CodeUnit == UInt8 - { + config: CSVConfiguration = CSVConfiguration() + ) throws where T.CodeUnit == UInt8 { + let reader = try BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true) - let iterator = UnicodeIterator(input: reader.makeUInt8Iterator(), inputEncodingType: codecType) + let iterator = UnicodeIterator( + input: reader.makeUInt8Iterator(), + inputEncodingType: codecType + ) try self.init(iterator: iterator, config: config) } - + /// Create an instance with `InputStream`. /// - /// - parameter stream: An `InputStream` object. If the stream is not open, initializer opens automatically. + /// - parameter stream: An `InputStream` object. If the stream is not open, + /// initializer opens automatically. /// - parameter codecType: A `UnicodeCodec` type for `stream`. /// - parameter endian: Endian to use when reading a stream. Default: `.big`. /// - parameter config: CSV configuration. @@ -210,18 +232,21 @@ public struct CSV { stream: InputStream, codecType: T.Type, endian: Endian = .big, - config: CSVConfiguration = CSVConfiguration()) - throws - where T.CodeUnit == UInt16 - { + config: CSVConfiguration = CSVConfiguration() + ) throws where T.CodeUnit == UInt16 { + let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) - let iterator = UnicodeIterator(input: reader.makeUInt16Iterator(), inputEncodingType: codecType) + let iterator = UnicodeIterator( + input: reader.makeUInt16Iterator(), + inputEncodingType: codecType + ) try self.init(iterator: iterator, config: config) } - + /// Create an instance with `InputStream`. /// - /// - parameter stream: An `InputStream` object. If the stream is not open, initializer opens automatically. + /// - parameter stream: An `InputStream` object. If the stream is not open, + /// initializer opens automatically. /// - parameter codecType: A `UnicodeCodec` type for `stream`. /// - parameter endian: Endian to use when reading a stream. Default: `.big`. /// - parameter config: CSV configuration. @@ -229,23 +254,25 @@ public struct CSV { stream: InputStream, codecType: T.Type, endian: Endian = .big, - config: CSVConfiguration = CSVConfiguration()) - throws - where T.CodeUnit == UInt32 - { + config: CSVConfiguration = CSVConfiguration() + ) throws where T.CodeUnit == UInt32 { + let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) - let iterator = UnicodeIterator(input: reader.makeUInt32Iterator(), inputEncodingType: codecType) + let iterator = UnicodeIterator( + input: reader.makeUInt32Iterator(), + inputEncodingType: codecType + ) try self.init(iterator: iterator, config: config) } - + // MARK: - Parse CSV - + fileprivate mutating func readRow() -> [String]? { var next = moveNext() if next == nil { return nil } - + var row = [String]() var field: String var end: Bool @@ -256,17 +283,15 @@ public struct CSV { next = moveNext() } } - + if next == nil { (field, end) = ("", true) - } - else if next == DQUOTE { + } else if next == DQUOTE { (field, end) = readField(quoted: true) - } - else { + } else { back = next (field, end) = readField(quoted: false) - + if config.trimFields { // Trim the trailing spaces field = field.trimmingCharacters(in: config.whitespaces) @@ -281,7 +306,7 @@ public struct CSV { return row } - + private mutating func readField(quoted: Bool) -> (String, Bool) { var field = "" @@ -290,14 +315,14 @@ public struct CSV { if quoted { if c == DQUOTE { var cNext = moveNext() - + if config.trimFields { // Trim the trailing spaces while cNext != nil && config.whitespaces.contains(cNext!) { cNext = moveNext() } } - + if cNext == nil || cNext == CR || cNext == LF { if cNext == CR { let cNextNext = moveNext() @@ -307,25 +332,20 @@ public struct CSV { } // END ROW return (field, true) - } - else if cNext == config.delimiter { + } else if cNext == config.delimiter { // END FIELD return (field, false) - } - else if cNext == DQUOTE { + } else if cNext == DQUOTE { // ESC field.append(String(DQUOTE)) - } - else { + } else { // ERROR? field.append(String(c)) } - } - else { + } else { field.append(String(c)) } - } - else { + } else { if c == CR || c == LF { if c == CR { let cNext = moveNext() @@ -335,23 +355,21 @@ public struct CSV { } // END ROW return (field, true) - } - else if c == config.delimiter { + } else if c == config.delimiter { // END FIELD return (field, false) - } - else { + } else { field.append(String(c)) } } - + next = moveNext() } - + // END FILE return (field, true) } - + private mutating func moveNext() -> UnicodeScalar? { if back != nil { defer { back = nil } diff --git a/Sources/UnicodeIterator.swift b/Sources/UnicodeIterator.swift index 71c330d..a73b759 100755 --- a/Sources/UnicodeIterator.swift +++ b/Sources/UnicodeIterator.swift @@ -11,15 +11,15 @@ internal struct UnicodeIterator< InputEncoding: UnicodeCodec> : IteratorProtocol where InputEncoding.CodeUnit == Input.Element { - + private var input: Input private var inputEncoding: InputEncoding - + internal init(input: Input, inputEncodingType: InputEncoding.Type) { self.input = input self.inputEncoding = inputEncodingType.init() } - + internal mutating func next() -> UnicodeScalar? { switch inputEncoding.decode(&input) { case .scalarValue(let c): return c @@ -27,5 +27,5 @@ internal struct UnicodeIterator< case .error: return nil } } - + } diff --git a/Tests/CSV/CSVTests.swift b/Tests/CSV/CSVTests.swift index 6528d42..0d4d934 100755 --- a/Tests/CSV/CSVTests.swift +++ b/Tests/CSV/CSVTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import CSV class CSVTests: XCTestCase { - + func testOneLine() { let csv = "\"abc\",1,2" var i = 0 @@ -23,7 +23,7 @@ class CSVTests: XCTestCase { } XCTAssertEqual(i, 1) } - + func testTwoLines() { let csv = "\"abc\",1,2\n\"cde\",3,4" var i = 0 @@ -37,7 +37,7 @@ class CSVTests: XCTestCase { } XCTAssertEqual(i, 2) } - + func testLastLineIsEmpty() { let csv = "\"abc\",1,2\n\"cde\",3,4\n" var i = 0 @@ -81,14 +81,14 @@ class CSVTests: XCTestCase { } XCTAssertEqual(i, 3) } - + func testCommaInQuotationMarks() { let csvString = "abab,\"cd,cd\",efef" var csv = try! CSV(string: csvString) let row = csv.next()! XCTAssertEqual(row.toArray(), ["abab", "cd,cd", "efef"]) } - + func testEscapedQuotationMark1() { let csvString = "abab,\"\"\"cdcd\",efef\r\nzxcv,asdf,qwer" var csv = try! CSV(string: csvString) @@ -97,7 +97,7 @@ class CSVTests: XCTestCase { row = csv.next()! XCTAssertEqual(row.toArray(), ["zxcv", "asdf", "qwer"]) } - + func testEscapedQuotationMark2() { let csvString = "abab,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\"" var csv = try! CSV(string: csvString) @@ -106,7 +106,7 @@ class CSVTests: XCTestCase { row = csv.next()! XCTAssertEqual(row.toArray(), ["zxcv", "asdf", "qw\"er"]) } - + func testEmptyField() { let csvString = "abab,,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\"," var csv = try! CSV(string: csvString) @@ -115,7 +115,7 @@ class CSVTests: XCTestCase { row = csv.next()! XCTAssertEqual(row.toArray(), ["zxcv", "asdf", "qw\"er", ""]) } - + func testDoubleQuoteBeforeLineBreak() { let csv = "\"abc\",1,\"2\"\n\n\"cde\",3,\"4\"" var i = 0 @@ -151,13 +151,13 @@ class CSVTests: XCTestCase { } XCTAssertEqual(i, 2) } - + func testCSVState1() { let it = "あ,い1,\"う\",えお\n,,x,".unicodeScalars.makeIterator() var csv = try! CSV(iterator: it, config: CSVConfiguration()) - + var rows = [[String]]() - + while let row = csv.next() { rows.append(row.toArray()) } @@ -186,7 +186,7 @@ class CSVTests: XCTestCase { XCTAssertNil(row["key9"]) } } - + func testToArray() { let csvString = "1,2,3,4,5\n6,7,8,9,0" let csv = try! CSV(string: csvString) @@ -207,5 +207,5 @@ class CSVTests: XCTestCase { XCTAssertEqual(rows[1]["name"], "name2") XCTAssertNil(rows[1]["yyy"]) } - + } diff --git a/Tests/CSV/LineBreakTests.swift b/Tests/CSV/LineBreakTests.swift index 6cf52e3..75aecdc 100644 --- a/Tests/CSV/LineBreakTests.swift +++ b/Tests/CSV/LineBreakTests.swift @@ -64,7 +64,7 @@ class LineBreakTests: XCTestCase { XCTAssertEqual(records[0], ["qwe", "asd"]) XCTAssertEqual(records[1], ["zxc", "rty"]) } - + func testLineBreakCR() { let csv = "qwe,asd\rzxc,rty" let records = parse(csv: csv) @@ -72,7 +72,7 @@ class LineBreakTests: XCTestCase { XCTAssertEqual(records[0], ["qwe", "asd"]) XCTAssertEqual(records[1], ["zxc", "rty"]) } - + func testLineBreakCRLF() { let csv = "qwe,asd\r\nzxc,rty" let records = parse(csv: csv) @@ -80,7 +80,7 @@ class LineBreakTests: XCTestCase { XCTAssertEqual(records[0], ["qwe", "asd"]) XCTAssertEqual(records[1], ["zxc", "rty"]) } - + func testLineBreakLFLF() { let csv = "qwe,asd\n\nzxc,rty" let records = parse(csv: csv) diff --git a/Tests/CSV/ReadmeTests.swift b/Tests/CSV/ReadmeTests.swift index e11517b..793bf1b 100755 --- a/Tests/CSV/ReadmeTests.swift +++ b/Tests/CSV/ReadmeTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import CSV class ReadmeTests: XCTestCase { - + func testFromCSVString() { let csv = try! CSV(string: "1,foo\n2,bar") for row in csv { @@ -19,7 +19,7 @@ class ReadmeTests: XCTestCase { // => ["2", "bar"] } } - + func testFromFile() { // let stream = InputStream(fileAtPath: "/path/to/file.csv")! // let csv = try! CSV(stream: stream) @@ -27,15 +27,15 @@ class ReadmeTests: XCTestCase { // print("\(row)") // } } - + func testGettingTheHeaderRow() { let csvString = "id,name\n1,foo\n2,bar" let config = CSVConfiguration(hasHeaderRow: true) // It must be true. let csv = try! CSV(string: csvString, config: config) - + let headerRow = csv.headerRow! print("\(headerRow)") // => ["id", "name"] - + for row in csv { print("\(row)") // => ["1", "foo"] @@ -46,30 +46,29 @@ class ReadmeTests: XCTestCase { func testGetTheFieldValueUsingIndex() { let csvString = "1,foo" let csv = try! CSV(string: csvString) - + for row in csv { print("\(row[0])") // => "1" print("\(row[1])") // => "foo" } } - func testGetTheFieldValueUsingKey() { let csvString = "id,name\n1,foo" let config = CSVConfiguration(hasHeaderRow: true) // It must be true. let csv = try! CSV(string: csvString, config: config) - + for row in csv { print("\(row["id"]!)") // => "1" print("\(row["name"]!)") // => "foo" } } - + func testProvideTheCharacterEncoding() { // let csv = try! CSV( // stream: InputStream(fileAtPath: "/path/to/file.csv")!, // codecType: UTF16.self, // endian: .big) } - + } diff --git a/Tests/CSV/TrimFieldsTests.swift b/Tests/CSV/TrimFieldsTests.swift index dd7d24c..0e0aa0b 100644 --- a/Tests/CSV/TrimFieldsTests.swift +++ b/Tests/CSV/TrimFieldsTests.swift @@ -19,7 +19,7 @@ class TrimFieldsTests: XCTestCase { XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } - + func testTrimFields2() { let csvString = " abc, def, ghi" let config = CSVConfiguration(trimFields: true) @@ -28,7 +28,7 @@ class TrimFieldsTests: XCTestCase { XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } - + func testTrimFields3() { let csvString = "abc ,def ,ghi " let config = CSVConfiguration(trimFields: true) @@ -37,7 +37,7 @@ class TrimFieldsTests: XCTestCase { XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } - + func testTrimFields4() { let csvString = " abc , def , ghi " let config = CSVConfiguration(trimFields: true) @@ -46,7 +46,7 @@ class TrimFieldsTests: XCTestCase { XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } - + func testTrimFields5() { let csvString = "\"abc\",\"def\",\"ghi\"" let config = CSVConfiguration(trimFields: true) @@ -55,7 +55,7 @@ class TrimFieldsTests: XCTestCase { XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } - + func testTrimFields6() { let csvString = " \"abc\", \"def\", \"ghi\"" let config = CSVConfiguration(trimFields: true) @@ -64,7 +64,7 @@ class TrimFieldsTests: XCTestCase { XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } - + func testTrimFields7() { let csvString = "\"abc\" ,\"def\" ,\"ghi\" " let config = CSVConfiguration(trimFields: true) @@ -73,7 +73,7 @@ class TrimFieldsTests: XCTestCase { XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } - + func testTrimFields8() { let csvString = " \"abc\" , \"def\" , \"ghi\" " let config = CSVConfiguration(trimFields: true) @@ -82,7 +82,7 @@ class TrimFieldsTests: XCTestCase { XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } - + func testTrimFields9() { let csvString = "\" abc \",\" def \",\" ghi \"" let config = CSVConfiguration(trimFields: true) @@ -91,7 +91,7 @@ class TrimFieldsTests: XCTestCase { XCTAssertEqual(row.toArray(), [" abc ", " def ", " ghi "]) } } - + func testTrimFields10() { let csvString = "\tabc,\t\tdef\t,ghi\t" let config = CSVConfiguration(trimFields: true) @@ -100,29 +100,29 @@ class TrimFieldsTests: XCTestCase { XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) } } - + func testTrimFields11() { let csvString = " abc \n def " let config = CSVConfiguration(trimFields: true) var csv = try! CSV(string: csvString, config: config) - + let row1 = csv.next()! XCTAssertEqual(row1.toArray(), ["abc"]) let row2 = csv.next()! XCTAssertEqual(row2.toArray(), ["def"]) } - + func testTrimFields12() { let csvString = " \"abc \" \n \" def\" " let config = CSVConfiguration(trimFields: true) var csv = try! CSV(string: csvString, config: config) - + let row1 = csv.next()! XCTAssertEqual(row1.toArray(), ["abc "]) let row2 = csv.next()! XCTAssertEqual(row2.toArray(), [" def"]) } - + func testTrimFields13() { let csvString = " abc \t\tdef\t ghi " let config = CSVConfiguration(trimFields: true, delimiter: UnicodeScalar("\t")!) @@ -131,5 +131,5 @@ class TrimFieldsTests: XCTestCase { XCTAssertEqual(row.toArray(), ["abc", "", "def", "ghi"]) } } - + } diff --git a/Tests/CSV/UnicodeTests.swift b/Tests/CSV/UnicodeTests.swift index 12bb418..d18938c 100644 --- a/Tests/CSV/UnicodeTests.swift +++ b/Tests/CSV/UnicodeTests.swift @@ -23,7 +23,7 @@ class UnicodeTests: XCTestCase { XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"]) XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""]) } - + func testUTF16WithNativeEndianBOM() { let csvString = "abab,,cdcd,efef\r\nzxcv,😆asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf16 @@ -35,7 +35,7 @@ class UnicodeTests: XCTestCase { XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"]) XCTAssertEqual(records[1], ["zxcv", "😆asdf", "qw\"er", ""]) } - + func testUTF16WithBigEndianBOM() { let csvString = "abab,,cdcd,efef\r\n😆zxcv,asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf16BigEndian @@ -48,7 +48,7 @@ class UnicodeTests: XCTestCase { XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"]) XCTAssertEqual(records[1], ["😆zxcv", "asdf", "qw\"er", ""]) } - + func testUTF16WithLittleEndianBOM() { let csvString = "abab,,cdcd,efef\r\nzxcv😆,asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf16LittleEndian @@ -61,7 +61,7 @@ class UnicodeTests: XCTestCase { XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"]) XCTAssertEqual(records[1], ["zxcv😆", "asdf", "qw\"er", ""]) } - + func testUTF32WithNativeEndianBOM() { let csvString = "😆abab,,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf32 @@ -73,7 +73,7 @@ class UnicodeTests: XCTestCase { XCTAssertEqual(records[0], ["😆abab", "", "cdcd", "efef"]) XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""]) } - + func testUTF32WithBigEndianBOM() { let csvString = "abab,,cd😆cd,efef\r\nzxcv,asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf32BigEndian @@ -86,7 +86,7 @@ class UnicodeTests: XCTestCase { XCTAssertEqual(records[0], ["abab", "", "cd😆cd", "efef"]) XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""]) } - + func testUTF32WithLittleEndianBOM() { let csvString = "abab,,cdcd,ef😆ef\r\nzxcv,asdf,\"qw\"\"er\"," let encoding = String.Encoding.utf32LittleEndian @@ -99,7 +99,7 @@ class UnicodeTests: XCTestCase { XCTAssertEqual(records[0], ["abab", "", "cdcd", "ef😆ef"]) XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""]) } - + private func getRecords(csv: CSV) -> [[String]] { var records = [[String]]() for row in csv { @@ -107,5 +107,5 @@ class UnicodeTests: XCTestCase { } return records } - + } From ac6d66370303ea5fb6bb6f4df252e999254e2608 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 23 Oct 2016 02:20:24 +0900 Subject: [PATCH 05/36] Update comment --- CSVConfiguration.swift | 1 - Sources/CSV.swift | 6 ++---- Sources/CSVError.swift | 1 - Sources/Endian.swift | 1 - 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/CSVConfiguration.swift b/CSVConfiguration.swift index 5fbf379..b89c6f2 100644 --- a/CSVConfiguration.swift +++ b/CSVConfiguration.swift @@ -13,7 +13,6 @@ internal let defaultTrimFields = false internal let defaultDelimiter = UnicodeScalar(UInt8(0x2c)) // "," internal let defaultWhitespaces = CharacterSet.whitespaces -// TODO: Documentation /// No overview available. public struct CSVConfiguration { diff --git a/Sources/CSV.swift b/Sources/CSV.swift index 335a3a0..427e5a3 100755 --- a/Sources/CSV.swift +++ b/Sources/CSV.swift @@ -28,7 +28,6 @@ extension CSV: IteratorProtocol { } -// TODO: Documentation /// No overview available. public struct CSV { @@ -153,7 +152,7 @@ public struct CSV { } /// Unavailable. - @available(*, unavailable, message: "Use init(stream:codecType:config:) instead") + @available(*, unavailable, message: "Use init(stream:codecType:endian:config:) instead") public init( stream: InputStream, codecType: T.Type, @@ -177,7 +176,7 @@ public struct CSV { } /// Unavailable. - @available(*, unavailable, message: "Use init(stream:codecType:config:) instead") + @available(*, unavailable, message: "Use init(stream:codecType:endian:config:) instead") public init( stream: InputStream, codecType: T.Type, @@ -205,7 +204,6 @@ public struct CSV { /// - parameter stream: An `InputStream` object. If the stream is not open, /// initializer opens automatically. /// - parameter codecType: A `UnicodeCodec` type for `stream`. - /// - parameter endian: Endian to use when reading a stream. Default: `.big`. /// - parameter config: CSV configuration. public init( stream: InputStream, diff --git a/Sources/CSVError.swift b/Sources/CSVError.swift index ffb3cfd..20b6c7b 100644 --- a/Sources/CSVError.swift +++ b/Sources/CSVError.swift @@ -6,7 +6,6 @@ // Copyright © 2016 yaslab. All rights reserved. // -// TODO: Documentation /// No overview available. public enum CSVError: Error { diff --git a/Sources/Endian.swift b/Sources/Endian.swift index abbe3a8..1dc96be 100644 --- a/Sources/Endian.swift +++ b/Sources/Endian.swift @@ -6,7 +6,6 @@ // Copyright © 2016 yaslab. All rights reserved. // -// TODO: Documentation /// No overview available. public enum Endian { From 40e34fc990dc0d9763be03bf73a4a23364afbaef Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 23 Oct 2016 02:22:07 +0900 Subject: [PATCH 06/36] Replace magic number with variable --- Sources/BinaryReader.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BinaryReader.swift b/Sources/BinaryReader.swift index 10e5cf2..2c7297d 100755 --- a/Sources/BinaryReader.swift +++ b/Sources/BinaryReader.swift @@ -145,7 +145,7 @@ internal class BinaryReader { if length < 0 { throw CSVError.streamErrorHasOccurred(error: stream.streamError!) } - if length != 4 { + if length != bufferSize { throw CSVError.stringEncodingMismatch } return try UnsafePointer(buffer).withMemoryRebound(to: UInt32.self, capacity: 1) { From 0b0fcd8cbba393de9823accc08f74c390be2dd68 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 23 Oct 2016 02:24:09 +0900 Subject: [PATCH 07/36] CSV subscript to be unavailable --- Sources/CSV+subscript.swift | 7 +++---- Sources/CSV.swift | 32 +++++++++++++------------------- Tests/CSV/CSVTests.swift | 21 --------------------- 3 files changed, 16 insertions(+), 44 deletions(-) diff --git a/Sources/CSV+subscript.swift b/Sources/CSV+subscript.swift index 8361815..b541abf 100755 --- a/Sources/CSV+subscript.swift +++ b/Sources/CSV+subscript.swift @@ -8,11 +8,10 @@ extension CSV { - // TODO: Documentation - /// No overview available. - @available(*, deprecated, message: "Use CSV.Row.subscript(String) instead") + /// Unavailable + @available(*, unavailable, message: "Use CSV.Row.subscript(String) instead") public subscript(key: String) -> String? { - return currentRow?[key] + return nil } } diff --git a/Sources/CSV.swift b/Sources/CSV.swift index 427e5a3..1898617 100755 --- a/Sources/CSV.swift +++ b/Sources/CSV.swift @@ -16,14 +16,12 @@ extension CSV: Sequence { } extension CSV: IteratorProtocol { - // TODO: Documentation /// No overview available. public mutating func next() -> Row? { guard let row = readRow() else { return nil } - currentRow = Row(data: row, headerRow: headerRow) - return currentRow + return Row(data: row, headerRow: headerRow) } } @@ -105,9 +103,6 @@ public struct CSV { private var back: UnicodeScalar? = nil - // TODO: deprecated - internal var currentRow: Row? = nil - /// CSV header row. To set a value for this property, /// you set `true` to `hasHeaerRow` in initializer. public private(set) var headerRow: HeaderRow? = nil @@ -266,8 +261,8 @@ public struct CSV { // MARK: - Parse CSV fileprivate mutating func readRow() -> [String]? { - var next = moveNext() - if next == nil { + var c = moveNext() + if c == nil { return nil } @@ -277,17 +272,17 @@ public struct CSV { while true { if config.trimFields { // Trim the leading spaces - while next != nil && config.whitespaces.contains(next!) { - next = moveNext() + while c != nil && config.whitespaces.contains(c!) { + c = moveNext() } } - if next == nil { + if c == nil { (field, end) = ("", true) - } else if next == DQUOTE { + } else if c == DQUOTE { (field, end) = readField(quoted: true) } else { - back = next + back = c (field, end) = readField(quoted: false) if config.trimFields { @@ -299,7 +294,7 @@ public struct CSV { if end { break } - next = moveNext() + c = moveNext() } return row @@ -308,8 +303,7 @@ public struct CSV { private mutating func readField(quoted: Bool) -> (String, Bool) { var field = "" - var next = moveNext() - while let c = next { + while let c = moveNext() { if quoted { if c == DQUOTE { var cNext = moveNext() @@ -360,8 +354,6 @@ public struct CSV { field.append(String(c)) } } - - next = moveNext() } // END FILE @@ -370,7 +362,9 @@ public struct CSV { private mutating func moveNext() -> UnicodeScalar? { if back != nil { - defer { back = nil } + defer { + back = nil + } return back } return iterator.next() diff --git a/Tests/CSV/CSVTests.swift b/Tests/CSV/CSVTests.swift index 0d4d934..d8b75e5 100755 --- a/Tests/CSV/CSVTests.swift +++ b/Tests/CSV/CSVTests.swift @@ -131,27 +131,6 @@ class CSVTests: XCTestCase { XCTAssertEqual(i, 3) } - func testSubscript() { - let csvString = "id,name\n001,hoge\n002,fuga" - let config = CSVConfiguration(hasHeaderRow: true) - var csv = try! CSV(string: csvString, config: config) - var i = 0 - while csv.next() != nil { - switch i { - case 0: - XCTAssertEqual(csv["id"], "001") - XCTAssertEqual(csv["name"], "hoge") - case 1: - XCTAssertEqual(csv["id"], "002") - XCTAssertEqual(csv["name"], "fuga") - default: - break - } - i += 1 - } - XCTAssertEqual(i, 2) - } - func testCSVState1() { let it = "あ,い1,\"う\",えお\n,,x,".unicodeScalars.makeIterator() var csv = try! CSV(iterator: it, config: CSVConfiguration()) From be8d1fda41eb3abf44283614ab0c988c9c16d24a Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 23 Oct 2016 02:25:48 +0900 Subject: [PATCH 08/36] Add more whitespace tests --- Tests/CSV/TrimFieldsTests.swift | 50 +++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/Tests/CSV/TrimFieldsTests.swift b/Tests/CSV/TrimFieldsTests.swift index 0e0aa0b..f55d2c9 100644 --- a/Tests/CSV/TrimFieldsTests.swift +++ b/Tests/CSV/TrimFieldsTests.swift @@ -132,4 +132,54 @@ class TrimFieldsTests: XCTestCase { } } + func testTrimFields14() { + let csvString = "" + let config = CSVConfiguration(trimFields: true) + let csv = try! CSV(string: csvString, config: config) + let rows = csv.map { $0.toArray() } + + XCTAssertEqual(rows.count, 0) + } + + func testTrimFields15() { + let csvString = " " + let config = CSVConfiguration(trimFields: true) + let csv = try! CSV(string: csvString, config: config) + let rows = csv.map { $0.toArray() } + + XCTAssertEqual(rows.count, 1) + XCTAssertEqual(rows[0], [""]) + } + + func testTrimFields16() { + let csvString = " , " + let config = CSVConfiguration(trimFields: true) + let csv = try! CSV(string: csvString, config: config) + let rows = csv.map { $0.toArray() } + + XCTAssertEqual(rows.count, 1) + XCTAssertEqual(rows[0], ["", ""]) + } + + func testTrimFields17() { + let csvString = " , \n" + let config = CSVConfiguration(trimFields: true) + let csv = try! CSV(string: csvString, config: config) + let rows = csv.map { $0.toArray() } + + XCTAssertEqual(rows.count, 1) + XCTAssertEqual(rows[0], ["", ""]) + } + + func testTrimFields18() { + let csvString = " , \n " + let config = CSVConfiguration(trimFields: true) + let csv = try! CSV(string: csvString, config: config) + let rows = csv.map { $0.toArray() } + + XCTAssertEqual(rows.count, 2) + XCTAssertEqual(rows[0], ["", ""]) + XCTAssertEqual(rows[1], [""]) + } + } From 28c54df85302046dce0984087c0eaeba55bff6b1 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 23 Oct 2016 23:32:09 +0900 Subject: [PATCH 09/36] Code cleanup --- CSV.xcodeproj/project.pbxproj | 20 +-- Sources/CSV+init.swift | 37 ----- Sources/CSV+iterator.swift | 89 +++++++++++ Sources/CSV+subscript.swift | 17 --- Sources/CSV.swift | 272 +++++++++++++++------------------- 5 files changed, 215 insertions(+), 220 deletions(-) create mode 100644 Sources/CSV+iterator.swift delete mode 100755 Sources/CSV+subscript.swift diff --git a/CSV.xcodeproj/project.pbxproj b/CSV.xcodeproj/project.pbxproj index eb8c4f9..9729461 100755 --- a/CSV.xcodeproj/project.pbxproj +++ b/CSV.xcodeproj/project.pbxproj @@ -21,6 +21,10 @@ 0E47EEBA1DBB268000EBF783 /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */; }; 0E47EEBB1DBB268000EBF783 /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */; }; 0E47EEBC1DBB268000EBF783 /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */; }; + 0E47EEC11DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; + 0E47EEC21DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; + 0E47EEC31DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; + 0E47EEC41DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; 0E7E8C8C1D0BC7BB0057A1C1 /* CSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E7E8C811D0BC7BB0057A1C1 /* CSV.framework */; }; 0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; }; 0E7E8CA21D0BC7F10057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; }; @@ -46,10 +50,6 @@ 0E9317D51D0DB2F200AC20A0 /* CSV+init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */; }; 0E9317D61D0DB2F200AC20A0 /* CSV+init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */; }; 0E9317D71D0DB2F200AC20A0 /* CSV+init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */; }; - 0E9317D91D0DB30800AC20A0 /* CSV+subscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D81D0DB30800AC20A0 /* CSV+subscript.swift */; }; - 0E9317DA1D0DB30800AC20A0 /* CSV+subscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D81D0DB30800AC20A0 /* CSV+subscript.swift */; }; - 0E9317DB1D0DB30800AC20A0 /* CSV+subscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D81D0DB30800AC20A0 /* CSV+subscript.swift */; }; - 0E9317DC1D0DB30800AC20A0 /* CSV+subscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D81D0DB30800AC20A0 /* CSV+subscript.swift */; }; 0E9317DE1D0DBCC500AC20A0 /* ReadmeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317DD1D0DBCC500AC20A0 /* ReadmeTests.swift */; }; 0E9317DF1D0DBCC500AC20A0 /* ReadmeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317DD1D0DBCC500AC20A0 /* ReadmeTests.swift */; }; 0E9317E01D0DBCC500AC20A0 /* ReadmeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317DD1D0DBCC500AC20A0 /* ReadmeTests.swift */; }; @@ -92,6 +92,7 @@ 0E3CE36B1DB5281E00FA45CF /* UnicodeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeTests.swift; sourceTree = ""; }; 0E3CE36F1DB529D700FA45CF /* TrimFieldsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrimFieldsTests.swift; sourceTree = ""; }; 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVConfiguration.swift; sourceTree = SOURCE_ROOT; }; + 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSV+iterator.swift"; sourceTree = ""; }; 0E7E8C811D0BC7BB0057A1C1 /* CSV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CSV.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8C8B1D0BC7BB0057A1C1 /* CSVTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CSVTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSV.swift; sourceTree = ""; }; @@ -107,7 +108,6 @@ 0E7E8CE81D0BCD0B0057A1C1 /* CSV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CSV.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8CF11D0BCD0B0057A1C1 /* CSVTests-tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CSVTests-tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSV+init.swift"; sourceTree = ""; }; - 0E9317D81D0DB30800AC20A0 /* CSV+subscript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSV+subscript.swift"; sourceTree = ""; }; 0E9317DD1D0DBCC500AC20A0 /* ReadmeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadmeTests.swift; sourceTree = ""; }; 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinaryReader.swift; sourceTree = ""; }; 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeIterator.swift; sourceTree = ""; }; @@ -199,7 +199,7 @@ 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */, 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */, 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */, - 0E9317D81D0DB30800AC20A0 /* CSV+subscript.swift */, + 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */, 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */, 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */, 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */, @@ -535,7 +535,7 @@ files = ( 0E9317D51D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB821D183BA9003EC967 /* UnicodeIterator.swift in Sources */, - 0E9317DA1D0DB30800AC20A0 /* CSV+subscript.swift in Sources */, + 0E47EEC21DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, 0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */, 0E0F160F1D197DB800C92580 /* Endian.swift in Sources */, 0E47EEBA1DBB268000EBF783 /* CSVConfiguration.swift in Sources */, @@ -562,7 +562,7 @@ files = ( 0E9317D71D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB841D183BA9003EC967 /* UnicodeIterator.swift in Sources */, - 0E9317DC1D0DB30800AC20A0 /* CSV+subscript.swift in Sources */, + 0E47EEC41DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, 0E7E8CBE1D0BC9D70057A1C1 /* CSV.swift in Sources */, 0E0F16111D197DB800C92580 /* Endian.swift in Sources */, 0E47EEBC1DBB268000EBF783 /* CSVConfiguration.swift in Sources */, @@ -577,7 +577,7 @@ files = ( 0E9317D41D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB811D183BA9003EC967 /* UnicodeIterator.swift in Sources */, - 0E9317D91D0DB30800AC20A0 /* CSV+subscript.swift in Sources */, + 0E47EEC11DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, 0E7E8CE01D0BCA8E0057A1C1 /* CSV.swift in Sources */, 0E0F160E1D197DB800C92580 /* Endian.swift in Sources */, 0E47EEB91DBB268000EBF783 /* CSVConfiguration.swift in Sources */, @@ -604,7 +604,7 @@ files = ( 0E9317D61D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB831D183BA9003EC967 /* UnicodeIterator.swift in Sources */, - 0E9317DB1D0DB30800AC20A0 /* CSV+subscript.swift in Sources */, + 0E47EEC31DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, 0E7E8D001D0BCDCF0057A1C1 /* CSV.swift in Sources */, 0E0F16101D197DB800C92580 /* Endian.swift in Sources */, 0E47EEBB1DBB268000EBF783 /* CSVConfiguration.swift in Sources */, diff --git a/Sources/CSV+init.swift b/Sources/CSV+init.swift index a0a5f03..1772320 100755 --- a/Sources/CSV+init.swift +++ b/Sources/CSV+init.swift @@ -22,24 +22,6 @@ extension CSV { try self.init(stream: stream, codecType: UTF8.self, config: config) } - // MARK: - deprecated - - /// Unavailable. - @available(*, unavailable, message: "Use init(stream:config:) instead") - public init( - stream: InputStream, - hasHeaderRow: Bool = defaultHasHeaderRow, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter) throws { - - let config = CSVConfiguration( - hasHeaderRow: hasHeaderRow, - trimFields: trimFields, - delimiter: delimiter - ) - try self.init(stream: stream, codecType: UTF8.self, config: config) - } - } extension CSV { @@ -56,23 +38,4 @@ extension CSV { try self.init(iterator: iterator, config: config) } - // MARK: - deprecated - - /// Unavailable. - @available(*, unavailable, message: "Use init(string:config:) instead") - public init( - string: String, - hasHeaderRow: Bool = defaultHasHeaderRow, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter) throws { - - let iterator = string.unicodeScalars.makeIterator() - let config = CSVConfiguration( - hasHeaderRow: hasHeaderRow, - trimFields: trimFields, - delimiter: delimiter - ) - try self.init(iterator: iterator, config: config) - } - } diff --git a/Sources/CSV+iterator.swift b/Sources/CSV+iterator.swift new file mode 100644 index 0000000..6bd89ce --- /dev/null +++ b/Sources/CSV+iterator.swift @@ -0,0 +1,89 @@ +// +// CSV+iterator.swift +// CSV +// +// Created by Yasuhiro Hatta on 2016/10/23. +// Copyright © 2016年 yaslab. All rights reserved. +// + +extension CSV: IteratorProtocol, Sequence { + + /// No overview available. + public mutating func next() -> Row? { + guard let row = readRow() else { + return nil + } + return Row(data: row, headerRow: headerRow) + } + +} + +extension CSV { + + /// No overview available. + public struct Row: RandomAccessCollection { + + private let data: [String] + private let headerRow: [String]? + + internal init(data: [String], headerRow: [String]?) { + self.data = data + self.headerRow = headerRow + } + + // MARK: - RandomAccessCollection + + /// No overview available. + public var startIndex: Int { + return data.startIndex + } + + /// No overview available. + public var endIndex: Int { + return data.endIndex + } + + /// No overview available. + public func index(before i: Int) -> Int { + return data.index(before: i) + } + + /// No overview available. + public func index(after i: Int) -> Int { + return data.index(after: i) + } + + /// No overview available. + public subscript(index: Int) -> String { + return data[index] + } + + // MARK: - Public method + + /// No overview available. + public subscript(key: String) -> String? { + assert(headerRow != nil, "CSVConfiguration.hasHeaderRow must be true") + guard let index = headerRow!.index(of: key) else { + return nil + } + return data[index] + } + + /// No overview available. + public func toArray() -> [String] { + return data + } + + /// No overview available. + public func toDictionary() -> [String : String] { + assert(headerRow != nil, "CSVConfiguration.hasHeaderRow must be true") + var dictionary: [String : String] = [:] + for (key, value) in zip(headerRow!, data) { + dictionary[key] = value + } + return dictionary + } + + } + +} diff --git a/Sources/CSV+subscript.swift b/Sources/CSV+subscript.swift deleted file mode 100755 index b541abf..0000000 --- a/Sources/CSV+subscript.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// CSV+subscript.swift -// CSV -// -// Created by Yasuhiro Hatta on 2016/06/13. -// Copyright © 2016 yaslab. All rights reserved. -// - -extension CSV { - - /// Unavailable - @available(*, unavailable, message: "Use CSV.Row.subscript(String) instead") - public subscript(key: String) -> String? { - return nil - } - -} diff --git a/Sources/CSV.swift b/Sources/CSV.swift index 1898617..b2ebc31 100755 --- a/Sources/CSV.swift +++ b/Sources/CSV.swift @@ -12,92 +12,9 @@ private let LF = UnicodeScalar(UInt8(0x0a)) // "\n" private let CR = UnicodeScalar(UInt8(0x0d)) // "\r" private let DQUOTE = UnicodeScalar(UInt8(0x22)) // "\"" -extension CSV: Sequence { } - -extension CSV: IteratorProtocol { - - /// No overview available. - public mutating func next() -> Row? { - guard let row = readRow() else { - return nil - } - return Row(data: row, headerRow: headerRow) - } - -} - /// No overview available. public struct CSV { - /// No overview available. - public typealias HeaderRow = [String] - - /// No overview available. - public struct Row: RandomAccessCollection { - - private let data: [String] - private let headerRow: HeaderRow? - - internal init(data: [String], headerRow: HeaderRow?) { - self.data = data - self.headerRow = headerRow - } - - // MARK: - RandomAccessCollection - - /// No overview available. - public var startIndex: Int { - return data.startIndex - } - - /// No overview available. - public var endIndex: Int { - return data.endIndex - } - - /// No overview available. - public func index(before i: Int) -> Int { - return data.index(before: i) - } - - /// No overview available. - public func index(after i: Int) -> Int { - return data.index(after: i) - } - - /// No overview available. - public subscript(index: Int) -> String { - return data[index] - } - - // MARK: - Public method - - /// No overview available. - public subscript(key: String) -> String? { - assert(headerRow != nil, "CSVConfiguration.hasHeaderRow must be true") - guard let index = headerRow!.index(of: key) else { - return nil - } - return data[index] - } - - /// No overview available. - public func toArray() -> [String] { - return data - } - - /// No overview available. - public func toDictionary() -> [String : String] { - assert(headerRow != nil, "CSVConfiguration.hasHeaderRow must be true") - var dictionary: [String : String] = [:] - for (key, value) in zip(headerRow!, data) { - dictionary[key] = value - } - return dictionary - } - - } - private var iterator: AnyIterator private let config: CSVConfiguration @@ -105,7 +22,7 @@ public struct CSV { /// CSV header row. To set a value for this property, /// you set `true` to `hasHeaerRow` in initializer. - public private(set) var headerRow: HeaderRow? = nil + public private(set) var headerRow: [String]? = nil internal init( iterator: T, @@ -123,77 +40,6 @@ public struct CSV { } } - /// Unavailable. - @available(*, unavailable, message: "Use init(stream:codecType:config:) instead") - public init( - stream: InputStream, - codecType: T.Type, - hasHeaderRow: Bool = defaultHasHeaderRow, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter - ) throws where T.CodeUnit == UInt8 { - - let reader = try BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true) - let iterator = UnicodeIterator( - input: reader.makeUInt8Iterator(), - inputEncodingType: codecType - ) - let config = CSVConfiguration( - hasHeaderRow: hasHeaderRow, - trimFields: trimFields, - delimiter: delimiter - ) - try self.init(iterator: iterator, config: config) - } - - /// Unavailable. - @available(*, unavailable, message: "Use init(stream:codecType:endian:config:) instead") - public init( - stream: InputStream, - codecType: T.Type, - endian: Endian = .big, - hasHeaderRow: Bool = defaultHasHeaderRow, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter - ) throws where T.CodeUnit == UInt16 { - - let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) - let iterator = UnicodeIterator( - input: reader.makeUInt16Iterator(), - inputEncodingType: codecType - ) - let config = CSVConfiguration( - hasHeaderRow: hasHeaderRow, - trimFields: trimFields, - delimiter: delimiter - ) - try self.init(iterator: iterator, config: config) - } - - /// Unavailable. - @available(*, unavailable, message: "Use init(stream:codecType:endian:config:) instead") - public init( - stream: InputStream, - codecType: T.Type, - endian: Endian = .big, - hasHeaderRow: Bool = defaultHasHeaderRow, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter - ) throws where T.CodeUnit == UInt32 { - - let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) - let iterator = UnicodeIterator( - input: reader.makeUInt32Iterator(), - inputEncodingType: codecType - ) - let config = CSVConfiguration( - hasHeaderRow: hasHeaderRow, - trimFields: trimFields, - delimiter: delimiter - ) - try self.init(iterator: iterator, config: config) - } - /// Create an instance with `InputStream`. /// /// - parameter stream: An `InputStream` object. If the stream is not open, @@ -260,7 +106,7 @@ public struct CSV { // MARK: - Parse CSV - fileprivate mutating func readRow() -> [String]? { + internal mutating func readRow() -> [String]? { var c = moveNext() if c == nil { return nil @@ -371,3 +217,117 @@ public struct CSV { } } + +extension CSV { + + /// Unavailable. + @available(*, unavailable, message: "Use init(stream:codecType:config:) instead") + public init( + stream: InputStream, + codecType: T.Type, + hasHeaderRow: Bool = defaultHasHeaderRow, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter + ) throws where T.CodeUnit == UInt8 { + + let reader = try BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true) + let iterator = UnicodeIterator( + input: reader.makeUInt8Iterator(), + inputEncodingType: codecType + ) + let config = CSVConfiguration( + hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter + ) + try self.init(iterator: iterator, config: config) + } + + /// Unavailable. + @available(*, unavailable, message: "Use init(stream:codecType:endian:config:) instead") + public init( + stream: InputStream, + codecType: T.Type, + endian: Endian = .big, + hasHeaderRow: Bool = defaultHasHeaderRow, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter + ) throws where T.CodeUnit == UInt16 { + + let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) + let iterator = UnicodeIterator( + input: reader.makeUInt16Iterator(), + inputEncodingType: codecType + ) + let config = CSVConfiguration( + hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter + ) + try self.init(iterator: iterator, config: config) + } + + /// Unavailable. + @available(*, unavailable, message: "Use init(stream:codecType:endian:config:) instead") + public init( + stream: InputStream, + codecType: T.Type, + endian: Endian = .big, + hasHeaderRow: Bool = defaultHasHeaderRow, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter + ) throws where T.CodeUnit == UInt32 { + + let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) + let iterator = UnicodeIterator( + input: reader.makeUInt32Iterator(), + inputEncodingType: codecType + ) + let config = CSVConfiguration( + hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter + ) + try self.init(iterator: iterator, config: config) + } + + /// Unavailable. + @available(*, unavailable, message: "Use init(stream:config:) instead") + public init( + stream: InputStream, + hasHeaderRow: Bool = defaultHasHeaderRow, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter) throws { + + let config = CSVConfiguration( + hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter + ) + try self.init(stream: stream, codecType: UTF8.self, config: config) + } + + /// Unavailable. + @available(*, unavailable, message: "Use init(string:config:) instead") + public init( + string: String, + hasHeaderRow: Bool = defaultHasHeaderRow, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter) throws { + + let iterator = string.unicodeScalars.makeIterator() + let config = CSVConfiguration( + hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter + ) + try self.init(iterator: iterator, config: config) + } + + /// Unavailable + @available(*, unavailable, message: "Use CSV.Row.subscript(String) instead") + public subscript(key: String) -> String? { + return nil + } + +} From 597272db9b0816fef8bfed2f690c293d3bdb78fd Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Mon, 24 Oct 2016 01:22:23 +0900 Subject: [PATCH 10/36] Error handling #10 --- CSVConfiguration.swift | 2 ++ Sources/BinaryReader.swift | 18 ++++++---- Sources/CSV+init.swift | 4 +-- Sources/CSV+iterator.swift | 2 +- Sources/CSV.swift | 64 +++++++++++++++++++++-------------- Sources/CSVError.swift | 2 ++ Sources/UnicodeIterator.swift | 15 +++++--- 7 files changed, 67 insertions(+), 40 deletions(-) diff --git a/CSVConfiguration.swift b/CSVConfiguration.swift index b89c6f2..995061f 100644 --- a/CSVConfiguration.swift +++ b/CSVConfiguration.swift @@ -16,6 +16,8 @@ internal let defaultWhitespaces = CharacterSet.whitespaces /// No overview available. public struct CSVConfiguration { + public var errorHandler: ((Error, Int, Int) -> Void)? = nil + /// `true` if the CSV has a header row, otherwise `false`. Default: `false`. public let hasHeaderRow: Bool /// No overview available. diff --git a/Sources/BinaryReader.swift b/Sources/BinaryReader.swift index 2c7297d..4b24b6e 100755 --- a/Sources/BinaryReader.swift +++ b/Sources/BinaryReader.swift @@ -164,21 +164,23 @@ internal class BinaryReader { extension BinaryReader { - internal struct UInt8Iterator: Sequence, IteratorProtocol { + internal class UInt8Iterator: Sequence, IteratorProtocol { private let reader: BinaryReader + internal var errorHandler: ((Error) -> Void)? = nil fileprivate init(reader: BinaryReader) { self.reader = reader } - internal mutating func next() -> UInt8? { + internal func next() -> UInt8? { if !reader.hasBytesAvailable { return nil } do { return try reader.readUInt8() } catch { + errorHandler?(error) return nil } } @@ -193,21 +195,23 @@ extension BinaryReader { extension BinaryReader { - internal struct UInt16Iterator: Sequence, IteratorProtocol { + internal class UInt16Iterator: Sequence, IteratorProtocol { private let reader: BinaryReader + internal var errorHandler: ((Error) -> Void)? = nil fileprivate init(reader: BinaryReader) { self.reader = reader } - internal mutating func next() -> UInt16? { + internal func next() -> UInt16? { if !reader.hasBytesAvailable { return nil } do { return try reader.readUInt16() } catch { + errorHandler?(error) return nil } } @@ -222,21 +226,23 @@ extension BinaryReader { extension BinaryReader { - internal struct UInt32Iterator: Sequence, IteratorProtocol { + internal class UInt32Iterator: Sequence, IteratorProtocol { private let reader: BinaryReader + internal var errorHandler: ((Error) -> Void)? = nil fileprivate init(reader: BinaryReader) { self.reader = reader } - internal mutating func next() -> UInt32? { + internal func next() -> UInt32? { if !reader.hasBytesAvailable { return nil } do { return try reader.readUInt32() } catch { + errorHandler?(error) return nil } } diff --git a/Sources/CSV+init.swift b/Sources/CSV+init.swift index 1772320..261b793 100755 --- a/Sources/CSV+init.swift +++ b/Sources/CSV+init.swift @@ -15,7 +15,7 @@ extension CSV { /// - parameter stream: An `InputStream` object. If the stream is not open, /// initializer opens automatically. /// - parameter config: CSV configuration. - public init( + public convenience init( stream: InputStream, config: CSVConfiguration = CSVConfiguration()) throws { @@ -30,7 +30,7 @@ extension CSV { /// /// - parameter string: An CSV string. /// - parameter config: CSV configuration. - public init( + public convenience init( string: String, config: CSVConfiguration = CSVConfiguration()) throws { diff --git a/Sources/CSV+iterator.swift b/Sources/CSV+iterator.swift index 6bd89ce..9676135 100644 --- a/Sources/CSV+iterator.swift +++ b/Sources/CSV+iterator.swift @@ -9,7 +9,7 @@ extension CSV: IteratorProtocol, Sequence { /// No overview available. - public mutating func next() -> Row? { + public func next() -> Row? { guard let row = readRow() else { return nil } diff --git a/Sources/CSV.swift b/Sources/CSV.swift index b2ebc31..be7dfa7 100755 --- a/Sources/CSV.swift +++ b/Sources/CSV.swift @@ -13,13 +13,16 @@ private let CR = UnicodeScalar(UInt8(0x0d)) // "\r" private let DQUOTE = UnicodeScalar(UInt8(0x22)) // "\"" /// No overview available. -public struct CSV { +public class CSV { private var iterator: AnyIterator private let config: CSVConfiguration private var back: UnicodeScalar? = nil + private var currentRowIndex: Int = 0 + private var currentFieldIndex: Int = 0 + /// CSV header row. To set a value for this property, /// you set `true` to `hasHeaerRow` in initializer. public private(set) var headerRow: [String]? = nil @@ -46,18 +49,18 @@ public struct CSV { /// initializer opens automatically. /// - parameter codecType: A `UnicodeCodec` type for `stream`. /// - parameter config: CSV configuration. - public init( + public convenience init( stream: InputStream, codecType: T.Type, config: CSVConfiguration = CSVConfiguration() ) throws where T.CodeUnit == UInt8 { let reader = try BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true) - let iterator = UnicodeIterator( - input: reader.makeUInt8Iterator(), - inputEncodingType: codecType - ) + let input = reader.makeUInt8Iterator() + let iterator = UnicodeIterator(input: input, inputEncodingType: codecType) try self.init(iterator: iterator, config: config) + input.errorHandler = self.errorHandler + iterator.errorHandler = self.errorHandler } /// Create an instance with `InputStream`. @@ -67,7 +70,7 @@ public struct CSV { /// - parameter codecType: A `UnicodeCodec` type for `stream`. /// - parameter endian: Endian to use when reading a stream. Default: `.big`. /// - parameter config: CSV configuration. - public init( + public convenience init( stream: InputStream, codecType: T.Type, endian: Endian = .big, @@ -75,11 +78,11 @@ public struct CSV { ) throws where T.CodeUnit == UInt16 { let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) - let iterator = UnicodeIterator( - input: reader.makeUInt16Iterator(), - inputEncodingType: codecType - ) + let input = reader.makeUInt16Iterator() + let iterator = UnicodeIterator(input: input, inputEncodingType: codecType) try self.init(iterator: iterator, config: config) + input.errorHandler = self.errorHandler + iterator.errorHandler = self.errorHandler } /// Create an instance with `InputStream`. @@ -89,7 +92,7 @@ public struct CSV { /// - parameter codecType: A `UnicodeCodec` type for `stream`. /// - parameter endian: Endian to use when reading a stream. Default: `.big`. /// - parameter config: CSV configuration. - public init( + public convenience init( stream: InputStream, codecType: T.Type, endian: Endian = .big, @@ -97,16 +100,18 @@ public struct CSV { ) throws where T.CodeUnit == UInt32 { let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) - let iterator = UnicodeIterator( - input: reader.makeUInt32Iterator(), - inputEncodingType: codecType - ) + let input = reader.makeUInt32Iterator() + let iterator = UnicodeIterator(input: input, inputEncodingType: codecType) try self.init(iterator: iterator, config: config) + input.errorHandler = self.errorHandler + iterator.errorHandler = self.errorHandler } // MARK: - Parse CSV - internal mutating func readRow() -> [String]? { + internal func readRow() -> [String]? { + currentFieldIndex = 0 + var c = moveNext() if c == nil { return nil @@ -140,13 +145,18 @@ public struct CSV { if end { break } + + currentFieldIndex += 1 + c = moveNext() } + currentRowIndex += 1 + return row } - private mutating func readField(quoted: Bool) -> (String, Bool) { + private func readField(quoted: Bool) -> (String, Bool) { var field = "" while let c = moveNext() { @@ -206,7 +216,7 @@ public struct CSV { return (field, true) } - private mutating func moveNext() -> UnicodeScalar? { + private func moveNext() -> UnicodeScalar? { if back != nil { defer { back = nil @@ -216,13 +226,15 @@ public struct CSV { return iterator.next() } -} + private func errorHandler(error: Error) { + config.errorHandler?(error, currentRowIndex, currentFieldIndex) + } -extension CSV { + // MARK: - deprecated /// Unavailable. @available(*, unavailable, message: "Use init(stream:codecType:config:) instead") - public init( + public convenience init( stream: InputStream, codecType: T.Type, hasHeaderRow: Bool = defaultHasHeaderRow, @@ -245,7 +257,7 @@ extension CSV { /// Unavailable. @available(*, unavailable, message: "Use init(stream:codecType:endian:config:) instead") - public init( + public convenience init( stream: InputStream, codecType: T.Type, endian: Endian = .big, @@ -269,7 +281,7 @@ extension CSV { /// Unavailable. @available(*, unavailable, message: "Use init(stream:codecType:endian:config:) instead") - public init( + public convenience init( stream: InputStream, codecType: T.Type, endian: Endian = .big, @@ -293,7 +305,7 @@ extension CSV { /// Unavailable. @available(*, unavailable, message: "Use init(stream:config:) instead") - public init( + public convenience init( stream: InputStream, hasHeaderRow: Bool = defaultHasHeaderRow, trimFields: Bool = defaultTrimFields, @@ -309,7 +321,7 @@ extension CSV { /// Unavailable. @available(*, unavailable, message: "Use init(string:config:) instead") - public init( + public convenience init( string: String, hasHeaderRow: Bool = defaultHasHeaderRow, trimFields: Bool = defaultTrimFields, diff --git a/Sources/CSVError.swift b/Sources/CSVError.swift index 20b6c7b..ec9db62 100644 --- a/Sources/CSVError.swift +++ b/Sources/CSVError.swift @@ -16,6 +16,8 @@ public enum CSVError: Error { /// No overview available. case streamErrorHasOccurred(error: Error) /// No overview available. + case unicodeDecoding + /// No overview available. case cannotReadHeaderRow /// No overview available. case stringEncodingMismatch diff --git a/Sources/UnicodeIterator.swift b/Sources/UnicodeIterator.swift index a73b759..d9be5ed 100755 --- a/Sources/UnicodeIterator.swift +++ b/Sources/UnicodeIterator.swift @@ -6,7 +6,7 @@ // Copyright © 2016 yaslab. All rights reserved. // -internal struct UnicodeIterator< +internal class UnicodeIterator< Input: IteratorProtocol, InputEncoding: UnicodeCodec> : IteratorProtocol @@ -14,17 +14,22 @@ internal struct UnicodeIterator< private var input: Input private var inputEncoding: InputEncoding + internal var errorHandler: ((Error) -> Void)? = nil internal init(input: Input, inputEncodingType: InputEncoding.Type) { self.input = input self.inputEncoding = inputEncodingType.init() } - internal mutating func next() -> UnicodeScalar? { + internal func next() -> UnicodeScalar? { switch inputEncoding.decode(&input) { - case .scalarValue(let c): return c - case .emptyInput: return nil - case .error: return nil + case .scalarValue(let c): + return c + case .emptyInput: + return nil + case .error: + errorHandler?(CSVError.unicodeDecoding) + return nil } } From bdefb44a91d4f386b573a5af185fb91251aa2d2f Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 30 Oct 2016 17:38:25 +0900 Subject: [PATCH 11/36] Fix subscript --- Sources/CSV+iterator.swift | 7 ++++++- Tests/CSV/CSVTests.swift | 28 ++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/Sources/CSV+iterator.swift b/Sources/CSV+iterator.swift index 9676135..e7981f2 100644 --- a/Sources/CSV+iterator.swift +++ b/Sources/CSV+iterator.swift @@ -66,6 +66,9 @@ extension CSV { guard let index = headerRow!.index(of: key) else { return nil } + guard (data.startIndex ..< data.endIndex).contains(index) else { + return nil + } return data[index] } @@ -79,7 +82,9 @@ extension CSV { assert(headerRow != nil, "CSVConfiguration.hasHeaderRow must be true") var dictionary: [String : String] = [:] for (key, value) in zip(headerRow!, data) { - dictionary[key] = value + if !dictionary.keys.contains(key) { + dictionary[key] = value + } } return dictionary } diff --git a/Tests/CSV/CSVTests.swift b/Tests/CSV/CSVTests.swift index d8b75e5..c79c1f6 100755 --- a/Tests/CSV/CSVTests.swift +++ b/Tests/CSV/CSVTests.swift @@ -155,7 +155,7 @@ class CSVTests: XCTestCase { } } - func testSubscriptString() { + func testSubscriptString1() { let csvString = "key1,key2\nvalue1,value2" let config = CSVConfiguration(hasHeaderRow: true) let csv = try! CSV(string: csvString, config: config) @@ -166,6 +166,17 @@ class CSVTests: XCTestCase { } } + func testSubscriptString2() { + let csvString = "key1,key2\nvalue1" + let config = CSVConfiguration(hasHeaderRow: true) + let csv = try! CSV(string: csvString, config: config) + for row in csv { + XCTAssertEqual(row["key1"], "value1") + XCTAssertNil(row["key2"]) + XCTAssertNil(row["key9"]) + } + } + func testToArray() { let csvString = "1,2,3,4,5\n6,7,8,9,0" let csv = try! CSV(string: csvString) @@ -174,7 +185,7 @@ class CSVTests: XCTestCase { XCTAssertEqual(rows[1], ["6", "7", "8", "9", "0"]) } - func testToDictionary() { + func testToDictionary1() { let csvString = "id,name\n1,name1\n2,name2" let config = CSVConfiguration(hasHeaderRow: true) let csv = try! CSV(string: csvString, config: config) @@ -187,4 +198,17 @@ class CSVTests: XCTestCase { XCTAssertNil(rows[1]["yyy"]) } + func testToDictionary2() { + let csvString = "id,name,id\n1,name1,11\n2,name2,22" + let config = CSVConfiguration(hasHeaderRow: true) + let csv = try! CSV(string: csvString, config: config) + let rows = csv.map { $0.toDictionary() } + XCTAssertEqual(rows[0]["id"], "1") + XCTAssertEqual(rows[0]["name"], "name1") + XCTAssertNil(rows[0]["xxx"]) + XCTAssertEqual(rows[1]["id"], "2") + XCTAssertEqual(rows[1]["name"], "name2") + XCTAssertNil(rows[1]["yyy"]) + } + } From c6cdbd1bd8669d06b84d624e13de1f7991b1008e Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Fri, 4 Nov 2016 19:56:59 +0900 Subject: [PATCH 12/36] Use `String.UnicodeScalarView` instead of `String` --- Sources/CSV.swift | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Sources/CSV.swift b/Sources/CSV.swift index be7dfa7..759a876 100755 --- a/Sources/CSV.swift +++ b/Sources/CSV.swift @@ -19,6 +19,7 @@ public class CSV { private let config: CSVConfiguration private var back: UnicodeScalar? = nil + private var fieldBuffer = String.UnicodeScalarView() private var currentRowIndex: Int = 0 private var currentFieldIndex: Int = 0 @@ -157,7 +158,7 @@ public class CSV { } private func readField(quoted: Bool) -> (String, Bool) { - var field = "" + fieldBuffer.removeAll(keepingCapacity: true) while let c = moveNext() { if quoted { @@ -179,19 +180,19 @@ public class CSV { } } // END ROW - return (field, true) + return (String(fieldBuffer), true) } else if cNext == config.delimiter { // END FIELD - return (field, false) + return (String(fieldBuffer), false) } else if cNext == DQUOTE { // ESC - field.append(String(DQUOTE)) + fieldBuffer.append(DQUOTE) } else { // ERROR? - field.append(String(c)) + fieldBuffer.append(c) } } else { - field.append(String(c)) + fieldBuffer.append(c) } } else { if c == CR || c == LF { @@ -202,18 +203,18 @@ public class CSV { } } // END ROW - return (field, true) + return (String(fieldBuffer), true) } else if c == config.delimiter { // END FIELD - return (field, false) + return (String(fieldBuffer), false) } else { - field.append(String(c)) + fieldBuffer.append(c) } } } // END FILE - return (field, true) + return (String(fieldBuffer), true) } private func moveNext() -> UnicodeScalar? { From 485738a95c5b812a05cde9b9080cf4a9ccd91c98 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Fri, 4 Nov 2016 20:05:40 +0900 Subject: [PATCH 13/36] Use `malloc` / `free` instead of `Array` --- Sources/BinaryReader.swift | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Sources/BinaryReader.swift b/Sources/BinaryReader.swift index 4b24b6e..4813247 100755 --- a/Sources/BinaryReader.swift +++ b/Sources/BinaryReader.swift @@ -45,9 +45,9 @@ internal class BinaryReader { private let endian: Endian private let closeOnDeinit: Bool - private var buffer = [UInt8](repeating: 0, count: 4) + private var buffer = malloc(4).assumingMemoryBound(to: UInt8.self) - private var tempBuffer = [UInt8](repeating: 0, count: 4) + private var tempBuffer = malloc(4).assumingMemoryBound(to: UInt8.self) private let tempBufferSize = 4 private var tempBufferOffset = 0 @@ -65,8 +65,8 @@ internal class BinaryReader { throw CSVError.cannotOpenFile } - let readCount = stream.read(&tempBuffer, maxLength: tempBufferSize) - if let (e, l) = readBOM(buffer: &tempBuffer, length: readCount) { + let readCount = stream.read(tempBuffer, maxLength: tempBufferSize) + if let (e, l) = readBOM(buffer: tempBuffer, length: readCount) { if endian != .unknown && endian != e { throw CSVError.stringEndianMismatch } @@ -83,6 +83,8 @@ internal class BinaryReader { if closeOnDeinit && stream.streamStatus != .closed { stream.close() } + free(buffer) + free(tempBuffer) } internal var hasBytesAvailable: Bool { @@ -108,7 +110,7 @@ internal class BinaryReader { internal func readUInt8() throws -> UInt8 { let bufferSize = 1 - let length = try readStream(&buffer, maxLength: bufferSize) + let length = try readStream(buffer, maxLength: bufferSize) if length < 0 { throw CSVError.streamErrorHasOccurred(error: stream.streamError!) } @@ -120,14 +122,14 @@ internal class BinaryReader { internal func readUInt16() throws -> UInt16 { let bufferSize = 2 - let length = try readStream(&buffer, maxLength: bufferSize) + let length = try readStream(buffer, maxLength: bufferSize) if length < 0 { throw CSVError.streamErrorHasOccurred(error: stream.streamError!) } if length != bufferSize { throw CSVError.stringEncodingMismatch } - return try UnsafePointer(buffer).withMemoryRebound(to: UInt16.self, capacity: 1) { + return try buffer.withMemoryRebound(to: UInt16.self, capacity: 1) { switch endian { case .big: return CFSwapInt16BigToHost($0[0]) @@ -141,14 +143,14 @@ internal class BinaryReader { internal func readUInt32() throws -> UInt32 { let bufferSize = 4 - let length = try readStream(&buffer, maxLength: bufferSize) + let length = try readStream(buffer, maxLength: bufferSize) if length < 0 { throw CSVError.streamErrorHasOccurred(error: stream.streamError!) } if length != bufferSize { throw CSVError.stringEncodingMismatch } - return try UnsafePointer(buffer).withMemoryRebound(to: UInt32.self, capacity: 1) { + return try buffer.withMemoryRebound(to: UInt32.self, capacity: 1) { switch endian { case .big: return CFSwapInt32BigToHost($0[0]) From cc2437e177ffe3ce928adba28ef8302afbaeb627 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sat, 5 Nov 2016 17:22:29 +0900 Subject: [PATCH 14/36] Fix circular reference --- CSVConfiguration.swift | 2 +- Sources/CSV+iterator.swift | 2 +- Sources/CSV.swift | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CSVConfiguration.swift b/CSVConfiguration.swift index 995061f..6e4e154 100644 --- a/CSVConfiguration.swift +++ b/CSVConfiguration.swift @@ -14,7 +14,7 @@ internal let defaultDelimiter = UnicodeScalar(UInt8(0x2c)) // "," internal let defaultWhitespaces = CharacterSet.whitespaces /// No overview available. -public struct CSVConfiguration { +public class CSVConfiguration { public var errorHandler: ((Error, Int, Int) -> Void)? = nil diff --git a/Sources/CSV+iterator.swift b/Sources/CSV+iterator.swift index e7981f2..fc8e9a8 100644 --- a/Sources/CSV+iterator.swift +++ b/Sources/CSV+iterator.swift @@ -21,7 +21,7 @@ extension CSV: IteratorProtocol, Sequence { extension CSV { /// No overview available. - public struct Row: RandomAccessCollection { + public class Row: RandomAccessCollection { private let data: [String] private let headerRow: [String]? diff --git a/Sources/CSV.swift b/Sources/CSV.swift index 759a876..00c7a37 100755 --- a/Sources/CSV.swift +++ b/Sources/CSV.swift @@ -60,8 +60,8 @@ public class CSV { let input = reader.makeUInt8Iterator() let iterator = UnicodeIterator(input: input, inputEncodingType: codecType) try self.init(iterator: iterator, config: config) - input.errorHandler = self.errorHandler - iterator.errorHandler = self.errorHandler + input.errorHandler = { [unowned self] in self.errorHandler(error: $0) } + iterator.errorHandler = { [unowned self] in self.errorHandler(error: $0) } } /// Create an instance with `InputStream`. @@ -82,8 +82,8 @@ public class CSV { let input = reader.makeUInt16Iterator() let iterator = UnicodeIterator(input: input, inputEncodingType: codecType) try self.init(iterator: iterator, config: config) - input.errorHandler = self.errorHandler - iterator.errorHandler = self.errorHandler + input.errorHandler = { [unowned self] in self.errorHandler(error: $0) } + iterator.errorHandler = { [unowned self] in self.errorHandler(error: $0) } } /// Create an instance with `InputStream`. @@ -104,8 +104,8 @@ public class CSV { let input = reader.makeUInt32Iterator() let iterator = UnicodeIterator(input: input, inputEncodingType: codecType) try self.init(iterator: iterator, config: config) - input.errorHandler = self.errorHandler - iterator.errorHandler = self.errorHandler + input.errorHandler = { [unowned self] in self.errorHandler(error: $0) } + iterator.errorHandler = { [unowned self] in self.errorHandler(error: $0) } } // MARK: - Parse CSV From 853233e4a746da1d5959c02a9b35c44b8bba1192 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Wed, 16 Nov 2016 01:23:32 +0900 Subject: [PATCH 15/36] Fix the misplacement of file --- CSV.xcodeproj/project.pbxproj | 20 +++++++++---------- .../CSVConfiguration.swift | 0 2 files changed, 10 insertions(+), 10 deletions(-) rename CSVConfiguration.swift => Sources/CSVConfiguration.swift (100%) diff --git a/CSV.xcodeproj/project.pbxproj b/CSV.xcodeproj/project.pbxproj index 557b94f..1a93eb7 100755 --- a/CSV.xcodeproj/project.pbxproj +++ b/CSV.xcodeproj/project.pbxproj @@ -17,10 +17,6 @@ 0E3CE3701DB529D700FA45CF /* TrimFieldsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CE36F1DB529D700FA45CF /* TrimFieldsTests.swift */; }; 0E3CE3711DB529D700FA45CF /* TrimFieldsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CE36F1DB529D700FA45CF /* TrimFieldsTests.swift */; }; 0E3CE3721DB529D700FA45CF /* TrimFieldsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CE36F1DB529D700FA45CF /* TrimFieldsTests.swift */; }; - 0E47EEB91DBB268000EBF783 /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */; }; - 0E47EEBA1DBB268000EBF783 /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */; }; - 0E47EEBB1DBB268000EBF783 /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */; }; - 0E47EEBC1DBB268000EBF783 /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */; }; 0E47EEC11DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; 0E47EEC21DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; 0E47EEC31DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; @@ -61,6 +57,10 @@ 0EA2AB821D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; }; 0EA2AB831D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; }; 0EA2AB841D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; }; + 0EDF8EAF1DDB6D620068056A /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */; }; + 0EDF8EB01DDB6DAC0068056A /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */; }; + 0EDF8EB11DDB6DAC0068056A /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */; }; + 0EDF8EB21DDB6DAD0068056A /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -91,7 +91,6 @@ 0E0F160D1D197DB800C92580 /* Endian.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Endian.swift; sourceTree = ""; }; 0E3CE36B1DB5281E00FA45CF /* UnicodeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeTests.swift; sourceTree = ""; }; 0E3CE36F1DB529D700FA45CF /* TrimFieldsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrimFieldsTests.swift; sourceTree = ""; }; - 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVConfiguration.swift; sourceTree = SOURCE_ROOT; }; 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSV+iterator.swift"; sourceTree = ""; }; 0E7E8C811D0BC7BB0057A1C1 /* CSV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CSV.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8C8B1D0BC7BB0057A1C1 /* CSVTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CSVTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -111,6 +110,7 @@ 0E9317DD1D0DBCC500AC20A0 /* ReadmeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadmeTests.swift; sourceTree = ""; }; 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinaryReader.swift; sourceTree = ""; }; 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeIterator.swift; sourceTree = ""; }; + 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVConfiguration.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -200,7 +200,7 @@ 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */, 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */, 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */, - 0E47EEB81DBB268000EBF783 /* CSVConfiguration.swift */, + 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */, 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */, 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */, 0E0F160D1D197DB800C92580 /* Endian.swift */, @@ -538,7 +538,7 @@ 0E47EEC21DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, 0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */, 0E0F160F1D197DB800C92580 /* Endian.swift in Sources */, - 0E47EEBA1DBB268000EBF783 /* CSVConfiguration.swift in Sources */, + 0EDF8EB01DDB6DAC0068056A /* CSVConfiguration.swift in Sources */, 0E7E8CA21D0BC7F10057A1C1 /* CSVError.swift in Sources */, 0EA2AB7D1D183B45003EC967 /* BinaryReader.swift in Sources */, ); @@ -565,7 +565,7 @@ 0E47EEC41DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, 0E7E8CBE1D0BC9D70057A1C1 /* CSV.swift in Sources */, 0E0F16111D197DB800C92580 /* Endian.swift in Sources */, - 0E47EEBC1DBB268000EBF783 /* CSVConfiguration.swift in Sources */, + 0EDF8EB21DDB6DAD0068056A /* CSVConfiguration.swift in Sources */, 0E7E8CBF1D0BC9D70057A1C1 /* CSVError.swift in Sources */, 0EA2AB7F1D183B45003EC967 /* BinaryReader.swift in Sources */, ); @@ -580,7 +580,7 @@ 0E47EEC11DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, 0E7E8CE01D0BCA8E0057A1C1 /* CSV.swift in Sources */, 0E0F160E1D197DB800C92580 /* Endian.swift in Sources */, - 0E47EEB91DBB268000EBF783 /* CSVConfiguration.swift in Sources */, + 0EDF8EAF1DDB6D620068056A /* CSVConfiguration.swift in Sources */, 0E7E8CE11D0BCA8E0057A1C1 /* CSVError.swift in Sources */, 0EA2AB7C1D183B45003EC967 /* BinaryReader.swift in Sources */, ); @@ -607,7 +607,7 @@ 0E47EEC31DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, 0E7E8D001D0BCDCF0057A1C1 /* CSV.swift in Sources */, 0E0F16101D197DB800C92580 /* Endian.swift in Sources */, - 0E47EEBB1DBB268000EBF783 /* CSVConfiguration.swift in Sources */, + 0EDF8EB11DDB6DAC0068056A /* CSVConfiguration.swift in Sources */, 0E7E8D011D0BCDCF0057A1C1 /* CSVError.swift in Sources */, 0EA2AB7E1D183B45003EC967 /* BinaryReader.swift in Sources */, ); diff --git a/CSVConfiguration.swift b/Sources/CSVConfiguration.swift similarity index 100% rename from CSVConfiguration.swift rename to Sources/CSVConfiguration.swift From aa5fd6ac0330f449426a731afdc0520af5bc2a2d Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Wed, 16 Nov 2016 01:49:25 +0900 Subject: [PATCH 16/36] =?UTF-8?q?Replace=20=E2=80=9Cvar=E2=80=9D=20with=20?= =?UTF-8?q?=E2=80=9Clet=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Tests/CSVTests/CSVTests.swift | 10 +++++----- Tests/CSVTests/TrimFieldsTests.swift | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Tests/CSVTests/CSVTests.swift b/Tests/CSVTests/CSVTests.swift index c79c1f6..2b59099 100755 --- a/Tests/CSVTests/CSVTests.swift +++ b/Tests/CSVTests/CSVTests.swift @@ -84,14 +84,14 @@ class CSVTests: XCTestCase { func testCommaInQuotationMarks() { let csvString = "abab,\"cd,cd\",efef" - var csv = try! CSV(string: csvString) + let csv = try! CSV(string: csvString) let row = csv.next()! XCTAssertEqual(row.toArray(), ["abab", "cd,cd", "efef"]) } func testEscapedQuotationMark1() { let csvString = "abab,\"\"\"cdcd\",efef\r\nzxcv,asdf,qwer" - var csv = try! CSV(string: csvString) + let csv = try! CSV(string: csvString) var row = csv.next()! XCTAssertEqual(row.toArray(), ["abab", "\"cdcd", "efef"]) row = csv.next()! @@ -100,7 +100,7 @@ class CSVTests: XCTestCase { func testEscapedQuotationMark2() { let csvString = "abab,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\"" - var csv = try! CSV(string: csvString) + let csv = try! CSV(string: csvString) var row = csv.next()! XCTAssertEqual(row.toArray(), ["abab", "cdcd", "efef"]) row = csv.next()! @@ -109,7 +109,7 @@ class CSVTests: XCTestCase { func testEmptyField() { let csvString = "abab,,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\"," - var csv = try! CSV(string: csvString) + let csv = try! CSV(string: csvString) var row = csv.next()! XCTAssertEqual(row.toArray(), ["abab", "", "cdcd", "efef"]) row = csv.next()! @@ -133,7 +133,7 @@ class CSVTests: XCTestCase { func testCSVState1() { let it = "あ,い1,\"う\",えお\n,,x,".unicodeScalars.makeIterator() - var csv = try! CSV(iterator: it, config: CSVConfiguration()) + let csv = try! CSV(iterator: it, config: CSVConfiguration()) var rows = [[String]]() diff --git a/Tests/CSVTests/TrimFieldsTests.swift b/Tests/CSVTests/TrimFieldsTests.swift index f55d2c9..e62a11c 100644 --- a/Tests/CSVTests/TrimFieldsTests.swift +++ b/Tests/CSVTests/TrimFieldsTests.swift @@ -104,7 +104,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields11() { let csvString = " abc \n def " let config = CSVConfiguration(trimFields: true) - var csv = try! CSV(string: csvString, config: config) + let csv = try! CSV(string: csvString, config: config) let row1 = csv.next()! XCTAssertEqual(row1.toArray(), ["abc"]) @@ -115,7 +115,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields12() { let csvString = " \"abc \" \n \" def\" " let config = CSVConfiguration(trimFields: true) - var csv = try! CSV(string: csvString, config: config) + let csv = try! CSV(string: csvString, config: config) let row1 = csv.next()! XCTAssertEqual(row1.toArray(), ["abc "]) From 2f7accff1a9c66a09d33b001df14e899c606903b Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Fri, 23 Dec 2016 16:58:14 +0900 Subject: [PATCH 17/36] Fix swiftlint warning --- Tests/CSVTests/TrimFieldsTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CSVTests/TrimFieldsTests.swift b/Tests/CSVTests/TrimFieldsTests.swift index f589739..43e477a 100644 --- a/Tests/CSVTests/TrimFieldsTests.swift +++ b/Tests/CSVTests/TrimFieldsTests.swift @@ -24,7 +24,7 @@ class TrimFieldsTests: XCTestCase { ("testTrimFields10", testTrimFields10), ("testTrimFields11", testTrimFields11), ("testTrimFields12", testTrimFields12), - ("testTrimFields13", testTrimFields13), + ("testTrimFields13", testTrimFields13) ] func testTrimFields1() { From 7b9caaf9a88b679590b3473266db8501f78c3598 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sat, 27 May 2017 22:27:53 +0900 Subject: [PATCH 18/36] Changed to use character literal --- Sources/CSV.swift | 8 ++++---- Sources/CSVConfiguration.swift | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/CSV.swift b/Sources/CSV.swift index 00c7a37..67b51e3 100755 --- a/Sources/CSV.swift +++ b/Sources/CSV.swift @@ -8,9 +8,9 @@ import Foundation -private let LF = UnicodeScalar(UInt8(0x0a)) // "\n" -private let CR = UnicodeScalar(UInt8(0x0d)) // "\r" -private let DQUOTE = UnicodeScalar(UInt8(0x22)) // "\"" +private let LF: UnicodeScalar = "\n" +private let CR: UnicodeScalar = "\r" +private let DQUOTE: UnicodeScalar = "\"" /// No overview available. public class CSV { @@ -228,7 +228,7 @@ public class CSV { } private func errorHandler(error: Error) { - config.errorHandler?(error, currentRowIndex, currentFieldIndex) + config.fileInputErrorHandler?(error, currentRowIndex, currentFieldIndex) } // MARK: - deprecated diff --git a/Sources/CSVConfiguration.swift b/Sources/CSVConfiguration.swift index 6e4e154..1313551 100644 --- a/Sources/CSVConfiguration.swift +++ b/Sources/CSVConfiguration.swift @@ -10,13 +10,13 @@ import Foundation internal let defaultHasHeaderRow = false internal let defaultTrimFields = false -internal let defaultDelimiter = UnicodeScalar(UInt8(0x2c)) // "," +internal let defaultDelimiter: UnicodeScalar = "," internal let defaultWhitespaces = CharacterSet.whitespaces /// No overview available. public class CSVConfiguration { - public var errorHandler: ((Error, Int, Int) -> Void)? = nil + public var fileInputErrorHandler: ((Error, Int, Int) -> Void)? = nil /// `true` if the CSV has a header row, otherwise `false`. Default: `false`. public let hasHeaderRow: Bool From 3bf2eab076f3598ec0cee3437730fcc4b9a86bcf Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 28 May 2017 12:20:29 +0900 Subject: [PATCH 19/36] Add CSVWriter class --- CSV.xcodeproj/project.pbxproj | 18 ++++ CSVWriter.swift | 160 ++++++++++++++++++++++++++++ Sources/CSV.swift | 9 +- Tests/CSVTests/CSVWriterTests.swift | 38 +++++++ 4 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 CSVWriter.swift create mode 100644 Tests/CSVTests/CSVWriterTests.swift diff --git a/CSV.xcodeproj/project.pbxproj b/CSV.xcodeproj/project.pbxproj index bbfa8e2..96b3d75 100755 --- a/CSV.xcodeproj/project.pbxproj +++ b/CSV.xcodeproj/project.pbxproj @@ -15,6 +15,13 @@ 0E47EEC21DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; 0E47EEC31DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; 0E47EEC41DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; + 0E5402161ED9DC960019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5402151ED9DC960019C3ED /* CSVWriter.swift */; }; + 0E5402171ED9DC960019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5402151ED9DC960019C3ED /* CSVWriter.swift */; }; + 0E5402181ED9DC960019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5402151ED9DC960019C3ED /* CSVWriter.swift */; }; + 0E5402191ED9DC960019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5402151ED9DC960019C3ED /* CSVWriter.swift */; }; + 0E54021B1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; }; + 0E54021C1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; }; + 0E54021D1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; }; 0E7E8C8C1D0BC7BB0057A1C1 /* CSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E7E8C811D0BC7BB0057A1C1 /* CSV.framework */; }; 0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; }; 0E7E8CA21D0BC7F10057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; }; @@ -90,6 +97,8 @@ /* Begin PBXFileReference section */ 0E0F160D1D197DB800C92580 /* Endian.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Endian.swift; sourceTree = ""; }; 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSV+iterator.swift"; sourceTree = ""; }; + 0E5402151ED9DC960019C3ED /* CSVWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVWriter.swift; sourceTree = SOURCE_ROOT; }; + 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVWriterTests.swift; sourceTree = ""; }; 0E7E8C811D0BC7BB0057A1C1 /* CSV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CSV.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8C8B1D0BC7BB0057A1C1 /* CSVTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CSVTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSV.swift; sourceTree = ""; }; @@ -206,6 +215,7 @@ 0E0F160D1D197DB800C92580 /* Endian.swift */, 0E7E8CAC1D0BC8610057A1C1 /* Info.plist */, 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */, + 0E5402151ED9DC960019C3ED /* CSVWriter.swift */, ); path = Sources; sourceTree = ""; @@ -227,6 +237,7 @@ 0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */, 0EDF8ED01DDB73370068056A /* TrimFieldsTests.swift */, 0EDF8ED11DDB73370068056A /* UnicodeTests.swift */, + 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */, ); path = CSVTests; sourceTree = ""; @@ -536,6 +547,7 @@ 0E9317D51D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB821D183BA9003EC967 /* UnicodeIterator.swift in Sources */, 0E47EEC21DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, + 0E5402171ED9DC960019C3ED /* CSVWriter.swift in Sources */, 0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */, 0E0F160F1D197DB800C92580 /* Endian.swift in Sources */, 0EDF8EB01DDB6DAC0068056A /* CSVConfiguration.swift in Sources */, @@ -553,6 +565,7 @@ 0EDF8EDF1DDB73520068056A /* TrimFieldsTests.swift in Sources */, 0EDF8EE01DDB73520068056A /* UnicodeTests.swift in Sources */, 0EDF8EDD1DDB73520068056A /* LineBreakTests.swift in Sources */, + 0E54021C1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -563,6 +576,7 @@ 0E9317D71D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB841D183BA9003EC967 /* UnicodeIterator.swift in Sources */, 0E47EEC41DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, + 0E5402191ED9DC960019C3ED /* CSVWriter.swift in Sources */, 0E7E8CBE1D0BC9D70057A1C1 /* CSV.swift in Sources */, 0E0F16111D197DB800C92580 /* Endian.swift in Sources */, 0EDF8EB21DDB6DAD0068056A /* CSVConfiguration.swift in Sources */, @@ -578,6 +592,7 @@ 0E9317D41D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB811D183BA9003EC967 /* UnicodeIterator.swift in Sources */, 0E47EEC11DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, + 0E5402161ED9DC960019C3ED /* CSVWriter.swift in Sources */, 0E7E8CE01D0BCA8E0057A1C1 /* CSV.swift in Sources */, 0E0F160E1D197DB800C92580 /* Endian.swift in Sources */, 0EDF8EAF1DDB6D620068056A /* CSVConfiguration.swift in Sources */, @@ -595,6 +610,7 @@ 0EDF8EDA1DDB73520068056A /* TrimFieldsTests.swift in Sources */, 0EDF8EDB1DDB73520068056A /* UnicodeTests.swift in Sources */, 0EDF8ED81DDB73520068056A /* LineBreakTests.swift in Sources */, + 0E54021B1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -605,6 +621,7 @@ 0E9317D61D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB831D183BA9003EC967 /* UnicodeIterator.swift in Sources */, 0E47EEC31DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, + 0E5402181ED9DC960019C3ED /* CSVWriter.swift in Sources */, 0E7E8D001D0BCDCF0057A1C1 /* CSV.swift in Sources */, 0E0F16101D197DB800C92580 /* Endian.swift in Sources */, 0EDF8EB11DDB6DAC0068056A /* CSVConfiguration.swift in Sources */, @@ -622,6 +639,7 @@ 0EDF8EE41DDB73530068056A /* TrimFieldsTests.swift in Sources */, 0EDF8EE51DDB73530068056A /* UnicodeTests.swift in Sources */, 0EDF8EE21DDB73530068056A /* LineBreakTests.swift in Sources */, + 0E54021D1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/CSVWriter.swift b/CSVWriter.swift new file mode 100644 index 0000000..786a50f --- /dev/null +++ b/CSVWriter.swift @@ -0,0 +1,160 @@ +// +// CSVWriter.swift +// CSV +// +// Created by Yasuhiro Hatta on 2017/05/28. +// Copyright © 2017 yaslab. All rights reserved. +// + +import Foundation + +public class CSVWriter { + + public struct Configuration { + + public var delimiter: String + public var newline: String + + public init(delimiter: String = String(defaultDelimiter), newline: String = String(LF)) { + self.delimiter = delimiter + self.newline = newline + } + + } + + public let stream: OutputStream + public let configuration: Configuration + fileprivate let writeScalar: ((Unicode.Scalar) -> Void) + + fileprivate var isFirstRecord: Bool = true + fileprivate var isFirstField: Bool = true + + fileprivate init( + stream: OutputStream, + configuration: Configuration, + writeScalar: @escaping ((Unicode.Scalar) -> Void)) { + + self.stream = stream + self.configuration = configuration + self.writeScalar = writeScalar + + if stream.streamStatus == .notOpen { + stream.open() + } + } + +} + +extension CSVWriter { + + public convenience init( + stream: OutputStream, + configuration: Configuration = Configuration()) { + + self.init(stream: stream, codecType: UTF8.self, configuration: configuration) + } + + public convenience init( + stream: OutputStream, + codecType: T.Type, + configuration: Configuration = Configuration() + ) where T.CodeUnit == UInt8 { + + self.init(stream: stream, configuration: configuration) { (scalar: Unicode.Scalar) in + codecType.encode(scalar) { (code: UInt8) in + var code = code + let count = stream.write(&code, maxLength: 1) + if count != 1 { + // FIXME: Error + print("ERROR: count != 1") + } + } + } + } + + public convenience init( + stream: OutputStream, + codecType: T.Type, + endian: Endian = .big, + configuration: Configuration = Configuration() + ) where T.CodeUnit == UInt16 { + + self.init(stream: stream, configuration: configuration) { (scalar: Unicode.Scalar) in + codecType.encode(scalar) { (code: UInt16) in + var code = (endian == .big) ? code.bigEndian : code.littleEndian + let count = withUnsafeBytes(of: &code) { (buffer) -> Int in + return stream.write(buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), maxLength: buffer.count) + } + if count != 2 { + // FIXME: Error + print("ERROR: count != 2") + } + } + } + } + + public convenience init( + stream: OutputStream, + codecType: T.Type, + endian: Endian = .big, + configuration: Configuration = Configuration() + ) where T.CodeUnit == UInt32 { + + self.init(stream: stream, configuration: configuration) { (scalar: Unicode.Scalar) in + codecType.encode(scalar) { (code: UInt32) in + var code = (endian == .big) ? code.bigEndian : code.littleEndian + let count = withUnsafeBytes(of: &code) { (buffer) -> Int in + return stream.write(buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), maxLength: buffer.count) + } + if count != 4 { + // FIXME: Error + print("ERROR: count != 4") + } + } + } + } + +} + +extension CSVWriter { + + public func beginNewRecord() { + isFirstField = true + } + + public func write(field value: String, quoted: Bool = false) { + if isFirstRecord { + isFirstRecord = false + } else { + if isFirstField { + configuration.newline.unicodeScalars.forEach(writeScalar) + } + } + + if isFirstField { + isFirstField = false + } else { + configuration.delimiter.unicodeScalars.forEach(writeScalar) + } + + var value = value + + if quoted { + value = value.replacingOccurrences(of: DQUOTE_STR, with: DQUOTE2_STR) + writeScalar(DQUOTE) + } + + value.unicodeScalars.forEach(writeScalar) + + if quoted { + writeScalar(DQUOTE) + } + } + + public func write(row values: [String], quotedAtIndex: ((Int) -> Bool) = { _ in false }) { + for (i, value) in values.enumerated() { + write(field: value, quoted: quotedAtIndex(i)) + } + } + +} diff --git a/Sources/CSV.swift b/Sources/CSV.swift index 67b51e3..6ce88e9 100755 --- a/Sources/CSV.swift +++ b/Sources/CSV.swift @@ -8,9 +8,12 @@ import Foundation -private let LF: UnicodeScalar = "\n" -private let CR: UnicodeScalar = "\r" -private let DQUOTE: UnicodeScalar = "\"" +internal let LF: UnicodeScalar = "\n" +internal let CR: UnicodeScalar = "\r" +internal let DQUOTE: UnicodeScalar = "\"" + +internal let DQUOTE_STR: String = "\"" +internal let DQUOTE2_STR: String = "\"\"" /// No overview available. public class CSV { diff --git a/Tests/CSVTests/CSVWriterTests.swift b/Tests/CSVTests/CSVWriterTests.swift new file mode 100644 index 0000000..5d9c551 --- /dev/null +++ b/Tests/CSVTests/CSVWriterTests.swift @@ -0,0 +1,38 @@ +// +// CSVWriterTests.swift +// CSV +// +// Created by Yasuhiro Hatta on 2017/05/28. +// Copyright © 2017年 yaslab. All rights reserved. +// + +import Foundation +import XCTest + +import CSV + +class CSVWriterTests: XCTestCase { + + static let allTests = [ + ("testSample", testSample) + ] + + func testSample() { + let stream = OutputStream(toMemory: ()) + let csv = CSVWriter(stream: stream, codecType: UTF8.self) + + for i in 0 ..< 10 { + csv.beginNewRecord() + csv.write(field: "\(i)") + csv.write(field: "\(i)-text", quoted: true) + } + + let _data = stream.property(forKey: .dataWrittenToMemoryStreamKey) + guard let data = _data as? Data else { + return + } + let str = String(data: data, encoding: .utf8)! + print(str) + } + +} From 376640007eea242497d09572e68d2040fa68afa0 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 28 May 2017 12:54:47 +0900 Subject: [PATCH 20/36] Use UnicodeScalar insted of Unicode.Scalar --- CSVWriter.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CSVWriter.swift b/CSVWriter.swift index 786a50f..9e037eb 100644 --- a/CSVWriter.swift +++ b/CSVWriter.swift @@ -24,7 +24,7 @@ public class CSVWriter { public let stream: OutputStream public let configuration: Configuration - fileprivate let writeScalar: ((Unicode.Scalar) -> Void) + fileprivate let writeScalar: ((UnicodeScalar) -> Void) fileprivate var isFirstRecord: Bool = true fileprivate var isFirstField: Bool = true @@ -32,7 +32,7 @@ public class CSVWriter { fileprivate init( stream: OutputStream, configuration: Configuration, - writeScalar: @escaping ((Unicode.Scalar) -> Void)) { + writeScalar: @escaping ((UnicodeScalar) -> Void)) { self.stream = stream self.configuration = configuration @@ -60,7 +60,7 @@ extension CSVWriter { configuration: Configuration = Configuration() ) where T.CodeUnit == UInt8 { - self.init(stream: stream, configuration: configuration) { (scalar: Unicode.Scalar) in + self.init(stream: stream, configuration: configuration) { (scalar: UnicodeScalar) in codecType.encode(scalar) { (code: UInt8) in var code = code let count = stream.write(&code, maxLength: 1) @@ -79,7 +79,7 @@ extension CSVWriter { configuration: Configuration = Configuration() ) where T.CodeUnit == UInt16 { - self.init(stream: stream, configuration: configuration) { (scalar: Unicode.Scalar) in + self.init(stream: stream, configuration: configuration) { (scalar: UnicodeScalar) in codecType.encode(scalar) { (code: UInt16) in var code = (endian == .big) ? code.bigEndian : code.littleEndian let count = withUnsafeBytes(of: &code) { (buffer) -> Int in @@ -100,7 +100,7 @@ extension CSVWriter { configuration: Configuration = Configuration() ) where T.CodeUnit == UInt32 { - self.init(stream: stream, configuration: configuration) { (scalar: Unicode.Scalar) in + self.init(stream: stream, configuration: configuration) { (scalar: UnicodeScalar) in codecType.encode(scalar) { (code: UInt32) in var code = (endian == .big) ? code.bigEndian : code.littleEndian let count = withUnsafeBytes(of: &code) { (buffer) -> Int in From 1918e5163d6c1f69431c41e3490a7807b40b0313 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 28 May 2017 12:58:38 +0900 Subject: [PATCH 21/36] Correct file misplacement --- CSV.xcodeproj/project.pbxproj | 22 +++++++++++----------- CSVWriter.swift => Sources/CSVWriter.swift | 0 2 files changed, 11 insertions(+), 11 deletions(-) rename CSVWriter.swift => Sources/CSVWriter.swift (100%) diff --git a/CSV.xcodeproj/project.pbxproj b/CSV.xcodeproj/project.pbxproj index 96b3d75..3018002 100755 --- a/CSV.xcodeproj/project.pbxproj +++ b/CSV.xcodeproj/project.pbxproj @@ -15,13 +15,13 @@ 0E47EEC21DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; 0E47EEC31DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; 0E47EEC41DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; - 0E5402161ED9DC960019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5402151ED9DC960019C3ED /* CSVWriter.swift */; }; - 0E5402171ED9DC960019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5402151ED9DC960019C3ED /* CSVWriter.swift */; }; - 0E5402181ED9DC960019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5402151ED9DC960019C3ED /* CSVWriter.swift */; }; - 0E5402191ED9DC960019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5402151ED9DC960019C3ED /* CSVWriter.swift */; }; 0E54021B1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; }; 0E54021C1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; }; 0E54021D1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; }; + 0E5402221EDA82220019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */; }; + 0E5402231EDA82220019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */; }; + 0E5402241EDA82220019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */; }; + 0E5402251EDA82230019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */; }; 0E7E8C8C1D0BC7BB0057A1C1 /* CSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E7E8C811D0BC7BB0057A1C1 /* CSV.framework */; }; 0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; }; 0E7E8CA21D0BC7F10057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; }; @@ -97,8 +97,8 @@ /* Begin PBXFileReference section */ 0E0F160D1D197DB800C92580 /* Endian.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Endian.swift; sourceTree = ""; }; 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSV+iterator.swift"; sourceTree = ""; }; - 0E5402151ED9DC960019C3ED /* CSVWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVWriter.swift; sourceTree = SOURCE_ROOT; }; 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVWriterTests.swift; sourceTree = ""; }; + 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVWriter.swift; sourceTree = ""; }; 0E7E8C811D0BC7BB0057A1C1 /* CSV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CSV.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8C8B1D0BC7BB0057A1C1 /* CSVTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CSVTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSV.swift; sourceTree = ""; }; @@ -212,10 +212,10 @@ 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */, 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */, 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */, + 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */, 0E0F160D1D197DB800C92580 /* Endian.swift */, 0E7E8CAC1D0BC8610057A1C1 /* Info.plist */, 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */, - 0E5402151ED9DC960019C3ED /* CSVWriter.swift */, ); path = Sources; sourceTree = ""; @@ -233,11 +233,11 @@ isa = PBXGroup; children = ( 0EDF8ECD1DDB73370068056A /* CSVTests.swift */, + 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */, 0EDF8ECE1DDB73370068056A /* LineBreakTests.swift */, 0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */, 0EDF8ED01DDB73370068056A /* TrimFieldsTests.swift */, 0EDF8ED11DDB73370068056A /* UnicodeTests.swift */, - 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */, ); path = CSVTests; sourceTree = ""; @@ -547,7 +547,7 @@ 0E9317D51D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB821D183BA9003EC967 /* UnicodeIterator.swift in Sources */, 0E47EEC21DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, - 0E5402171ED9DC960019C3ED /* CSVWriter.swift in Sources */, + 0E5402241EDA82220019C3ED /* CSVWriter.swift in Sources */, 0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */, 0E0F160F1D197DB800C92580 /* Endian.swift in Sources */, 0EDF8EB01DDB6DAC0068056A /* CSVConfiguration.swift in Sources */, @@ -576,7 +576,7 @@ 0E9317D71D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB841D183BA9003EC967 /* UnicodeIterator.swift in Sources */, 0E47EEC41DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, - 0E5402191ED9DC960019C3ED /* CSVWriter.swift in Sources */, + 0E5402221EDA82220019C3ED /* CSVWriter.swift in Sources */, 0E7E8CBE1D0BC9D70057A1C1 /* CSV.swift in Sources */, 0E0F16111D197DB800C92580 /* Endian.swift in Sources */, 0EDF8EB21DDB6DAD0068056A /* CSVConfiguration.swift in Sources */, @@ -592,7 +592,7 @@ 0E9317D41D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB811D183BA9003EC967 /* UnicodeIterator.swift in Sources */, 0E47EEC11DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, - 0E5402161ED9DC960019C3ED /* CSVWriter.swift in Sources */, + 0E5402251EDA82230019C3ED /* CSVWriter.swift in Sources */, 0E7E8CE01D0BCA8E0057A1C1 /* CSV.swift in Sources */, 0E0F160E1D197DB800C92580 /* Endian.swift in Sources */, 0EDF8EAF1DDB6D620068056A /* CSVConfiguration.swift in Sources */, @@ -621,7 +621,7 @@ 0E9317D61D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB831D183BA9003EC967 /* UnicodeIterator.swift in Sources */, 0E47EEC31DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, - 0E5402181ED9DC960019C3ED /* CSVWriter.swift in Sources */, + 0E5402231EDA82220019C3ED /* CSVWriter.swift in Sources */, 0E7E8D001D0BCDCF0057A1C1 /* CSV.swift in Sources */, 0E0F16101D197DB800C92580 /* Endian.swift in Sources */, 0EDF8EB11DDB6DAC0068056A /* CSVConfiguration.swift in Sources */, diff --git a/CSVWriter.swift b/Sources/CSVWriter.swift similarity index 100% rename from CSVWriter.swift rename to Sources/CSVWriter.swift From f46887bdbbd7a3e4a6c52073f2d43fac64b4ca5e Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 28 May 2017 12:59:19 +0900 Subject: [PATCH 22/36] Add more CSVWriter tests --- Tests/CSVTests/CSVWriterTests.swift | 176 ++++++++++++++++++++++++---- 1 file changed, 155 insertions(+), 21 deletions(-) diff --git a/Tests/CSVTests/CSVWriterTests.swift b/Tests/CSVTests/CSVWriterTests.swift index 5d9c551..08bbd61 100644 --- a/Tests/CSVTests/CSVWriterTests.swift +++ b/Tests/CSVTests/CSVWriterTests.swift @@ -11,28 +11,162 @@ import XCTest import CSV -class CSVWriterTests: XCTestCase { +extension OutputStream { - static let allTests = [ - ("testSample", testSample) - ] - - func testSample() { - let stream = OutputStream(toMemory: ()) - let csv = CSVWriter(stream: stream, codecType: UTF8.self) - - for i in 0 ..< 10 { - csv.beginNewRecord() - csv.write(field: "\(i)") - csv.write(field: "\(i)-text", quoted: true) - } - - let _data = stream.property(forKey: .dataWrittenToMemoryStreamKey) - guard let data = _data as? Data else { - return - } - let str = String(data: data, encoding: .utf8)! - print(str) + var data: Data? { + return property(forKey: .dataWrittenToMemoryStreamKey) as? Data + } + +} + +class CSVWriterTests: XCTestCase { + + static let allTests = [ + ("testSingleFieldSingleRecord", testSingleFieldSingleRecord), + ("testSingleFieldMultipleRecord", testSingleFieldMultipleRecord), + ("testMultipleFieldSingleRecord", testMultipleFieldSingleRecord), + ("testMultipleFieldMultipleRecord", testMultipleFieldMultipleRecord), + ("testQuoted", testQuoted), + ("testQuotedNewline", testQuotedNewline), + ("testEscapeQuote", testEscapeQuote) + ] + + /// xxxx + func testSingleFieldSingleRecord() { + let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" + + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = CSVWriter(stream: stream) + csv.beginNewRecord() + csv.write(field: str) + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, str) + } + + /// xxxx + /// xxxx + func testSingleFieldMultipleRecord() { + let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" + + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = CSVWriter(stream: stream) + csv.beginNewRecord() + csv.write(field: str + "-1") + csv.beginNewRecord() + csv.write(field: str + "-2") + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, "\(str)-1\n\(str)-2") + } + + /// xxxx,xxxx + func testMultipleFieldSingleRecord() { + let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" + + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = CSVWriter(stream: stream) + csv.beginNewRecord() + csv.write(field: str + "-1") + csv.write(field: str + "-2") + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, "\(str)-1,\(str)-2") + } + + /// xxxx,xxxx + /// xxxx,xxxx + func testMultipleFieldMultipleRecord() { + let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" + + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = CSVWriter(stream: stream) + csv.beginNewRecord() + csv.write(field: str + "-1-1") + csv.write(field: str + "-1-2") + csv.beginNewRecord() + csv.write(field: str + "-2-1") + csv.write(field: str + "-2-2") + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, "\(str)-1-1,\(str)-1-2\n\(str)-2-1,\(str)-2-2") + } + + /// "xxxx",xxxx + func testQuoted() { + let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" + + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = CSVWriter(stream: stream) + csv.beginNewRecord() + csv.write(field: str + "-1", quoted: true) + csv.write(field: str + "-2") // quoted: false + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, "\"\(str)-1\",\(str)-2") + } + + /// xxxx,"xx\nxx" + func testQuotedNewline() { + let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" + + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = CSVWriter(stream: stream) + csv.beginNewRecord() + csv.write(field: str + "-1") // quoted: false + csv.write(field: str + "-\n-2", quoted: true) + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, "\(str)-1,\"\(str)-\n-2\"") + } + + /// xxxx,"xx""xx" + func testEscapeQuote() { + let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" + + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = CSVWriter(stream: stream) + csv.beginNewRecord() + csv.write(field: str + "-1") // quoted: false + csv.write(field: str + "-\"-2", quoted: true) + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, "\(str)-1,\"\(str)-\"\"-2\"") } } From 1d72634a035666f6139deecd7ec9cc48825deff5 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 28 May 2017 13:35:40 +0900 Subject: [PATCH 23/36] Update .travis.yml --- .swift-version | 2 +- .travis.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.swift-version b/.swift-version index 9f55b2c..8c50098 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -3.0 +3.1 diff --git a/.travis.yml b/.travis.yml index 00660c2..4f959be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode8 +osx_image: xcode8.3 env: - LC_CTYPE=en_US.UTF-8 git: From eb387105909c8f0c17edf0d7e10a043a8e78e646 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 28 May 2017 14:27:11 +0900 Subject: [PATCH 24/36] Implement error handling --- Sources/BinaryReader.swift | 6 +-- Sources/CSVError.swift | 6 ++- Sources/CSVWriter.swift | 79 +++++++++++++++++------------ Tests/CSVTests/CSVWriterTests.swift | 44 ++++++++-------- 4 files changed, 75 insertions(+), 60 deletions(-) diff --git a/Sources/BinaryReader.swift b/Sources/BinaryReader.swift index 497afcf..6247c0d 100755 --- a/Sources/BinaryReader.swift +++ b/Sources/BinaryReader.swift @@ -62,7 +62,7 @@ internal class BinaryReader { stream.open() } if stream.streamStatus != .open { - throw CSVError.cannotOpenFile + throw CSVError.cannotOpenStream } let readCount = stream.read(tempBuffer, maxLength: tempBufferSize) @@ -93,7 +93,7 @@ internal class BinaryReader { private func readStream(_ buffer: UnsafeMutablePointer, maxLength: Int) throws -> Int { if stream.streamStatus != .open { - throw CSVError.cannotReadFile + throw CSVError.cannotReadStream } var i = 0 @@ -115,7 +115,7 @@ internal class BinaryReader { throw CSVError.streamErrorHasOccurred(error: stream.streamError!) } if length != bufferSize { - throw CSVError.cannotReadFile + throw CSVError.cannotReadStream } return buffer[0] } diff --git a/Sources/CSVError.swift b/Sources/CSVError.swift index ec9db62..bfa2183 100644 --- a/Sources/CSVError.swift +++ b/Sources/CSVError.swift @@ -10,9 +10,11 @@ public enum CSVError: Error { /// No overview available. - case cannotOpenFile + case cannotOpenStream /// No overview available. - case cannotReadFile + case cannotReadStream + /// No overview available. + case cannotWriteStream /// No overview available. case streamErrorHasOccurred(error: Error) /// No overview available. diff --git a/Sources/CSVWriter.swift b/Sources/CSVWriter.swift index 9e037eb..1262e59 100644 --- a/Sources/CSVWriter.swift +++ b/Sources/CSVWriter.swift @@ -24,7 +24,7 @@ public class CSVWriter { public let stream: OutputStream public let configuration: Configuration - fileprivate let writeScalar: ((UnicodeScalar) -> Void) + fileprivate let writeScalar: ((UnicodeScalar) throws -> Void) fileprivate var isFirstRecord: Bool = true fileprivate var isFirstField: Bool = true @@ -32,7 +32,7 @@ public class CSVWriter { fileprivate init( stream: OutputStream, configuration: Configuration, - writeScalar: @escaping ((UnicodeScalar) -> Void)) { + writeScalar: @escaping ((UnicodeScalar) throws -> Void)) throws { self.stream = stream self.configuration = configuration @@ -41,6 +41,9 @@ public class CSVWriter { if stream.streamStatus == .notOpen { stream.open() } + if stream.streamStatus != .open { + throw CSVError.cannotOpenStream + } } } @@ -49,26 +52,29 @@ extension CSVWriter { public convenience init( stream: OutputStream, - configuration: Configuration = Configuration()) { + configuration: Configuration = Configuration()) throws { - self.init(stream: stream, codecType: UTF8.self, configuration: configuration) + try self.init(stream: stream, codecType: UTF8.self, configuration: configuration) } public convenience init( stream: OutputStream, codecType: T.Type, configuration: Configuration = Configuration() - ) where T.CodeUnit == UInt8 { + ) throws where T.CodeUnit == UInt8 { - self.init(stream: stream, configuration: configuration) { (scalar: UnicodeScalar) in + try self.init(stream: stream, configuration: configuration) { (scalar: UnicodeScalar) throws in + var error: CSVError? = nil codecType.encode(scalar) { (code: UInt8) in var code = code let count = stream.write(&code, maxLength: 1) if count != 1 { - // FIXME: Error - print("ERROR: count != 1") + error = CSVError.cannotWriteStream } } + if let error = error { + throw error + } } } @@ -77,19 +83,22 @@ extension CSVWriter { codecType: T.Type, endian: Endian = .big, configuration: Configuration = Configuration() - ) where T.CodeUnit == UInt16 { + ) throws where T.CodeUnit == UInt16 { - self.init(stream: stream, configuration: configuration) { (scalar: UnicodeScalar) in + try self.init(stream: stream, configuration: configuration) { (scalar: UnicodeScalar) throws in + var error: CSVError? = nil codecType.encode(scalar) { (code: UInt16) in var code = (endian == .big) ? code.bigEndian : code.littleEndian - let count = withUnsafeBytes(of: &code) { (buffer) -> Int in - return stream.write(buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), maxLength: buffer.count) - } - if count != 2 { - // FIXME: Error - print("ERROR: count != 2") + withUnsafeBytes(of: &code) { (buffer) -> Void in + let count = stream.write(buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), maxLength: buffer.count) + if count != buffer.count { + error = CSVError.cannotWriteStream + } } } + if let error = error { + throw error + } } } @@ -98,22 +107,25 @@ extension CSVWriter { codecType: T.Type, endian: Endian = .big, configuration: Configuration = Configuration() - ) where T.CodeUnit == UInt32 { + ) throws where T.CodeUnit == UInt32 { - self.init(stream: stream, configuration: configuration) { (scalar: UnicodeScalar) in + try self.init(stream: stream, configuration: configuration) { (scalar: UnicodeScalar) throws in + var error: CSVError? = nil codecType.encode(scalar) { (code: UInt32) in var code = (endian == .big) ? code.bigEndian : code.littleEndian - let count = withUnsafeBytes(of: &code) { (buffer) -> Int in - return stream.write(buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), maxLength: buffer.count) - } - if count != 4 { - // FIXME: Error - print("ERROR: count != 4") + withUnsafeBytes(of: &code) { (buffer) -> Void in + let count = stream.write(buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), maxLength: buffer.count) + if count != buffer.count { + error = CSVError.cannotWriteStream + } } } + if let error = error { + throw error + } } } - + } extension CSVWriter { @@ -122,38 +134,39 @@ extension CSVWriter { isFirstField = true } - public func write(field value: String, quoted: Bool = false) { + public func write(field value: String, quoted: Bool = false) throws { if isFirstRecord { isFirstRecord = false } else { if isFirstField { - configuration.newline.unicodeScalars.forEach(writeScalar) + try configuration.newline.unicodeScalars.forEach(writeScalar) } } if isFirstField { isFirstField = false } else { - configuration.delimiter.unicodeScalars.forEach(writeScalar) + try configuration.delimiter.unicodeScalars.forEach(writeScalar) } var value = value if quoted { value = value.replacingOccurrences(of: DQUOTE_STR, with: DQUOTE2_STR) - writeScalar(DQUOTE) + try writeScalar(DQUOTE) } - value.unicodeScalars.forEach(writeScalar) + try value.unicodeScalars.forEach(writeScalar) if quoted { - writeScalar(DQUOTE) + try writeScalar(DQUOTE) } } - public func write(row values: [String], quotedAtIndex: ((Int) -> Bool) = { _ in false }) { + public func write(record values: [String], quotedAtIndex: ((Int) -> Bool) = { _ in false }) throws { + beginNewRecord() for (i, value) in values.enumerated() { - write(field: value, quoted: quotedAtIndex(i)) + try write(field: value, quoted: quotedAtIndex(i)) } } diff --git a/Tests/CSVTests/CSVWriterTests.swift b/Tests/CSVTests/CSVWriterTests.swift index 08bbd61..00cd991 100644 --- a/Tests/CSVTests/CSVWriterTests.swift +++ b/Tests/CSVTests/CSVWriterTests.swift @@ -38,9 +38,9 @@ class CSVWriterTests: XCTestCase { let stream = OutputStream(toMemory: ()) stream.open() - let csv = CSVWriter(stream: stream) + let csv = try! CSVWriter(stream: stream) csv.beginNewRecord() - csv.write(field: str) + try! csv.write(field: str) stream.close() let data = stream.data! @@ -57,11 +57,11 @@ class CSVWriterTests: XCTestCase { let stream = OutputStream(toMemory: ()) stream.open() - let csv = CSVWriter(stream: stream) + let csv = try! CSVWriter(stream: stream) csv.beginNewRecord() - csv.write(field: str + "-1") + try! csv.write(field: str + "-1") csv.beginNewRecord() - csv.write(field: str + "-2") + try! csv.write(field: str + "-2") stream.close() let data = stream.data! @@ -77,10 +77,10 @@ class CSVWriterTests: XCTestCase { let stream = OutputStream(toMemory: ()) stream.open() - let csv = CSVWriter(stream: stream) + let csv = try! CSVWriter(stream: stream) csv.beginNewRecord() - csv.write(field: str + "-1") - csv.write(field: str + "-2") + try! csv.write(field: str + "-1") + try! csv.write(field: str + "-2") stream.close() let data = stream.data! @@ -97,13 +97,13 @@ class CSVWriterTests: XCTestCase { let stream = OutputStream(toMemory: ()) stream.open() - let csv = CSVWriter(stream: stream) + let csv = try! CSVWriter(stream: stream) csv.beginNewRecord() - csv.write(field: str + "-1-1") - csv.write(field: str + "-1-2") + try! csv.write(field: str + "-1-1") + try! csv.write(field: str + "-1-2") csv.beginNewRecord() - csv.write(field: str + "-2-1") - csv.write(field: str + "-2-2") + try! csv.write(field: str + "-2-1") + try! csv.write(field: str + "-2-2") stream.close() let data = stream.data! @@ -119,10 +119,10 @@ class CSVWriterTests: XCTestCase { let stream = OutputStream(toMemory: ()) stream.open() - let csv = CSVWriter(stream: stream) + let csv = try! CSVWriter(stream: stream) csv.beginNewRecord() - csv.write(field: str + "-1", quoted: true) - csv.write(field: str + "-2") // quoted: false + try! csv.write(field: str + "-1", quoted: true) + try! csv.write(field: str + "-2") // quoted: false stream.close() let data = stream.data! @@ -138,10 +138,10 @@ class CSVWriterTests: XCTestCase { let stream = OutputStream(toMemory: ()) stream.open() - let csv = CSVWriter(stream: stream) + let csv = try! CSVWriter(stream: stream) csv.beginNewRecord() - csv.write(field: str + "-1") // quoted: false - csv.write(field: str + "-\n-2", quoted: true) + try! csv.write(field: str + "-1") // quoted: false + try! csv.write(field: str + "-\n-2", quoted: true) stream.close() let data = stream.data! @@ -157,10 +157,10 @@ class CSVWriterTests: XCTestCase { let stream = OutputStream(toMemory: ()) stream.open() - let csv = CSVWriter(stream: stream) + let csv = try! CSVWriter(stream: stream) csv.beginNewRecord() - csv.write(field: str + "-1") // quoted: false - csv.write(field: str + "-\"-2", quoted: true) + try! csv.write(field: str + "-1") // quoted: false + try! csv.write(field: str + "-\"-2", quoted: true) stream.close() let data = stream.data! From cf430d4ed246c96e20fdc90d20030d3eff73992b Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 28 May 2017 14:50:59 +0900 Subject: [PATCH 25/36] Add Linux Tests --- Tests/LinuxMain.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 9448d07..4359671 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -11,6 +11,7 @@ import XCTest XCTMain([ testCase(CSVTests.allTests), + testCase(CSVWriterTests.allTests), testCase(LineBreakTests.allTests), testCase(ReadmeTests.allTests), testCase(TrimFieldsTests.allTests), From d9213a7c62cad7d3aa6c662bca51aa77261da9ea Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 28 May 2017 15:16:19 +0900 Subject: [PATCH 26/36] chmod -x --- Sources/BinaryReader.swift | 0 Sources/CSV+init.swift | 0 Sources/CSV.swift | 0 Sources/CSVVersion.h | 0 Sources/UnicodeIterator.swift | 0 5 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 Sources/BinaryReader.swift mode change 100755 => 100644 Sources/CSV+init.swift mode change 100755 => 100644 Sources/CSV.swift mode change 100755 => 100644 Sources/CSVVersion.h mode change 100755 => 100644 Sources/UnicodeIterator.swift diff --git a/Sources/BinaryReader.swift b/Sources/BinaryReader.swift old mode 100755 new mode 100644 diff --git a/Sources/CSV+init.swift b/Sources/CSV+init.swift old mode 100755 new mode 100644 diff --git a/Sources/CSV.swift b/Sources/CSV.swift old mode 100755 new mode 100644 diff --git a/Sources/CSVVersion.h b/Sources/CSVVersion.h old mode 100755 new mode 100644 diff --git a/Sources/UnicodeIterator.swift b/Sources/UnicodeIterator.swift old mode 100755 new mode 100644 From 683e286ac7dedc0f7b97fb124f6ffafc32581059 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 28 May 2017 15:17:39 +0900 Subject: [PATCH 27/36] chmod -x --- Tests/CSVTests/CSVTests.swift | 0 Tests/CSVTests/ReadmeTests.swift | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 Tests/CSVTests/CSVTests.swift mode change 100755 => 100644 Tests/CSVTests/ReadmeTests.swift diff --git a/Tests/CSVTests/CSVTests.swift b/Tests/CSVTests/CSVTests.swift old mode 100755 new mode 100644 diff --git a/Tests/CSVTests/ReadmeTests.swift b/Tests/CSVTests/ReadmeTests.swift old mode 100755 new mode 100644 From b0ac142fdebe0e9b6b10f73afb27617629324dc7 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 28 May 2017 15:27:03 +0900 Subject: [PATCH 28/36] Fix CSVWriterTests for Linux --- Tests/CSVTests/CSVWriterTests.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Tests/CSVTests/CSVWriterTests.swift b/Tests/CSVTests/CSVWriterTests.swift index 00cd991..26e3fac 100644 --- a/Tests/CSVTests/CSVWriterTests.swift +++ b/Tests/CSVTests/CSVWriterTests.swift @@ -14,7 +14,10 @@ import CSV extension OutputStream { var data: Data? { - return property(forKey: .dataWrittenToMemoryStreamKey) as? Data + guard let nsData = property(forKey: .dataWrittenToMemoryStreamKey) as? NSData else { + return nil + } + return Data(referencing: nsData) } } From d9e0b1d36940b7f4435c6ea39d542a5cb87aa2b4 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 28 May 2017 18:23:45 +0900 Subject: [PATCH 29/36] Add more CSVWriter tests --- Tests/CSVTests/CSVWriterTests.swift | 125 ++++++++++++++++++++++++---- 1 file changed, 110 insertions(+), 15 deletions(-) diff --git a/Tests/CSVTests/CSVWriterTests.swift b/Tests/CSVTests/CSVWriterTests.swift index 26e3fac..c17cb78 100644 --- a/Tests/CSVTests/CSVWriterTests.swift +++ b/Tests/CSVTests/CSVWriterTests.swift @@ -31,13 +31,19 @@ class CSVWriterTests: XCTestCase { ("testMultipleFieldMultipleRecord", testMultipleFieldMultipleRecord), ("testQuoted", testQuoted), ("testQuotedNewline", testQuotedNewline), - ("testEscapeQuote", testEscapeQuote) + ("testEscapeQuote", testEscapeQuote), + ("testDelimiter", testDelimiter), + ("testNewline", testNewline), + ("testUTF16BE", testUTF16BE), + ("testUTF16LE", testUTF16LE), + ("testUTF32BE", testUTF32BE), + ("testUTF32LE", testUTF32LE) ] + let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" + /// xxxx func testSingleFieldSingleRecord() { - let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" - let stream = OutputStream(toMemory: ()) stream.open() @@ -55,8 +61,6 @@ class CSVWriterTests: XCTestCase { /// xxxx /// xxxx func testSingleFieldMultipleRecord() { - let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" - let stream = OutputStream(toMemory: ()) stream.open() @@ -75,8 +79,6 @@ class CSVWriterTests: XCTestCase { /// xxxx,xxxx func testMultipleFieldSingleRecord() { - let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" - let stream = OutputStream(toMemory: ()) stream.open() @@ -95,8 +97,6 @@ class CSVWriterTests: XCTestCase { /// xxxx,xxxx /// xxxx,xxxx func testMultipleFieldMultipleRecord() { - let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" - let stream = OutputStream(toMemory: ()) stream.open() @@ -117,8 +117,6 @@ class CSVWriterTests: XCTestCase { /// "xxxx",xxxx func testQuoted() { - let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" - let stream = OutputStream(toMemory: ()) stream.open() @@ -136,8 +134,6 @@ class CSVWriterTests: XCTestCase { /// xxxx,"xx\nxx" func testQuotedNewline() { - let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" - let stream = OutputStream(toMemory: ()) stream.open() @@ -155,8 +151,6 @@ class CSVWriterTests: XCTestCase { /// xxxx,"xx""xx" func testEscapeQuote() { - let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" - let stream = OutputStream(toMemory: ()) stream.open() @@ -172,4 +166,105 @@ class CSVWriterTests: XCTestCase { XCTAssertEqual(csvStr, "\(str)-1,\"\(str)-\"\"-2\"") } + /// Test delimiter: "\t" + func testDelimiter() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let config = CSVWriter.Configuration(delimiter: "\t") + let csv = try! CSVWriter.init(stream: stream, configuration: config) + csv.beginNewRecord() + try! csv.write(field: str + "-1") + try! csv.write(field: str + "-2") + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, "\(str)-1\t\(str)-2") + } + + /// Test newline: "\r\n" + func testNewline() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let config = CSVWriter.Configuration(newline: "\r\n") + let csv = try! CSVWriter.init(stream: stream, configuration: config) + csv.beginNewRecord() + try! csv.write(field: str + "-1") + csv.beginNewRecord() + try! csv.write(field: str + "-2") + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf8)! + + XCTAssertEqual(csvStr, "\(str)-1\r\n\(str)-2") + } + + /// UTF16 Big Endian + func testUTF16BE() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter(stream: stream, codecType: UTF16.self, endian: .big) + csv.beginNewRecord() + try! csv.write(field: str) + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf16BigEndian)! + + XCTAssertEqual(csvStr, str) + } + + /// UTF16 Little Endian + func testUTF16LE() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter(stream: stream, codecType: UTF16.self, endian: .little) + csv.beginNewRecord() + try! csv.write(field: str) + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf16LittleEndian)! + + XCTAssertEqual(csvStr, str) + } + + /// UTF32 Big Endian + func testUTF32BE() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter(stream: stream, codecType: UTF32.self, endian: .big) + csv.beginNewRecord() + try! csv.write(field: str) + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf32BigEndian)! + + XCTAssertEqual(csvStr, str) + } + + /// UTF32 Little Endian + func testUTF32LE() { + let stream = OutputStream(toMemory: ()) + stream.open() + + let csv = try! CSVWriter(stream: stream, codecType: UTF32.self, endian: .little) + csv.beginNewRecord() + try! csv.write(field: str) + + stream.close() + let data = stream.data! + let csvStr = String(data: data, encoding: .utf32LittleEndian)! + + XCTAssertEqual(csvStr, str) + } + } From 7d56f466a9b43617e755e44972153cc6984c2060 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Mon, 29 May 2017 01:33:24 +0900 Subject: [PATCH 30/36] Rename CSV to CSVReader --- CSV.xcodeproj/project.pbxproj | 50 +---- Sources/CSV+init.swift | 41 ---- Sources/CSV+iterator.swift | 94 --------- Sources/CSVConfiguration.swift | 46 ----- Sources/CSVError.swift | 2 +- Sources/{CSV.swift => CSVReader.swift} | 253 ++++++++++++++++++------- Tests/CSVTests/CSVTests.swift | 187 +++++++++--------- Tests/CSVTests/LineBreakTests.swift | 6 +- Tests/CSVTests/ReadmeTests.swift | 40 ++-- Tests/CSVTests/TrimFieldsTests.swift | 162 ++++++++-------- Tests/CSVTests/UnicodeTests.swift | 20 +- 11 files changed, 405 insertions(+), 496 deletions(-) delete mode 100644 Sources/CSV+init.swift delete mode 100644 Sources/CSV+iterator.swift delete mode 100644 Sources/CSVConfiguration.swift rename Sources/{CSV.swift => CSVReader.swift} (58%) diff --git a/CSV.xcodeproj/project.pbxproj b/CSV.xcodeproj/project.pbxproj index 3018002..ab20d7f 100755 --- a/CSV.xcodeproj/project.pbxproj +++ b/CSV.xcodeproj/project.pbxproj @@ -11,10 +11,6 @@ 0E0F160F1D197DB800C92580 /* Endian.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F160D1D197DB800C92580 /* Endian.swift */; }; 0E0F16101D197DB800C92580 /* Endian.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F160D1D197DB800C92580 /* Endian.swift */; }; 0E0F16111D197DB800C92580 /* Endian.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F160D1D197DB800C92580 /* Endian.swift */; }; - 0E47EEC11DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; - 0E47EEC21DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; - 0E47EEC31DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; - 0E47EEC41DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; }; 0E54021B1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; }; 0E54021C1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; }; 0E54021D1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; }; @@ -23,24 +19,20 @@ 0E5402241EDA82220019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */; }; 0E5402251EDA82230019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */; }; 0E7E8C8C1D0BC7BB0057A1C1 /* CSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E7E8C811D0BC7BB0057A1C1 /* CSV.framework */; }; - 0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; }; + 0E7E8CA11D0BC7F10057A1C1 /* CSVReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */; }; 0E7E8CA21D0BC7F10057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; }; 0E7E8CA31D0BC7F10057A1C1 /* CSVVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 0E7E8CBE1D0BC9D70057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; }; + 0E7E8CBE1D0BC9D70057A1C1 /* CSVReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */; }; 0E7E8CBF1D0BC9D70057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; }; 0E7E8CC01D0BC9D70057A1C1 /* CSVVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0E7E8CD01D0BCA2A0057A1C1 /* CSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E7E8CC61D0BCA2A0057A1C1 /* CSV.framework */; }; - 0E7E8CE01D0BCA8E0057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; }; + 0E7E8CE01D0BCA8E0057A1C1 /* CSVReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */; }; 0E7E8CE11D0BCA8E0057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; }; 0E7E8CE21D0BCA8E0057A1C1 /* CSVVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0E7E8CF21D0BCD0B0057A1C1 /* CSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E7E8CE81D0BCD0B0057A1C1 /* CSV.framework */; }; - 0E7E8D001D0BCDCF0057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; }; + 0E7E8D001D0BCDCF0057A1C1 /* CSVReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */; }; 0E7E8D011D0BCDCF0057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; }; 0E7E8D021D0BCDCF0057A1C1 /* CSVVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 0E9317D41D0DB2F200AC20A0 /* CSV+init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */; }; - 0E9317D51D0DB2F200AC20A0 /* CSV+init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */; }; - 0E9317D61D0DB2F200AC20A0 /* CSV+init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */; }; - 0E9317D71D0DB2F200AC20A0 /* CSV+init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */; }; 0EA2AB7C1D183B45003EC967 /* BinaryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */; }; 0EA2AB7D1D183B45003EC967 /* BinaryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */; }; 0EA2AB7E1D183B45003EC967 /* BinaryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */; }; @@ -49,10 +41,6 @@ 0EA2AB821D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; }; 0EA2AB831D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; }; 0EA2AB841D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; }; - 0EDF8EAF1DDB6D620068056A /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */; }; - 0EDF8EB01DDB6DAC0068056A /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */; }; - 0EDF8EB11DDB6DAC0068056A /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */; }; - 0EDF8EB21DDB6DAD0068056A /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */; }; 0EDF8ED71DDB73520068056A /* CSVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECD1DDB73370068056A /* CSVTests.swift */; }; 0EDF8ED81DDB73520068056A /* LineBreakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECE1DDB73370068056A /* LineBreakTests.swift */; }; 0EDF8ED91DDB73520068056A /* ReadmeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */; }; @@ -96,12 +84,11 @@ /* Begin PBXFileReference section */ 0E0F160D1D197DB800C92580 /* Endian.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Endian.swift; sourceTree = ""; }; - 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSV+iterator.swift"; sourceTree = ""; }; 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVWriterTests.swift; sourceTree = ""; }; 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVWriter.swift; sourceTree = ""; }; 0E7E8C811D0BC7BB0057A1C1 /* CSV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CSV.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8C8B1D0BC7BB0057A1C1 /* CSVTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CSVTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSV.swift; sourceTree = ""; }; + 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVReader.swift; sourceTree = ""; }; 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVError.swift; sourceTree = ""; }; 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSVVersion.h; sourceTree = ""; }; 0E7E8CAC1D0BC8610057A1C1 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -111,10 +98,8 @@ 0E7E8CCF1D0BCA2A0057A1C1 /* CSVTests-OSX.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CSVTests-OSX.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8CE81D0BCD0B0057A1C1 /* CSV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CSV.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8CF11D0BCD0B0057A1C1 /* CSVTests-tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CSVTests-tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSV+init.swift"; sourceTree = ""; }; 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinaryReader.swift; sourceTree = ""; }; 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeIterator.swift; sourceTree = ""; }; - 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVConfiguration.swift; sourceTree = ""; }; 0EDF8ECD1DDB73370068056A /* CSVTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVTests.swift; sourceTree = ""; }; 0EDF8ECE1DDB73370068056A /* LineBreakTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineBreakTests.swift; sourceTree = ""; }; 0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadmeTests.swift; sourceTree = ""; }; @@ -206,11 +191,8 @@ isa = PBXGroup; children = ( 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */, - 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */, - 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */, - 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */, - 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */, 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */, + 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */, 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */, 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */, 0E0F160D1D197DB800C92580 /* Endian.swift */, @@ -544,13 +526,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0E9317D51D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB821D183BA9003EC967 /* UnicodeIterator.swift in Sources */, - 0E47EEC21DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, 0E5402241EDA82220019C3ED /* CSVWriter.swift in Sources */, - 0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */, + 0E7E8CA11D0BC7F10057A1C1 /* CSVReader.swift in Sources */, 0E0F160F1D197DB800C92580 /* Endian.swift in Sources */, - 0EDF8EB01DDB6DAC0068056A /* CSVConfiguration.swift in Sources */, 0E7E8CA21D0BC7F10057A1C1 /* CSVError.swift in Sources */, 0EA2AB7D1D183B45003EC967 /* BinaryReader.swift in Sources */, ); @@ -573,13 +552,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0E9317D71D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB841D183BA9003EC967 /* UnicodeIterator.swift in Sources */, - 0E47EEC41DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, 0E5402221EDA82220019C3ED /* CSVWriter.swift in Sources */, - 0E7E8CBE1D0BC9D70057A1C1 /* CSV.swift in Sources */, + 0E7E8CBE1D0BC9D70057A1C1 /* CSVReader.swift in Sources */, 0E0F16111D197DB800C92580 /* Endian.swift in Sources */, - 0EDF8EB21DDB6DAD0068056A /* CSVConfiguration.swift in Sources */, 0E7E8CBF1D0BC9D70057A1C1 /* CSVError.swift in Sources */, 0EA2AB7F1D183B45003EC967 /* BinaryReader.swift in Sources */, ); @@ -589,13 +565,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0E9317D41D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB811D183BA9003EC967 /* UnicodeIterator.swift in Sources */, - 0E47EEC11DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, 0E5402251EDA82230019C3ED /* CSVWriter.swift in Sources */, - 0E7E8CE01D0BCA8E0057A1C1 /* CSV.swift in Sources */, + 0E7E8CE01D0BCA8E0057A1C1 /* CSVReader.swift in Sources */, 0E0F160E1D197DB800C92580 /* Endian.swift in Sources */, - 0EDF8EAF1DDB6D620068056A /* CSVConfiguration.swift in Sources */, 0E7E8CE11D0BCA8E0057A1C1 /* CSVError.swift in Sources */, 0EA2AB7C1D183B45003EC967 /* BinaryReader.swift in Sources */, ); @@ -618,13 +591,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0E9317D61D0DB2F200AC20A0 /* CSV+init.swift in Sources */, 0EA2AB831D183BA9003EC967 /* UnicodeIterator.swift in Sources */, - 0E47EEC31DBCDB1800EBF783 /* CSV+iterator.swift in Sources */, 0E5402231EDA82220019C3ED /* CSVWriter.swift in Sources */, - 0E7E8D001D0BCDCF0057A1C1 /* CSV.swift in Sources */, + 0E7E8D001D0BCDCF0057A1C1 /* CSVReader.swift in Sources */, 0E0F16101D197DB800C92580 /* Endian.swift in Sources */, - 0EDF8EB11DDB6DAC0068056A /* CSVConfiguration.swift in Sources */, 0E7E8D011D0BCDCF0057A1C1 /* CSVError.swift in Sources */, 0EA2AB7E1D183B45003EC967 /* BinaryReader.swift in Sources */, ); diff --git a/Sources/CSV+init.swift b/Sources/CSV+init.swift deleted file mode 100644 index 261b793..0000000 --- a/Sources/CSV+init.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// CSV+init.swift -// CSV -// -// Created by Yasuhiro Hatta on 2016/06/13. -// Copyright © 2016 yaslab. All rights reserved. -// - -import Foundation - -extension CSV { - - /// Create an instance with `InputStream`. - /// - /// - parameter stream: An `InputStream` object. If the stream is not open, - /// initializer opens automatically. - /// - parameter config: CSV configuration. - public convenience init( - stream: InputStream, - config: CSVConfiguration = CSVConfiguration()) throws { - - try self.init(stream: stream, codecType: UTF8.self, config: config) - } - -} - -extension CSV { - - /// Create an instance with CSV string. - /// - /// - parameter string: An CSV string. - /// - parameter config: CSV configuration. - public convenience init( - string: String, - config: CSVConfiguration = CSVConfiguration()) throws { - - let iterator = string.unicodeScalars.makeIterator() - try self.init(iterator: iterator, config: config) - } - -} diff --git a/Sources/CSV+iterator.swift b/Sources/CSV+iterator.swift deleted file mode 100644 index fc8e9a8..0000000 --- a/Sources/CSV+iterator.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// CSV+iterator.swift -// CSV -// -// Created by Yasuhiro Hatta on 2016/10/23. -// Copyright © 2016年 yaslab. All rights reserved. -// - -extension CSV: IteratorProtocol, Sequence { - - /// No overview available. - public func next() -> Row? { - guard let row = readRow() else { - return nil - } - return Row(data: row, headerRow: headerRow) - } - -} - -extension CSV { - - /// No overview available. - public class Row: RandomAccessCollection { - - private let data: [String] - private let headerRow: [String]? - - internal init(data: [String], headerRow: [String]?) { - self.data = data - self.headerRow = headerRow - } - - // MARK: - RandomAccessCollection - - /// No overview available. - public var startIndex: Int { - return data.startIndex - } - - /// No overview available. - public var endIndex: Int { - return data.endIndex - } - - /// No overview available. - public func index(before i: Int) -> Int { - return data.index(before: i) - } - - /// No overview available. - public func index(after i: Int) -> Int { - return data.index(after: i) - } - - /// No overview available. - public subscript(index: Int) -> String { - return data[index] - } - - // MARK: - Public method - - /// No overview available. - public subscript(key: String) -> String? { - assert(headerRow != nil, "CSVConfiguration.hasHeaderRow must be true") - guard let index = headerRow!.index(of: key) else { - return nil - } - guard (data.startIndex ..< data.endIndex).contains(index) else { - return nil - } - return data[index] - } - - /// No overview available. - public func toArray() -> [String] { - return data - } - - /// No overview available. - public func toDictionary() -> [String : String] { - assert(headerRow != nil, "CSVConfiguration.hasHeaderRow must be true") - var dictionary: [String : String] = [:] - for (key, value) in zip(headerRow!, data) { - if !dictionary.keys.contains(key) { - dictionary[key] = value - } - } - return dictionary - } - - } - -} diff --git a/Sources/CSVConfiguration.swift b/Sources/CSVConfiguration.swift deleted file mode 100644 index 1313551..0000000 --- a/Sources/CSVConfiguration.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// CSVConfiguration.swift -// CSV -// -// Created by Yasuhiro Hatta on 2016/10/22. -// Copyright © 2016 yaslab. All rights reserved. -// - -import Foundation - -internal let defaultHasHeaderRow = false -internal let defaultTrimFields = false -internal let defaultDelimiter: UnicodeScalar = "," -internal let defaultWhitespaces = CharacterSet.whitespaces - -/// No overview available. -public class CSVConfiguration { - - public var fileInputErrorHandler: ((Error, Int, Int) -> Void)? = nil - - /// `true` if the CSV has a header row, otherwise `false`. Default: `false`. - public let hasHeaderRow: Bool - /// No overview available. - public let trimFields: Bool - /// Default: `","`. - public let delimiter: UnicodeScalar - /// No overview available. - public let whitespaces: CharacterSet - - /// No overview available. - public init( - hasHeaderRow: Bool = defaultHasHeaderRow, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter, - whitespaces: CharacterSet = defaultWhitespaces) { - - self.hasHeaderRow = hasHeaderRow - self.trimFields = trimFields - self.delimiter = delimiter - - var whitespaces = whitespaces - _ = whitespaces.remove(delimiter) - self.whitespaces = whitespaces - } - -} diff --git a/Sources/CSVError.swift b/Sources/CSVError.swift index bfa2183..81743ee 100644 --- a/Sources/CSVError.swift +++ b/Sources/CSVError.swift @@ -20,7 +20,7 @@ public enum CSVError: Error { /// No overview available. case unicodeDecoding /// No overview available. - case cannotReadHeaderRow + case cannotReadHeaderRecord /// No overview available. case stringEncodingMismatch /// No overview available. diff --git a/Sources/CSV.swift b/Sources/CSVReader.swift similarity index 58% rename from Sources/CSV.swift rename to Sources/CSVReader.swift index 6ce88e9..36f3311 100644 --- a/Sources/CSV.swift +++ b/Sources/CSVReader.swift @@ -1,5 +1,5 @@ // -// CSV.swift +// CSVReader.swift // CSV // // Created by Yasuhiro Hatta on 2016/06/11. @@ -15,38 +15,82 @@ internal let DQUOTE: UnicodeScalar = "\"" internal let DQUOTE_STR: String = "\"" internal let DQUOTE2_STR: String = "\"\"" +internal let defaultHasHeaderRecord = false +internal let defaultTrimFields = false +internal let defaultDelimiter: UnicodeScalar = "," +internal let defaultWhitespaces = CharacterSet.whitespaces + /// No overview available. -public class CSV { +public class CSVReader { + + /// No overview available. + public struct Configuration { + + public var fileInputErrorHandler: ((Error, Int, Int) -> Void)? = nil + + /// `true` if the CSV has a header record, otherwise `false`. Default: `false`. + public var hasHeaderRecord: Bool + /// No overview available. + public var trimFields: Bool + /// Default: `","`. + public var delimiter: UnicodeScalar + /// No overview available. + public var whitespaces: CharacterSet + + /// No overview available. + public init( + hasHeaderRecord: Bool = defaultHasHeaderRecord, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter, + whitespaces: CharacterSet = defaultWhitespaces) { + + self.hasHeaderRecord = hasHeaderRecord + self.trimFields = trimFields + self.delimiter = delimiter + + var whitespaces = whitespaces + _ = whitespaces.remove(delimiter) + self.whitespaces = whitespaces + } + + } - private var iterator: AnyIterator - private let config: CSVConfiguration + fileprivate var iterator: AnyIterator + //public let stream: InputStream? + public let configuration: Configuration - private var back: UnicodeScalar? = nil - private var fieldBuffer = String.UnicodeScalarView() + fileprivate var back: UnicodeScalar? = nil + fileprivate var fieldBuffer = String.UnicodeScalarView() - private var currentRowIndex: Int = 0 - private var currentFieldIndex: Int = 0 + fileprivate var currentRecordIndex: Int = 0 + fileprivate var currentFieldIndex: Int = 0 - /// CSV header row. To set a value for this property, - /// you set `true` to `hasHeaerRow` in initializer. - public private(set) var headerRow: [String]? = nil + /// CSV header record. To set a value for this property, + /// you set `true` to `headerRecord` in initializer. + public private (set) var headerRecord: [String]? = nil + public fileprivate (set) var currentRecord: [String]? = nil + internal init( iterator: T, - config: CSVConfiguration + configuration: Configuration ) throws where T.Element == UnicodeScalar { self.iterator = AnyIterator(iterator) - self.config = config + self.configuration = configuration - if config.hasHeaderRow { - guard let headerRow = readRow() else { - throw CSVError.cannotReadHeaderRow + if configuration.hasHeaderRecord { + guard let headerRecord = readRecord() else { + throw CSVError.cannotReadHeaderRecord } - self.headerRow = headerRow + self.headerRecord = headerRecord } } +} + +extension CSVReader { + /// Create an instance with `InputStream`. /// /// - parameter stream: An `InputStream` object. If the stream is not open, @@ -56,13 +100,13 @@ public class CSV { public convenience init( stream: InputStream, codecType: T.Type, - config: CSVConfiguration = CSVConfiguration() + configuration: Configuration = Configuration() ) throws where T.CodeUnit == UInt8 { let reader = try BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true) let input = reader.makeUInt8Iterator() let iterator = UnicodeIterator(input: input, inputEncodingType: codecType) - try self.init(iterator: iterator, config: config) + try self.init(iterator: iterator, configuration: configuration) input.errorHandler = { [unowned self] in self.errorHandler(error: $0) } iterator.errorHandler = { [unowned self] in self.errorHandler(error: $0) } } @@ -78,13 +122,13 @@ public class CSV { stream: InputStream, codecType: T.Type, endian: Endian = .big, - config: CSVConfiguration = CSVConfiguration() + configuration: Configuration = Configuration() ) throws where T.CodeUnit == UInt16 { let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) let input = reader.makeUInt16Iterator() let iterator = UnicodeIterator(input: input, inputEncodingType: codecType) - try self.init(iterator: iterator, config: config) + try self.init(iterator: iterator, configuration: configuration) input.errorHandler = { [unowned self] in self.errorHandler(error: $0) } iterator.errorHandler = { [unowned self] in self.errorHandler(error: $0) } } @@ -100,20 +144,52 @@ public class CSV { stream: InputStream, codecType: T.Type, endian: Endian = .big, - config: CSVConfiguration = CSVConfiguration() + configuration: Configuration = Configuration() ) throws where T.CodeUnit == UInt32 { let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) let input = reader.makeUInt32Iterator() let iterator = UnicodeIterator(input: input, inputEncodingType: codecType) - try self.init(iterator: iterator, config: config) + try self.init(iterator: iterator, configuration: configuration) input.errorHandler = { [unowned self] in self.errorHandler(error: $0) } iterator.errorHandler = { [unowned self] in self.errorHandler(error: $0) } } + + /// Create an instance with `InputStream`. + /// + /// - parameter stream: An `InputStream` object. If the stream is not open, + /// initializer opens automatically. + /// - parameter config: CSV configuration. + public convenience init( + stream: InputStream, + configuration: Configuration = Configuration()) throws { + + try self.init(stream: stream, codecType: UTF8.self, configuration: configuration) + } + + /// Create an instance with CSV string. + /// + /// - parameter string: An CSV string. + /// - parameter config: CSV configuration. + public convenience init( + string: String, + configuration: Configuration = Configuration()) throws { + + let iterator = string.unicodeScalars.makeIterator() + try self.init(iterator: iterator, configuration: configuration) + } - // MARK: - Parse CSV + private func errorHandler(error: Error) { + configuration.fileInputErrorHandler?(error, currentRecordIndex, currentFieldIndex) + } + +} - internal func readRow() -> [String]? { +// MARK: - Parse CSV + +extension CSVReader { + + fileprivate func readRecord() -> [String]? { currentFieldIndex = 0 var c = moveNext() @@ -121,13 +197,13 @@ public class CSV { return nil } - var row = [String]() + var record = [String]() var field: String var end: Bool while true { - if config.trimFields { + if configuration.trimFields { // Trim the leading spaces - while c != nil && config.whitespaces.contains(c!) { + while c != nil && configuration.whitespaces.contains(c!) { c = moveNext() } } @@ -140,12 +216,12 @@ public class CSV { back = c (field, end) = readField(quoted: false) - if config.trimFields { + if configuration.trimFields { // Trim the trailing spaces - field = field.trimmingCharacters(in: config.whitespaces) + field = field.trimmingCharacters(in: configuration.whitespaces) } } - row.append(field) + record.append(field) if end { break } @@ -155,9 +231,10 @@ public class CSV { c = moveNext() } - currentRowIndex += 1 + currentRecordIndex += 1 - return row + currentRecord = record + return record } private func readField(quoted: Bool) -> (String, Bool) { @@ -168,9 +245,9 @@ public class CSV { if c == DQUOTE { var cNext = moveNext() - if config.trimFields { + if configuration.trimFields { // Trim the trailing spaces - while cNext != nil && config.whitespaces.contains(cNext!) { + while cNext != nil && configuration.whitespaces.contains(cNext!) { cNext = moveNext() } } @@ -182,9 +259,9 @@ public class CSV { back = cNextNext } } - // END ROW + // END RECORD return (String(fieldBuffer), true) - } else if cNext == config.delimiter { + } else if cNext == configuration.delimiter { // END FIELD return (String(fieldBuffer), false) } else if cNext == DQUOTE { @@ -205,9 +282,9 @@ public class CSV { back = cNext } } - // END ROW + // END RECORD return (String(fieldBuffer), true) - } else if c == config.delimiter { + } else if c == configuration.delimiter { // END FIELD return (String(fieldBuffer), false) } else { @@ -230,18 +307,61 @@ public class CSV { return iterator.next() } - private func errorHandler(error: Error) { - config.fileInputErrorHandler?(error, currentRowIndex, currentFieldIndex) +} + +extension CSVReader { + + public func enumerateRecords(_ block: (([String], [String]?, inout Bool) throws -> Void)) rethrows { + var stop = false + while let record = readRecord() { + try block(record, headerRecord, &stop) + if stop { + break + } + } } + +} - // MARK: - deprecated +extension CSVReader: IteratorProtocol { + + @discardableResult + public func next() -> [String]? { + return readRecord() + } + +} +extension CSVReader { + + public subscript(key: String) -> String? { + guard let header = headerRecord else { + fatalError("CSVReader.headerRecord must not be nil") + } + guard let index = header.index(of: key) else { + return nil + } + guard let record = currentRecord else { + fatalError("CSVReader.currentRecord must not be nil") + } + if index >= record.count { + return nil + } + return record[index] + } + +} + +// MARK: - deprecated + +extension CSVReader { + /// Unavailable. @available(*, unavailable, message: "Use init(stream:codecType:config:) instead") public convenience init( stream: InputStream, codecType: T.Type, - hasHeaderRow: Bool = defaultHasHeaderRow, + hasHeaderRow: Bool = defaultHasHeaderRecord, trimFields: Bool = defaultTrimFields, delimiter: UnicodeScalar = defaultDelimiter ) throws where T.CodeUnit == UInt8 { @@ -251,12 +371,12 @@ public class CSV { input: reader.makeUInt8Iterator(), inputEncodingType: codecType ) - let config = CSVConfiguration( - hasHeaderRow: hasHeaderRow, + let config = Configuration( + hasHeaderRecord: hasHeaderRow, trimFields: trimFields, delimiter: delimiter ) - try self.init(iterator: iterator, config: config) + try self.init(iterator: iterator, configuration: config) } /// Unavailable. @@ -265,7 +385,7 @@ public class CSV { stream: InputStream, codecType: T.Type, endian: Endian = .big, - hasHeaderRow: Bool = defaultHasHeaderRow, + hasHeaderRow: Bool = defaultHasHeaderRecord, trimFields: Bool = defaultTrimFields, delimiter: UnicodeScalar = defaultDelimiter ) throws where T.CodeUnit == UInt16 { @@ -275,12 +395,12 @@ public class CSV { input: reader.makeUInt16Iterator(), inputEncodingType: codecType ) - let config = CSVConfiguration( - hasHeaderRow: hasHeaderRow, + let config = Configuration( + hasHeaderRecord: hasHeaderRow, trimFields: trimFields, delimiter: delimiter ) - try self.init(iterator: iterator, config: config) + try self.init(iterator: iterator, configuration: config) } /// Unavailable. @@ -289,7 +409,7 @@ public class CSV { stream: InputStream, codecType: T.Type, endian: Endian = .big, - hasHeaderRow: Bool = defaultHasHeaderRow, + hasHeaderRow: Bool = defaultHasHeaderRecord, trimFields: Bool = defaultTrimFields, delimiter: UnicodeScalar = defaultDelimiter ) throws where T.CodeUnit == UInt32 { @@ -299,51 +419,52 @@ public class CSV { input: reader.makeUInt32Iterator(), inputEncodingType: codecType ) - let config = CSVConfiguration( - hasHeaderRow: hasHeaderRow, + let config = Configuration( + hasHeaderRecord: hasHeaderRow, trimFields: trimFields, delimiter: delimiter ) - try self.init(iterator: iterator, config: config) + try self.init(iterator: iterator, configuration: config) } /// Unavailable. @available(*, unavailable, message: "Use init(stream:config:) instead") public convenience init( stream: InputStream, - hasHeaderRow: Bool = defaultHasHeaderRow, + hasHeaderRow: Bool = defaultHasHeaderRecord, trimFields: Bool = defaultTrimFields, delimiter: UnicodeScalar = defaultDelimiter) throws { - let config = CSVConfiguration( - hasHeaderRow: hasHeaderRow, + let config = Configuration( + hasHeaderRecord: hasHeaderRow, trimFields: trimFields, delimiter: delimiter ) - try self.init(stream: stream, codecType: UTF8.self, config: config) + try self.init(stream: stream, codecType: UTF8.self, configuration: config) } /// Unavailable. @available(*, unavailable, message: "Use init(string:config:) instead") public convenience init( string: String, - hasHeaderRow: Bool = defaultHasHeaderRow, + hasHeaderRow: Bool = defaultHasHeaderRecord, trimFields: Bool = defaultTrimFields, delimiter: UnicodeScalar = defaultDelimiter) throws { let iterator = string.unicodeScalars.makeIterator() - let config = CSVConfiguration( - hasHeaderRow: hasHeaderRow, + let config = Configuration( + hasHeaderRecord: hasHeaderRow, trimFields: trimFields, delimiter: delimiter ) - try self.init(iterator: iterator, config: config) + try self.init(iterator: iterator, configuration: config) } /// Unavailable - @available(*, unavailable, message: "Use CSV.Row.subscript(String) instead") - public subscript(key: String) -> String? { - return nil - } +// @available(*, unavailable, message: "Use CSV.Row.subscript(String) instead") +// public subscript(key: String) -> String? { +// // FIXME: +// return nil +// } } diff --git a/Tests/CSVTests/CSVTests.swift b/Tests/CSVTests/CSVTests.swift index 7030c45..fbcdd20 100644 --- a/Tests/CSVTests/CSVTests.swift +++ b/Tests/CSVTests/CSVTests.swift @@ -27,16 +27,17 @@ class CSVTests: XCTestCase { ("testSubscriptString1", testSubscriptString1), ("testSubscriptString2", testSubscriptString2), ("testToArray", testToArray), - ("testToDictionary1", testToDictionary1), - ("testToDictionary2", testToDictionary2) + //("testToDictionary1", testToDictionary1), + //("testToDictionary2", testToDictionary2) ] func testOneLine() { let csv = "\"abc\",1,2" var i = 0 - for row in try! CSV(string: csv) { + + for record in AnyIterator(try! CSVReader(string: csv)) { switch i { - case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"]) + case 0: XCTAssertEqual(record, ["abc", "1", "2"]) default: break } i += 1 @@ -47,10 +48,10 @@ class CSVTests: XCTestCase { func testTwoLines() { let csv = "\"abc\",1,2\n\"cde\",3,4" var i = 0 - for row in try! CSV(string: csv) { + for record in AnyIterator(try! CSVReader(string: csv)) { switch i { - case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"]) - case 1: XCTAssertEqual(row.toArray(), ["cde", "3", "4"]) + case 0: XCTAssertEqual(record, ["abc", "1", "2"]) + case 1: XCTAssertEqual(record, ["cde", "3", "4"]) default: break } i += 1 @@ -61,10 +62,10 @@ class CSVTests: XCTestCase { func testLastLineIsEmpty() { let csv = "\"abc\",1,2\n\"cde\",3,4\n" var i = 0 - for row in try! CSV(string: csv) { + for record in AnyIterator(try! CSVReader(string: csv)) { switch i { - case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"]) - case 1: XCTAssertEqual(row.toArray(), ["cde", "3", "4"]) + case 0: XCTAssertEqual(record, ["abc", "1", "2"]) + case 1: XCTAssertEqual(record, ["cde", "3", "4"]) default: break } i += 1 @@ -75,11 +76,11 @@ class CSVTests: XCTestCase { func testLastLineIsWhiteSpace() { let csv = "\"abc\",1,2\n\"cde\",3,4\n " var i = 0 - for row in try! CSV(string: csv) { + for record in AnyIterator(try! CSVReader(string: csv)) { switch i { - case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"]) - case 1: XCTAssertEqual(row.toArray(), ["cde", "3", "4"]) - case 2: XCTAssertEqual(row.toArray(), [" "]) + case 0: XCTAssertEqual(record, ["abc", "1", "2"]) + case 1: XCTAssertEqual(record, ["cde", "3", "4"]) + case 2: XCTAssertEqual(record, [" "]) default: break } i += 1 @@ -90,11 +91,11 @@ class CSVTests: XCTestCase { func testMiddleLineIsEmpty() { let csv = "\"abc\",1,2\n\n\"cde\",3,4" var i = 0 - for row in try! CSV(string: csv) { + for record in AnyIterator(try! CSVReader(string: csv)) { switch i { - case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"]) - case 1: XCTAssertEqual(row.toArray(), [""]) - case 2: XCTAssertEqual(row.toArray(), ["cde", "3", "4"]) + case 0: XCTAssertEqual(record, ["abc", "1", "2"]) + case 1: XCTAssertEqual(record, [""]) + case 2: XCTAssertEqual(record, ["cde", "3", "4"]) default: break } i += 1 @@ -104,46 +105,46 @@ class CSVTests: XCTestCase { func testCommaInQuotationMarks() { let csvString = "abab,\"cd,cd\",efef" - let csv = try! CSV(string: csvString) - let row = csv.next()! - XCTAssertEqual(row.toArray(), ["abab", "cd,cd", "efef"]) + let csv = try! CSVReader(string: csvString) + let record = csv.next()! + XCTAssertEqual(record, ["abab", "cd,cd", "efef"]) } func testEscapedQuotationMark1() { let csvString = "abab,\"\"\"cdcd\",efef\r\nzxcv,asdf,qwer" - let csv = try! CSV(string: csvString) - var row = csv.next()! - XCTAssertEqual(row.toArray(), ["abab", "\"cdcd", "efef"]) - row = csv.next()! - XCTAssertEqual(row.toArray(), ["zxcv", "asdf", "qwer"]) + let csv = try! CSVReader(string: csvString) + var record = csv.next()! + XCTAssertEqual(record, ["abab", "\"cdcd", "efef"]) + record = csv.next()! + XCTAssertEqual(record, ["zxcv", "asdf", "qwer"]) } func testEscapedQuotationMark2() { let csvString = "abab,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\"" - let csv = try! CSV(string: csvString) - var row = csv.next()! - XCTAssertEqual(row.toArray(), ["abab", "cdcd", "efef"]) - row = csv.next()! - XCTAssertEqual(row.toArray(), ["zxcv", "asdf", "qw\"er"]) + let csv = try! CSVReader(string: csvString) + var record = csv.next()! + XCTAssertEqual(record, ["abab", "cdcd", "efef"]) + record = csv.next()! + XCTAssertEqual(record, ["zxcv", "asdf", "qw\"er"]) } func testEmptyField() { let csvString = "abab,,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\"," - let csv = try! CSV(string: csvString) - var row = csv.next()! - XCTAssertEqual(row.toArray(), ["abab", "", "cdcd", "efef"]) - row = csv.next()! - XCTAssertEqual(row.toArray(), ["zxcv", "asdf", "qw\"er", ""]) + let csv = try! CSVReader(string: csvString) + var record = csv.next()! + XCTAssertEqual(record, ["abab", "", "cdcd", "efef"]) + record = csv.next()! + XCTAssertEqual(record, ["zxcv", "asdf", "qw\"er", ""]) } func testDoubleQuoteBeforeLineBreak() { let csv = "\"abc\",1,\"2\"\n\n\"cde\",3,\"4\"" var i = 0 - for row in try! CSV(string: csv) { + for record in AnyIterator(try! CSVReader(string: csv)) { switch i { - case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"]) - case 1: XCTAssertEqual(row.toArray(), [""]) - case 2: XCTAssertEqual(row.toArray(), ["cde", "3", "4"]) + case 0: XCTAssertEqual(record, ["abc", "1", "2"]) + case 1: XCTAssertEqual(record, [""]) + case 2: XCTAssertEqual(record, ["cde", "3", "4"]) default: break } i += 1 @@ -153,82 +154,80 @@ class CSVTests: XCTestCase { func testCSVState1() { let it = "あ,い1,\"う\",えお\n,,x,".unicodeScalars.makeIterator() - let csv = try! CSV(iterator: it, config: CSVConfiguration()) + let csv = try! CSVReader(iterator: it, configuration: CSVReader.Configuration()) - var rows = [[String]]() + var records = [[String]]() - while let row = csv.next() { - rows.append(row.toArray()) + while let record = csv.next() { + records.append(record) } - XCTAssertEqual(rows.count, 2) - XCTAssertEqual(rows[0], ["あ", "い1", "う", "えお"]) - XCTAssertEqual(rows[1], ["", "", "x", ""]) + XCTAssertEqual(records.count, 2) + XCTAssertEqual(records[0], ["あ", "い1", "う", "えお"]) + XCTAssertEqual(records[1], ["", "", "x", ""]) } func testSubscriptInt() { let csvString = "a,bb,ccc" - let csv = try! CSV(string: csvString) - for row in csv { - XCTAssertEqual(row[0], "a") - XCTAssertEqual(row[1], "bb") - XCTAssertEqual(row[2], "ccc") + let csv = try! CSVReader(string: csvString) + for record in AnyIterator(csv) { + XCTAssertEqual(record[0], "a") + XCTAssertEqual(record[1], "bb") + XCTAssertEqual(record[2], "ccc") } } func testSubscriptString1() { let csvString = "key1,key2\nvalue1,value2" - let config = CSVConfiguration(hasHeaderRow: true) - let csv = try! CSV(string: csvString, config: config) - for row in csv { - XCTAssertEqual(row["key1"], "value1") - XCTAssertEqual(row["key2"], "value2") - XCTAssertNil(row["key9"]) - } + let config = CSVReader.Configuration(hasHeaderRecord: true) + let csv = try! CSVReader(string: csvString, configuration: config) + csv.next() + XCTAssertEqual(csv["key1"], "value1") + XCTAssertEqual(csv["key2"], "value2") + XCTAssertNil(csv["key9"]) } func testSubscriptString2() { let csvString = "key1,key2\nvalue1" - let config = CSVConfiguration(hasHeaderRow: true) - let csv = try! CSV(string: csvString, config: config) - for row in csv { - XCTAssertEqual(row["key1"], "value1") - XCTAssertNil(row["key2"]) - XCTAssertNil(row["key9"]) - } + let config = CSVReader.Configuration(hasHeaderRecord: true) + let csv = try! CSVReader(string: csvString, configuration: config) + csv.next() + XCTAssertEqual(csv["key1"], "value1") + XCTAssertNil(csv["key2"]) + XCTAssertNil(csv["key9"]) } func testToArray() { let csvString = "1,2,3,4,5\n6,7,8,9,0" - let csv = try! CSV(string: csvString) - let rows = csv.map { $0.toArray() } - XCTAssertEqual(rows[0], ["1", "2", "3", "4", "5"]) - XCTAssertEqual(rows[1], ["6", "7", "8", "9", "0"]) + let csv = try! CSVReader(string: csvString) + let records = AnyIterator(csv).map { $0 } + XCTAssertEqual(records[0], ["1", "2", "3", "4", "5"]) + XCTAssertEqual(records[1], ["6", "7", "8", "9", "0"]) } - func testToDictionary1() { - let csvString = "id,name\n1,name1\n2,name2" - let config = CSVConfiguration(hasHeaderRow: true) - let csv = try! CSV(string: csvString, config: config) - let rows = csv.map { $0.toDictionary() } - XCTAssertEqual(rows[0]["id"], "1") - XCTAssertEqual(rows[0]["name"], "name1") - XCTAssertNil(rows[0]["xxx"]) - XCTAssertEqual(rows[1]["id"], "2") - XCTAssertEqual(rows[1]["name"], "name2") - XCTAssertNil(rows[1]["yyy"]) - } +// func testToDictionary1() { +// let csvString = "id,name\n1,name1\n2,name2" +// let config = CSVReader.Configuration(hasHeaderRow: true) +// let csv = try! CSVReader(string: csvString, configuration: config) +// let rows = AnyIterator(csv).map { $0.toDictionary() } +// XCTAssertEqual(rows[0]["id"], "1") +// XCTAssertEqual(rows[0]["name"], "name1") +// XCTAssertNil(rows[0]["xxx"]) +// XCTAssertEqual(rows[1]["id"], "2") +// XCTAssertEqual(rows[1]["name"], "name2") +// XCTAssertNil(rows[1]["yyy"]) +// } - func testToDictionary2() { - let csvString = "id,name,id\n1,name1,11\n2,name2,22" - let config = CSVConfiguration(hasHeaderRow: true) - let csv = try! CSV(string: csvString, config: config) - let rows = csv.map { $0.toDictionary() } - XCTAssertEqual(rows[0]["id"], "1") - XCTAssertEqual(rows[0]["name"], "name1") - XCTAssertNil(rows[0]["xxx"]) - XCTAssertEqual(rows[1]["id"], "2") - XCTAssertEqual(rows[1]["name"], "name2") - XCTAssertNil(rows[1]["yyy"]) - } +// func testToDictionary2() { +// let csvString = "id,name,id\n1,name1,11\n2,name2,22" +// let config = CSVReader.Configuration(hasHeaderRow: true) +// let csv = try! CSVReader(string: csvString, configuration: config) +// let rows = AnyIterator(csv).map { $0.toDictionary() } +// XCTAssertEqual(rows[0]["id"], "1") +// XCTAssertEqual(rows[0]["name"], "name1") +// XCTAssertNil(rows[0]["xxx"]) +// XCTAssertEqual(rows[1]["id"], "2") +// XCTAssertEqual(rows[1]["name"], "name2") +// XCTAssertNil(rows[1]["yyy"]) +// } } diff --git a/Tests/CSVTests/LineBreakTests.swift b/Tests/CSVTests/LineBreakTests.swift index 495fa4f..37d04a0 100644 --- a/Tests/CSVTests/LineBreakTests.swift +++ b/Tests/CSVTests/LineBreakTests.swift @@ -124,10 +124,10 @@ class LineBreakTests: XCTestCase { } private func parse(csv: String) -> [[String]] { - let reader = try! CSV(string: csv) + let reader = try! CSVReader(string: csv) var records = [[String]]() - for row in reader { - records.append(row.toArray()) + reader.enumerateRecords { (record, _, _) in + records.append(record) } return records } diff --git a/Tests/CSVTests/ReadmeTests.swift b/Tests/CSVTests/ReadmeTests.swift index 4e445c2..cde14f4 100644 --- a/Tests/CSVTests/ReadmeTests.swift +++ b/Tests/CSVTests/ReadmeTests.swift @@ -21,9 +21,9 @@ class ReadmeTests: XCTestCase { ] func testFromCSVString() { - let csv = try! CSV(string: "1,foo\n2,bar") - for row in csv { - print("\(row)") + let csv = try! CSVReader(string: "1,foo\n2,bar") + csv.enumerateRecords { (record, _, _) in + print("\(record)") // => ["1", "foo"] // => ["2", "bar"] } @@ -39,14 +39,14 @@ class ReadmeTests: XCTestCase { func testGettingTheHeaderRow() { let csvString = "id,name\n1,foo\n2,bar" - let config = CSVConfiguration(hasHeaderRow: true) // It must be true. - let csv = try! CSV(string: csvString, config: config) + let config = CSVReader.Configuration(hasHeaderRecord: true) // It must be true. + let csv = try! CSVReader(string: csvString, configuration: config) - let headerRow = csv.headerRow! + let headerRow = csv.headerRecord! print("\(headerRow)") // => ["id", "name"] - for row in csv { - print("\(row)") + csv.enumerateRecords { (record, _, _) in + print("\(record)") // => ["1", "foo"] // => ["2", "bar"] } @@ -54,23 +54,23 @@ class ReadmeTests: XCTestCase { func testGetTheFieldValueUsingIndex() { let csvString = "1,foo" - let csv = try! CSV(string: csvString) + let csv = try! CSVReader(string: csvString) - for row in csv { - print("\(row[0])") // => "1" - print("\(row[1])") // => "foo" + csv.enumerateRecords { (record, _, _) in + print("\(record[0])") // => "1" + print("\(record[1])") // => "foo" } } func testGetTheFieldValueUsingKey() { - let csvString = "id,name\n1,foo" - let config = CSVConfiguration(hasHeaderRow: true) // It must be true. - let csv = try! CSV(string: csvString, config: config) - - for row in csv { - print("\(row["id"]!)") // => "1" - print("\(row["name"]!)") // => "foo" - } +// let csvString = "id,name\n1,foo" +// let config = CSVReader.Configuration(hasHeaderRow: true) // It must be true. +// let csv = try! CSVReader(string: csvString, configuration: config) +// +// csv.enumerateRecords { (record, _, _) in +// print("\(record["id"]!)") // => "1" +// print("\(record["name"]!)") // => "foo" +// } } func testProvideTheCharacterEncoding() { diff --git a/Tests/CSVTests/TrimFieldsTests.swift b/Tests/CSVTests/TrimFieldsTests.swift index 43e477a..0a7719e 100644 --- a/Tests/CSVTests/TrimFieldsTests.swift +++ b/Tests/CSVTests/TrimFieldsTests.swift @@ -29,173 +29,173 @@ class TrimFieldsTests: XCTestCase { func testTrimFields1() { let csvString = "abc,def,ghi" - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) - for row in csv { - XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } func testTrimFields2() { let csvString = " abc, def, ghi" - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) - for row in csv { - XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } func testTrimFields3() { let csvString = "abc ,def ,ghi " - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) - for row in csv { - XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } func testTrimFields4() { let csvString = " abc , def , ghi " - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) - for row in csv { - XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } func testTrimFields5() { let csvString = "\"abc\",\"def\",\"ghi\"" - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) - for row in csv { - XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } func testTrimFields6() { let csvString = " \"abc\", \"def\", \"ghi\"" - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) - for row in csv { - XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } func testTrimFields7() { let csvString = "\"abc\" ,\"def\" ,\"ghi\" " - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) - for row in csv { - XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } func testTrimFields8() { let csvString = " \"abc\" , \"def\" , \"ghi\" " - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) - for row in csv { - XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } func testTrimFields9() { let csvString = "\" abc \",\" def \",\" ghi \"" - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) - for row in csv { - XCTAssertEqual(row.toArray(), [" abc ", " def ", " ghi "]) + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) + for record in AnyIterator(csv) { + XCTAssertEqual(record, [" abc ", " def ", " ghi "]) } } func testTrimFields10() { let csvString = "\tabc,\t\tdef\t,ghi\t" - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) - for row in csv { - XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"]) + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "def", "ghi"]) } } func testTrimFields11() { let csvString = " abc \n def " - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) - let row1 = csv.next()! - XCTAssertEqual(row1.toArray(), ["abc"]) - let row2 = csv.next()! - XCTAssertEqual(row2.toArray(), ["def"]) + let record1 = csv.next()! + XCTAssertEqual(record1, ["abc"]) + let record2 = csv.next()! + XCTAssertEqual(record2, ["def"]) } func testTrimFields12() { let csvString = " \"abc \" \n \" def\" " - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) - let row1 = csv.next()! - XCTAssertEqual(row1.toArray(), ["abc "]) - let row2 = csv.next()! - XCTAssertEqual(row2.toArray(), [" def"]) + let record1 = csv.next()! + XCTAssertEqual(record1, ["abc "]) + let record2 = csv.next()! + XCTAssertEqual(record2, [" def"]) } func testTrimFields13() { let csvString = " abc \t\tdef\t ghi " - let config = CSVConfiguration(trimFields: true, delimiter: UnicodeScalar("\t")!) - let csv = try! CSV(string: csvString, config: config) - for row in csv { - XCTAssertEqual(row.toArray(), ["abc", "", "def", "ghi"]) + let config = CSVReader.Configuration(trimFields: true, delimiter: UnicodeScalar("\t")!) + let csv = try! CSVReader(string: csvString, configuration: config) + for record in AnyIterator(csv) { + XCTAssertEqual(record, ["abc", "", "def", "ghi"]) } } func testTrimFields14() { let csvString = "" - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) - let rows = csv.map { $0.toArray() } + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) + let records = AnyIterator(csv).map { $0 } - XCTAssertEqual(rows.count, 0) + XCTAssertEqual(records.count, 0) } func testTrimFields15() { let csvString = " " - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) - let rows = csv.map { $0.toArray() } + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) + let records = AnyIterator(csv).map { $0 } - XCTAssertEqual(rows.count, 1) - XCTAssertEqual(rows[0], [""]) + XCTAssertEqual(records.count, 1) + XCTAssertEqual(records[0], [""]) } func testTrimFields16() { let csvString = " , " - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) - let rows = csv.map { $0.toArray() } + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) + let records = AnyIterator(csv).map { $0 } - XCTAssertEqual(rows.count, 1) - XCTAssertEqual(rows[0], ["", ""]) + XCTAssertEqual(records.count, 1) + XCTAssertEqual(records[0], ["", ""]) } func testTrimFields17() { let csvString = " , \n" - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) - let rows = csv.map { $0.toArray() } + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) + let records = AnyIterator(csv).map { $0 } - XCTAssertEqual(rows.count, 1) - XCTAssertEqual(rows[0], ["", ""]) + XCTAssertEqual(records.count, 1) + XCTAssertEqual(records[0], ["", ""]) } func testTrimFields18() { let csvString = " , \n " - let config = CSVConfiguration(trimFields: true) - let csv = try! CSV(string: csvString, config: config) - let rows = csv.map { $0.toArray() } + let config = CSVReader.Configuration(trimFields: true) + let csv = try! CSVReader(string: csvString, configuration: config) + let records = AnyIterator(csv).map { $0 } - XCTAssertEqual(rows.count, 2) - XCTAssertEqual(rows[0], ["", ""]) - XCTAssertEqual(rows[1], [""]) + XCTAssertEqual(records.count, 2) + XCTAssertEqual(records[0], ["", ""]) + XCTAssertEqual(records[1], [""]) } } diff --git a/Tests/CSVTests/UnicodeTests.swift b/Tests/CSVTests/UnicodeTests.swift index 5697f68..5369681 100644 --- a/Tests/CSVTests/UnicodeTests.swift +++ b/Tests/CSVTests/UnicodeTests.swift @@ -29,7 +29,7 @@ class UnicodeTests: XCTestCase { mutableData.append(utf8BOM, count: utf8BOM.count) mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData) - let csv = try! CSV(stream: stream, codecType: UTF8.self) + let csv = try! CSVReader(stream: stream, codecType: UTF8.self) let records = getRecords(csv: csv) XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"]) XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""]) @@ -41,7 +41,7 @@ class UnicodeTests: XCTestCase { var mutableData = Data() mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData as Data) - let csv = try! CSV(stream: stream, codecType: UTF16.self, endian: .unknown) + let csv = try! CSVReader(stream: stream, codecType: UTF16.self, endian: .unknown) let records = getRecords(csv: csv) XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"]) XCTAssertEqual(records[1], ["zxcv", "😆asdf", "qw\"er", ""]) @@ -54,7 +54,7 @@ class UnicodeTests: XCTestCase { mutableData.append(utf16BigEndianBOM, count: utf16BigEndianBOM.count) mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData as Data) - let csv = try! CSV(stream: stream, codecType: UTF16.self, endian: .big) + let csv = try! CSVReader(stream: stream, codecType: UTF16.self, endian: .big) let records = getRecords(csv: csv) XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"]) XCTAssertEqual(records[1], ["😆zxcv", "asdf", "qw\"er", ""]) @@ -67,7 +67,7 @@ class UnicodeTests: XCTestCase { mutableData.append(utf16LittleEndianBOM, count: utf16LittleEndianBOM.count) mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData as Data) - let csv = try! CSV(stream: stream, codecType: UTF16.self, endian: .little) + let csv = try! CSVReader(stream: stream, codecType: UTF16.self, endian: .little) let records = getRecords(csv: csv) XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"]) XCTAssertEqual(records[1], ["zxcv😆", "asdf", "qw\"er", ""]) @@ -79,7 +79,7 @@ class UnicodeTests: XCTestCase { var mutableData = Data() mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData as Data) - let csv = try! CSV(stream: stream, codecType: UTF32.self, endian: .unknown) + let csv = try! CSVReader(stream: stream, codecType: UTF32.self, endian: .unknown) let records = getRecords(csv: csv) XCTAssertEqual(records[0], ["😆abab", "", "cdcd", "efef"]) XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""]) @@ -92,7 +92,7 @@ class UnicodeTests: XCTestCase { mutableData.append(utf32BigEndianBOM, count: utf32BigEndianBOM.count) mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData as Data) - let csv = try! CSV(stream: stream, codecType: UTF32.self, endian: .big) + let csv = try! CSVReader(stream: stream, codecType: UTF32.self, endian: .big) let records = getRecords(csv: csv) XCTAssertEqual(records[0], ["abab", "", "cd😆cd", "efef"]) XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""]) @@ -105,16 +105,16 @@ class UnicodeTests: XCTestCase { mutableData.append(utf32LittleEndianBOM, count: utf32LittleEndianBOM.count) mutableData.append(csvString.data(using: encoding)!) let stream = InputStream(data: mutableData as Data) - let csv = try! CSV(stream: stream, codecType: UTF32.self, endian: .little) + let csv = try! CSVReader(stream: stream, codecType: UTF32.self, endian: .little) let records = getRecords(csv: csv) XCTAssertEqual(records[0], ["abab", "", "cdcd", "ef😆ef"]) XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""]) } - private func getRecords(csv: CSV) -> [[String]] { + private func getRecords(csv: CSVReader) -> [[String]] { var records = [[String]]() - for row in csv { - records.append(row.toArray()) + csv.enumerateRecords { (record, _, _) in + records.append(record) } return records } From 2a284b6d3da97c842db0eec2cafdce6eef08c59b Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 18 Jun 2017 15:50:31 +0900 Subject: [PATCH 31/36] Keep compatibility with version 1 --- .swiftlint.yml | 2 + CSV.swift | 11 + CSV.xcodeproj/project.pbxproj | 18 ++ Sources/BinaryReader.swift | 16 +- Sources/CSVError.swift | 6 +- Sources/CSVReader.swift | 312 +++++++++++---------------- Sources/CSVWriter.swift | 98 +++++---- Sources/Endian.swift | 8 +- Sources/UnicodeIterator.swift | 2 +- Tests/CSVTests/CSVTests.swift | 16 +- Tests/CSVTests/CSVWriterTests.swift | 140 ++++++------ Tests/CSVTests/LineBreakTests.swift | 4 +- Tests/CSVTests/ReadmeTests.swift | 20 +- Tests/CSVTests/TrimFieldsTests.swift | 54 ++--- Tests/CSVTests/UnicodeTests.swift | 2 +- Tests/CSVTests/Version1Tests.swift | 105 +++++++++ 16 files changed, 439 insertions(+), 375 deletions(-) create mode 100644 CSV.swift create mode 100644 Tests/CSVTests/Version1Tests.swift diff --git a/.swiftlint.yml b/.swiftlint.yml index 270cbaf..c68f278 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -2,3 +2,5 @@ disabled_rules: - force_cast - force_try - variable_name +included: + - Sources diff --git a/CSV.swift b/CSV.swift new file mode 100644 index 0000000..61ee942 --- /dev/null +++ b/CSV.swift @@ -0,0 +1,11 @@ +// +// CSV.swift +// CSV +// +// Created by Yasuhiro Hatta on 2016/06/11. +// Copyright © 2016 yaslab. All rights reserved. +// + +public typealias CSV = CSVReader + +extension CSV: Sequence { } diff --git a/CSV.xcodeproj/project.pbxproj b/CSV.xcodeproj/project.pbxproj index ab20d7f..193f042 100755 --- a/CSV.xcodeproj/project.pbxproj +++ b/CSV.xcodeproj/project.pbxproj @@ -33,6 +33,9 @@ 0E7E8D001D0BCDCF0057A1C1 /* CSVReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */; }; 0E7E8D011D0BCDCF0057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; }; 0E7E8D021D0BCDCF0057A1C1 /* CSVVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0E7F657B1EF6437E00E1E1A0 /* Version1Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F657A1EF6437E00E1E1A0 /* Version1Tests.swift */; }; + 0E7F657C1EF6437E00E1E1A0 /* Version1Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F657A1EF6437E00E1E1A0 /* Version1Tests.swift */; }; + 0E7F657D1EF6437E00E1E1A0 /* Version1Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F657A1EF6437E00E1E1A0 /* Version1Tests.swift */; }; 0EA2AB7C1D183B45003EC967 /* BinaryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */; }; 0EA2AB7D1D183B45003EC967 /* BinaryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */; }; 0EA2AB7E1D183B45003EC967 /* BinaryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */; }; @@ -41,6 +44,10 @@ 0EA2AB821D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; }; 0EA2AB831D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; }; 0EA2AB841D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; }; + 0EBC9E691EE064A500432A2D /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBC9E681EE064A500432A2D /* CSV.swift */; }; + 0EBC9E6A1EE064A500432A2D /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBC9E681EE064A500432A2D /* CSV.swift */; }; + 0EBC9E6B1EE064A500432A2D /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBC9E681EE064A500432A2D /* CSV.swift */; }; + 0EBC9E6C1EE064A500432A2D /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBC9E681EE064A500432A2D /* CSV.swift */; }; 0EDF8ED71DDB73520068056A /* CSVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECD1DDB73370068056A /* CSVTests.swift */; }; 0EDF8ED81DDB73520068056A /* LineBreakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECE1DDB73370068056A /* LineBreakTests.swift */; }; 0EDF8ED91DDB73520068056A /* ReadmeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */; }; @@ -98,8 +105,10 @@ 0E7E8CCF1D0BCA2A0057A1C1 /* CSVTests-OSX.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CSVTests-OSX.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8CE81D0BCD0B0057A1C1 /* CSV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CSV.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E7E8CF11D0BCD0B0057A1C1 /* CSVTests-tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CSVTests-tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 0E7F657A1EF6437E00E1E1A0 /* Version1Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Version1Tests.swift; sourceTree = ""; }; 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinaryReader.swift; sourceTree = ""; }; 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeIterator.swift; sourceTree = ""; }; + 0EBC9E681EE064A500432A2D /* CSV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSV.swift; sourceTree = SOURCE_ROOT; }; 0EDF8ECD1DDB73370068056A /* CSVTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVTests.swift; sourceTree = ""; }; 0EDF8ECE1DDB73370068056A /* LineBreakTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineBreakTests.swift; sourceTree = ""; }; 0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadmeTests.swift; sourceTree = ""; }; @@ -191,6 +200,7 @@ isa = PBXGroup; children = ( 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */, + 0EBC9E681EE064A500432A2D /* CSV.swift */, 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */, 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */, 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */, @@ -220,6 +230,7 @@ 0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */, 0EDF8ED01DDB73370068056A /* TrimFieldsTests.swift */, 0EDF8ED11DDB73370068056A /* UnicodeTests.swift */, + 0E7F657A1EF6437E00E1E1A0 /* Version1Tests.swift */, ); path = CSVTests; sourceTree = ""; @@ -526,6 +537,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 0EBC9E6A1EE064A500432A2D /* CSV.swift in Sources */, 0EA2AB821D183BA9003EC967 /* UnicodeIterator.swift in Sources */, 0E5402241EDA82220019C3ED /* CSVWriter.swift in Sources */, 0E7E8CA11D0BC7F10057A1C1 /* CSVReader.swift in Sources */, @@ -542,6 +554,7 @@ 0EDF8EDE1DDB73520068056A /* ReadmeTests.swift in Sources */, 0EDF8EDC1DDB73520068056A /* CSVTests.swift in Sources */, 0EDF8EDF1DDB73520068056A /* TrimFieldsTests.swift in Sources */, + 0E7F657C1EF6437E00E1E1A0 /* Version1Tests.swift in Sources */, 0EDF8EE01DDB73520068056A /* UnicodeTests.swift in Sources */, 0EDF8EDD1DDB73520068056A /* LineBreakTests.swift in Sources */, 0E54021C1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */, @@ -552,6 +565,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 0EBC9E6C1EE064A500432A2D /* CSV.swift in Sources */, 0EA2AB841D183BA9003EC967 /* UnicodeIterator.swift in Sources */, 0E5402221EDA82220019C3ED /* CSVWriter.swift in Sources */, 0E7E8CBE1D0BC9D70057A1C1 /* CSVReader.swift in Sources */, @@ -565,6 +579,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 0EBC9E691EE064A500432A2D /* CSV.swift in Sources */, 0EA2AB811D183BA9003EC967 /* UnicodeIterator.swift in Sources */, 0E5402251EDA82230019C3ED /* CSVWriter.swift in Sources */, 0E7E8CE01D0BCA8E0057A1C1 /* CSVReader.swift in Sources */, @@ -581,6 +596,7 @@ 0EDF8ED91DDB73520068056A /* ReadmeTests.swift in Sources */, 0EDF8ED71DDB73520068056A /* CSVTests.swift in Sources */, 0EDF8EDA1DDB73520068056A /* TrimFieldsTests.swift in Sources */, + 0E7F657B1EF6437E00E1E1A0 /* Version1Tests.swift in Sources */, 0EDF8EDB1DDB73520068056A /* UnicodeTests.swift in Sources */, 0EDF8ED81DDB73520068056A /* LineBreakTests.swift in Sources */, 0E54021B1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */, @@ -591,6 +607,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 0EBC9E6B1EE064A500432A2D /* CSV.swift in Sources */, 0EA2AB831D183BA9003EC967 /* UnicodeIterator.swift in Sources */, 0E5402231EDA82220019C3ED /* CSVWriter.swift in Sources */, 0E7E8D001D0BCDCF0057A1C1 /* CSVReader.swift in Sources */, @@ -607,6 +624,7 @@ 0EDF8EE31DDB73530068056A /* ReadmeTests.swift in Sources */, 0EDF8EE11DDB73530068056A /* CSVTests.swift in Sources */, 0EDF8EE41DDB73530068056A /* TrimFieldsTests.swift in Sources */, + 0E7F657D1EF6437E00E1E1A0 /* Version1Tests.swift in Sources */, 0EDF8EE51DDB73530068056A /* UnicodeTests.swift in Sources */, 0EDF8EE21DDB73530068056A /* LineBreakTests.swift in Sources */, 0E54021D1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */, diff --git a/Sources/BinaryReader.swift b/Sources/BinaryReader.swift index 6247c0d..a48254b 100644 --- a/Sources/BinaryReader.swift +++ b/Sources/BinaryReader.swift @@ -53,8 +53,8 @@ internal class BinaryReader { internal init( stream: InputStream, - endian: Endian = .unknown, - closeOnDeinit: Bool = true) throws { + endian: Endian, + closeOnDeinit: Bool) throws { var endian = endian @@ -62,7 +62,7 @@ internal class BinaryReader { stream.open() } if stream.streamStatus != .open { - throw CSVError.cannotOpenStream + throw CSVError.cannotOpenFile } let readCount = stream.read(tempBuffer, maxLength: tempBufferSize) @@ -93,7 +93,7 @@ internal class BinaryReader { private func readStream(_ buffer: UnsafeMutablePointer, maxLength: Int) throws -> Int { if stream.streamStatus != .open { - throw CSVError.cannotReadStream + throw CSVError.cannotReadFile } var i = 0 @@ -115,7 +115,7 @@ internal class BinaryReader { throw CSVError.streamErrorHasOccurred(error: stream.streamError!) } if length != bufferSize { - throw CSVError.cannotReadStream + throw CSVError.cannotReadFile } return buffer[0] } @@ -169,7 +169,7 @@ extension BinaryReader { internal class UInt8Iterator: Sequence, IteratorProtocol { private let reader: BinaryReader - internal var errorHandler: ((Error) -> Void)? = nil + internal var errorHandler: ((Error) -> Void)? fileprivate init(reader: BinaryReader) { self.reader = reader @@ -200,7 +200,7 @@ extension BinaryReader { internal class UInt16Iterator: Sequence, IteratorProtocol { private let reader: BinaryReader - internal var errorHandler: ((Error) -> Void)? = nil + internal var errorHandler: ((Error) -> Void)? fileprivate init(reader: BinaryReader) { self.reader = reader @@ -231,7 +231,7 @@ extension BinaryReader { internal class UInt32Iterator: Sequence, IteratorProtocol { private let reader: BinaryReader - internal var errorHandler: ((Error) -> Void)? = nil + internal var errorHandler: ((Error) -> Void)? fileprivate init(reader: BinaryReader) { self.reader = reader diff --git a/Sources/CSVError.swift b/Sources/CSVError.swift index 81743ee..a2df4cc 100644 --- a/Sources/CSVError.swift +++ b/Sources/CSVError.swift @@ -10,9 +10,9 @@ public enum CSVError: Error { /// No overview available. - case cannotOpenStream + case cannotOpenFile /// No overview available. - case cannotReadStream + case cannotReadFile /// No overview available. case cannotWriteStream /// No overview available. @@ -20,7 +20,7 @@ public enum CSVError: Error { /// No overview available. case unicodeDecoding /// No overview available. - case cannotReadHeaderRecord + case cannotReadHeaderRow /// No overview available. case stringEncodingMismatch /// No overview available. diff --git a/Sources/CSVReader.swift b/Sources/CSVReader.swift index 36f3311..e2cc9b3 100644 --- a/Sources/CSVReader.swift +++ b/Sources/CSVReader.swift @@ -15,62 +15,61 @@ internal let DQUOTE: UnicodeScalar = "\"" internal let DQUOTE_STR: String = "\"" internal let DQUOTE2_STR: String = "\"\"" -internal let defaultHasHeaderRecord = false +internal let defaultHasHeaderRow = false internal let defaultTrimFields = false internal let defaultDelimiter: UnicodeScalar = "," internal let defaultWhitespaces = CharacterSet.whitespaces +internal let defaultNewline: UnicodeScalar = LF /// No overview available. public class CSVReader { - + /// No overview available. public struct Configuration { - - public var fileInputErrorHandler: ((Error, Int, Int) -> Void)? = nil - - /// `true` if the CSV has a header record, otherwise `false`. Default: `false`. - public var hasHeaderRecord: Bool + + /// `true` if the CSV has a header row, otherwise `false`. Default: `false`. + public var hasHeaderRow: Bool /// No overview available. public var trimFields: Bool /// Default: `","`. public var delimiter: UnicodeScalar /// No overview available. public var whitespaces: CharacterSet - + /// No overview available. - public init( - hasHeaderRecord: Bool = defaultHasHeaderRecord, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter, - whitespaces: CharacterSet = defaultWhitespaces) { - - self.hasHeaderRecord = hasHeaderRecord + internal init( + hasHeaderRow: Bool, + trimFields: Bool, + delimiter: UnicodeScalar, + whitespaces: CharacterSet) { + + self.hasHeaderRow = hasHeaderRow self.trimFields = trimFields self.delimiter = delimiter - + var whitespaces = whitespaces _ = whitespaces.remove(delimiter) self.whitespaces = whitespaces } - + } fileprivate var iterator: AnyIterator - //public let stream: InputStream? public let configuration: Configuration + public fileprivate (set) var error: Error? - fileprivate var back: UnicodeScalar? = nil + fileprivate var back: UnicodeScalar? fileprivate var fieldBuffer = String.UnicodeScalarView() - fileprivate var currentRecordIndex: Int = 0 + fileprivate var currentRowIndex: Int = 0 fileprivate var currentFieldIndex: Int = 0 - /// CSV header record. To set a value for this property, - /// you set `true` to `headerRecord` in initializer. - public private (set) var headerRecord: [String]? = nil + /// CSV header row. To set a value for this property, + /// you set `true` to `headerRow` in initializer. + public private (set) var headerRow: [String]? + + public fileprivate (set) var currentRow: [String]? - public fileprivate (set) var currentRecord: [String]? = nil - internal init( iterator: T, configuration: Configuration @@ -79,34 +78,42 @@ public class CSVReader { self.iterator = AnyIterator(iterator) self.configuration = configuration - if configuration.hasHeaderRecord { - guard let headerRecord = readRecord() else { - throw CSVError.cannotReadHeaderRecord + if configuration.hasHeaderRow { + guard let headerRow = readRow() else { + throw CSVError.cannotReadHeaderRow } - self.headerRecord = headerRecord + self.headerRow = headerRow } } } extension CSVReader { - + /// Create an instance with `InputStream`. /// /// - parameter stream: An `InputStream` object. If the stream is not open, /// initializer opens automatically. /// - parameter codecType: A `UnicodeCodec` type for `stream`. - /// - parameter config: CSV configuration. + /// - parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`. + /// - parameter delimiter: Default: `","`. public convenience init( stream: InputStream, codecType: T.Type, - configuration: Configuration = Configuration() + hasHeaderRow: Bool = defaultHasHeaderRow, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter, + whitespaces: CharacterSet = defaultWhitespaces ) throws where T.CodeUnit == UInt8 { let reader = try BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true) let input = reader.makeUInt8Iterator() let iterator = UnicodeIterator(input: input, inputEncodingType: codecType) - try self.init(iterator: iterator, configuration: configuration) + let config = Configuration(hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter, + whitespaces: whitespaces) + try self.init(iterator: iterator, configuration: config) input.errorHandler = { [unowned self] in self.errorHandler(error: $0) } iterator.errorHandler = { [unowned self] in self.errorHandler(error: $0) } } @@ -117,18 +124,26 @@ extension CSVReader { /// initializer opens automatically. /// - parameter codecType: A `UnicodeCodec` type for `stream`. /// - parameter endian: Endian to use when reading a stream. Default: `.big`. - /// - parameter config: CSV configuration. + /// - parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`. + /// - parameter delimiter: Default: `","`. public convenience init( stream: InputStream, codecType: T.Type, endian: Endian = .big, - configuration: Configuration = Configuration() + hasHeaderRow: Bool = defaultHasHeaderRow, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter, + whitespaces: CharacterSet = defaultWhitespaces ) throws where T.CodeUnit == UInt16 { let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) let input = reader.makeUInt16Iterator() let iterator = UnicodeIterator(input: input, inputEncodingType: codecType) - try self.init(iterator: iterator, configuration: configuration) + let config = Configuration(hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter, + whitespaces: whitespaces) + try self.init(iterator: iterator, configuration: config) input.errorHandler = { [unowned self] in self.errorHandler(error: $0) } iterator.errorHandler = { [unowned self] in self.errorHandler(error: $0) } } @@ -139,57 +154,86 @@ extension CSVReader { /// initializer opens automatically. /// - parameter codecType: A `UnicodeCodec` type for `stream`. /// - parameter endian: Endian to use when reading a stream. Default: `.big`. - /// - parameter config: CSV configuration. + /// - parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`. + /// - parameter delimiter: Default: `","`. public convenience init( stream: InputStream, codecType: T.Type, endian: Endian = .big, - configuration: Configuration = Configuration() + hasHeaderRow: Bool = defaultHasHeaderRow, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter, + whitespaces: CharacterSet = defaultWhitespaces ) throws where T.CodeUnit == UInt32 { let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) let input = reader.makeUInt32Iterator() let iterator = UnicodeIterator(input: input, inputEncodingType: codecType) - try self.init(iterator: iterator, configuration: configuration) + let config = Configuration(hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter, + whitespaces: whitespaces) + try self.init(iterator: iterator, configuration: config) input.errorHandler = { [unowned self] in self.errorHandler(error: $0) } iterator.errorHandler = { [unowned self] in self.errorHandler(error: $0) } } - + /// Create an instance with `InputStream`. /// /// - parameter stream: An `InputStream` object. If the stream is not open, /// initializer opens automatically. - /// - parameter config: CSV configuration. + /// - parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`. + /// - parameter delimiter: Default: `","`. public convenience init( stream: InputStream, - configuration: Configuration = Configuration()) throws { - - try self.init(stream: stream, codecType: UTF8.self, configuration: configuration) + hasHeaderRow: Bool = defaultHasHeaderRow, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter, + whitespaces: CharacterSet = defaultWhitespaces + ) throws { + + try self.init( + stream: stream, + codecType: UTF8.self, + hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter, + whitespaces: whitespaces) } - + /// Create an instance with CSV string. /// /// - parameter string: An CSV string. - /// - parameter config: CSV configuration. + /// - parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`. + /// - parameter delimiter: Default: `","`. public convenience init( string: String, - configuration: Configuration = Configuration()) throws { - + hasHeaderRow: Bool = defaultHasHeaderRow, + trimFields: Bool = defaultTrimFields, + delimiter: UnicodeScalar = defaultDelimiter, + whitespaces: CharacterSet = defaultWhitespaces + ) throws { + let iterator = string.unicodeScalars.makeIterator() - try self.init(iterator: iterator, configuration: configuration) + let config = Configuration(hasHeaderRow: hasHeaderRow, + trimFields: trimFields, + delimiter: delimiter, + whitespaces: whitespaces) + try self.init(iterator: iterator, configuration: config) } private func errorHandler(error: Error) { - configuration.fileInputErrorHandler?(error, currentRecordIndex, currentFieldIndex) + //configuration.fileInputErrorHandler?(error, currentRowIndex, currentFieldIndex) + self.error = error } - + } // MARK: - Parse CSV extension CSVReader { - - fileprivate func readRecord() -> [String]? { + + fileprivate func readRow() -> [String]? { currentFieldIndex = 0 var c = moveNext() @@ -197,7 +241,7 @@ extension CSVReader { return nil } - var record = [String]() + var row = [String]() var field: String var end: Bool while true { @@ -221,7 +265,7 @@ extension CSVReader { field = field.trimmingCharacters(in: configuration.whitespaces) } } - record.append(field) + row.append(field) if end { break } @@ -231,10 +275,10 @@ extension CSVReader { c = moveNext() } - currentRecordIndex += 1 + currentRowIndex += 1 - currentRecord = record - return record + currentRow = row + return row } private func readField(quoted: Bool) -> (String, Bool) { @@ -259,7 +303,7 @@ extension CSVReader { back = cNextNext } } - // END RECORD + // END ROW return (String(fieldBuffer), true) } else if cNext == configuration.delimiter { // END FIELD @@ -282,7 +326,7 @@ extension CSVReader { back = cNext } } - // END RECORD + // END ROW return (String(fieldBuffer), true) } else if c == configuration.delimiter { // END FIELD @@ -310,161 +354,47 @@ extension CSVReader { } extension CSVReader { - - public func enumerateRecords(_ block: (([String], [String]?, inout Bool) throws -> Void)) rethrows { + + public func enumerateRows(_ block: (([String], [String]?, inout Bool) throws -> Void)) throws { var stop = false - while let record = readRecord() { - try block(record, headerRecord, &stop) + while let row = readRow() { + try block(row, headerRow, &stop) if stop { break } } + if let error = error { + throw error + } } - + } extension CSVReader: IteratorProtocol { - + @discardableResult public func next() -> [String]? { - return readRecord() + return readRow() } - + } extension CSVReader { public subscript(key: String) -> String? { - guard let header = headerRecord else { - fatalError("CSVReader.headerRecord must not be nil") + guard let header = headerRow else { + fatalError("CSVReader.headerRow must not be nil") } guard let index = header.index(of: key) else { return nil } - guard let record = currentRecord else { - fatalError("CSVReader.currentRecord must not be nil") + guard let row = currentRow else { + fatalError("CSVReader.currentRow must not be nil") } - if index >= record.count { + if index >= row.count { return nil } - return record[index] + return row[index] } - -} - -// MARK: - deprecated - -extension CSVReader { - - /// Unavailable. - @available(*, unavailable, message: "Use init(stream:codecType:config:) instead") - public convenience init( - stream: InputStream, - codecType: T.Type, - hasHeaderRow: Bool = defaultHasHeaderRecord, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter - ) throws where T.CodeUnit == UInt8 { - - let reader = try BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true) - let iterator = UnicodeIterator( - input: reader.makeUInt8Iterator(), - inputEncodingType: codecType - ) - let config = Configuration( - hasHeaderRecord: hasHeaderRow, - trimFields: trimFields, - delimiter: delimiter - ) - try self.init(iterator: iterator, configuration: config) - } - - /// Unavailable. - @available(*, unavailable, message: "Use init(stream:codecType:endian:config:) instead") - public convenience init( - stream: InputStream, - codecType: T.Type, - endian: Endian = .big, - hasHeaderRow: Bool = defaultHasHeaderRecord, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter - ) throws where T.CodeUnit == UInt16 { - - let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) - let iterator = UnicodeIterator( - input: reader.makeUInt16Iterator(), - inputEncodingType: codecType - ) - let config = Configuration( - hasHeaderRecord: hasHeaderRow, - trimFields: trimFields, - delimiter: delimiter - ) - try self.init(iterator: iterator, configuration: config) - } - - /// Unavailable. - @available(*, unavailable, message: "Use init(stream:codecType:endian:config:) instead") - public convenience init( - stream: InputStream, - codecType: T.Type, - endian: Endian = .big, - hasHeaderRow: Bool = defaultHasHeaderRecord, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter - ) throws where T.CodeUnit == UInt32 { - - let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true) - let iterator = UnicodeIterator( - input: reader.makeUInt32Iterator(), - inputEncodingType: codecType - ) - let config = Configuration( - hasHeaderRecord: hasHeaderRow, - trimFields: trimFields, - delimiter: delimiter - ) - try self.init(iterator: iterator, configuration: config) - } - - /// Unavailable. - @available(*, unavailable, message: "Use init(stream:config:) instead") - public convenience init( - stream: InputStream, - hasHeaderRow: Bool = defaultHasHeaderRecord, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter) throws { - - let config = Configuration( - hasHeaderRecord: hasHeaderRow, - trimFields: trimFields, - delimiter: delimiter - ) - try self.init(stream: stream, codecType: UTF8.self, configuration: config) - } - - /// Unavailable. - @available(*, unavailable, message: "Use init(string:config:) instead") - public convenience init( - string: String, - hasHeaderRow: Bool = defaultHasHeaderRecord, - trimFields: Bool = defaultTrimFields, - delimiter: UnicodeScalar = defaultDelimiter) throws { - - let iterator = string.unicodeScalars.makeIterator() - let config = Configuration( - hasHeaderRecord: hasHeaderRow, - trimFields: trimFields, - delimiter: delimiter - ) - try self.init(iterator: iterator, configuration: config) - } - - /// Unavailable -// @available(*, unavailable, message: "Use CSV.Row.subscript(String) instead") -// public subscript(key: String) -> String? { -// // FIXME: -// return nil -// } } diff --git a/Sources/CSVWriter.swift b/Sources/CSVWriter.swift index 1262e59..97f8bc0 100644 --- a/Sources/CSVWriter.swift +++ b/Sources/CSVWriter.swift @@ -9,61 +9,71 @@ import Foundation public class CSVWriter { - + public struct Configuration { - + public var delimiter: String public var newline: String - - public init(delimiter: String = String(defaultDelimiter), newline: String = String(LF)) { + + internal init(delimiter: String, newline: String) { self.delimiter = delimiter self.newline = newline } - + } - + public let stream: OutputStream public let configuration: Configuration fileprivate let writeScalar: ((UnicodeScalar) throws -> Void) - fileprivate var isFirstRecord: Bool = true + fileprivate var isFirstRow: Bool = true fileprivate var isFirstField: Bool = true fileprivate init( stream: OutputStream, configuration: Configuration, writeScalar: @escaping ((UnicodeScalar) throws -> Void)) throws { - + self.stream = stream self.configuration = configuration self.writeScalar = writeScalar - + if stream.streamStatus == .notOpen { stream.open() } if stream.streamStatus != .open { - throw CSVError.cannotOpenStream + throw CSVError.cannotOpenFile } } - + + deinit { + if stream.streamStatus == .open { + stream.close() + } + } + } extension CSVWriter { public convenience init( stream: OutputStream, - configuration: Configuration = Configuration()) throws { + delimiter: String = String(defaultDelimiter), + newline: String = String(defaultNewline) + ) throws { - try self.init(stream: stream, codecType: UTF8.self, configuration: configuration) + try self.init(stream: stream, codecType: UTF8.self, delimiter: delimiter, newline: newline) } - + public convenience init( stream: OutputStream, codecType: T.Type, - configuration: Configuration = Configuration() + delimiter: String = String(defaultDelimiter), + newline: String = String(defaultNewline) ) throws where T.CodeUnit == UInt8 { - - try self.init(stream: stream, configuration: configuration) { (scalar: UnicodeScalar) throws in + + let config = Configuration(delimiter: delimiter, newline: newline) + try self.init(stream: stream, configuration: config) { (scalar: UnicodeScalar) throws in var error: CSVError? = nil codecType.encode(scalar) { (code: UInt8) in var code = code @@ -77,20 +87,23 @@ extension CSVWriter { } } } - + public convenience init( stream: OutputStream, codecType: T.Type, endian: Endian = .big, - configuration: Configuration = Configuration() + delimiter: String = String(defaultDelimiter), + newline: String = String(defaultNewline) ) throws where T.CodeUnit == UInt16 { - - try self.init(stream: stream, configuration: configuration) { (scalar: UnicodeScalar) throws in + + let config = Configuration(delimiter: delimiter, newline: newline) + try self.init(stream: stream, configuration: config) { (scalar: UnicodeScalar) throws in var error: CSVError? = nil codecType.encode(scalar) { (code: UInt16) in var code = (endian == .big) ? code.bigEndian : code.littleEndian withUnsafeBytes(of: &code) { (buffer) -> Void in - let count = stream.write(buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), maxLength: buffer.count) + let count = stream.write(buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), + maxLength: buffer.count) if count != buffer.count { error = CSVError.cannotWriteStream } @@ -101,20 +114,23 @@ extension CSVWriter { } } } - + public convenience init( stream: OutputStream, codecType: T.Type, endian: Endian = .big, - configuration: Configuration = Configuration() + delimiter: String = String(defaultDelimiter), + newline: String = String(defaultNewline) ) throws where T.CodeUnit == UInt32 { - - try self.init(stream: stream, configuration: configuration) { (scalar: UnicodeScalar) throws in + + let config = Configuration(delimiter: delimiter, newline: newline) + try self.init(stream: stream, configuration: config) { (scalar: UnicodeScalar) throws in var error: CSVError? = nil codecType.encode(scalar) { (code: UInt32) in var code = (endian == .big) ? code.bigEndian : code.littleEndian withUnsafeBytes(of: &code) { (buffer) -> Void in - let count = stream.write(buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), maxLength: buffer.count) + let count = stream.write(buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), + maxLength: buffer.count) if count != buffer.count { error = CSVError.cannotWriteStream } @@ -130,44 +146,44 @@ extension CSVWriter { extension CSVWriter { - public func beginNewRecord() { + public func beginNewRow() { isFirstField = true } - + public func write(field value: String, quoted: Bool = false) throws { - if isFirstRecord { - isFirstRecord = false + if isFirstRow { + isFirstRow = false } else { if isFirstField { try configuration.newline.unicodeScalars.forEach(writeScalar) } } - + if isFirstField { isFirstField = false } else { try configuration.delimiter.unicodeScalars.forEach(writeScalar) } - + var value = value - - if quoted { + + if quoted { value = value.replacingOccurrences(of: DQUOTE_STR, with: DQUOTE2_STR) try writeScalar(DQUOTE) } - + try value.unicodeScalars.forEach(writeScalar) - + if quoted { try writeScalar(DQUOTE) } } - - public func write(record values: [String], quotedAtIndex: ((Int) -> Bool) = { _ in false }) throws { - beginNewRecord() + + public func write(row values: [String], quotedAtIndex: ((Int) -> Bool) = { _ in false }) throws { + beginNewRow() for (i, value) in values.enumerated() { try write(field: value, quoted: quotedAtIndex(i)) } } - + } diff --git a/Sources/Endian.swift b/Sources/Endian.swift index 1dc96be..51c94e3 100644 --- a/Sources/Endian.swift +++ b/Sources/Endian.swift @@ -6,14 +6,14 @@ // Copyright © 2016 yaslab. All rights reserved. // -/// No overview available. +/// Represents byte order. public enum Endian { - /// No overview available. + /// Big endian. case big - /// No overview available. + /// Little endian. case little - /// No overview available. + /// Multibyte character sets. case unknown } diff --git a/Sources/UnicodeIterator.swift b/Sources/UnicodeIterator.swift index d9be5ed..9ac798a 100644 --- a/Sources/UnicodeIterator.swift +++ b/Sources/UnicodeIterator.swift @@ -14,7 +14,7 @@ internal class UnicodeIterator< private var input: Input private var inputEncoding: InputEncoding - internal var errorHandler: ((Error) -> Void)? = nil + internal var errorHandler: ((Error) -> Void)? internal init(input: Input, inputEncodingType: InputEncoding.Type) { self.input = input diff --git a/Tests/CSVTests/CSVTests.swift b/Tests/CSVTests/CSVTests.swift index fbcdd20..a19e97a 100644 --- a/Tests/CSVTests/CSVTests.swift +++ b/Tests/CSVTests/CSVTests.swift @@ -26,7 +26,7 @@ class CSVTests: XCTestCase { ("testSubscriptInt", testSubscriptInt), ("testSubscriptString1", testSubscriptString1), ("testSubscriptString2", testSubscriptString2), - ("testToArray", testToArray), + ("testToArray", testToArray) //("testToDictionary1", testToDictionary1), //("testToDictionary2", testToDictionary2) ] @@ -34,7 +34,7 @@ class CSVTests: XCTestCase { func testOneLine() { let csv = "\"abc\",1,2" var i = 0 - + for record in AnyIterator(try! CSVReader(string: csv)) { switch i { case 0: XCTAssertEqual(record, ["abc", "1", "2"]) @@ -154,7 +154,11 @@ class CSVTests: XCTestCase { func testCSVState1() { let it = "あ,い1,\"う\",えお\n,,x,".unicodeScalars.makeIterator() - let csv = try! CSVReader(iterator: it, configuration: CSVReader.Configuration()) + let config = CSVReader.Configuration(hasHeaderRow: false, + trimFields: false, + delimiter: ",", + whitespaces: .whitespaces) + let csv = try! CSVReader(iterator: it, configuration: config) var records = [[String]]() @@ -178,8 +182,7 @@ class CSVTests: XCTestCase { func testSubscriptString1() { let csvString = "key1,key2\nvalue1,value2" - let config = CSVReader.Configuration(hasHeaderRecord: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, hasHeaderRow: true) csv.next() XCTAssertEqual(csv["key1"], "value1") XCTAssertEqual(csv["key2"], "value2") @@ -188,8 +191,7 @@ class CSVTests: XCTestCase { func testSubscriptString2() { let csvString = "key1,key2\nvalue1" - let config = CSVReader.Configuration(hasHeaderRecord: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, hasHeaderRow: true) csv.next() XCTAssertEqual(csv["key1"], "value1") XCTAssertNil(csv["key2"]) diff --git a/Tests/CSVTests/CSVWriterTests.swift b/Tests/CSVTests/CSVWriterTests.swift index c17cb78..4dddb38 100644 --- a/Tests/CSVTests/CSVWriterTests.swift +++ b/Tests/CSVTests/CSVWriterTests.swift @@ -12,18 +12,18 @@ import XCTest import CSV extension OutputStream { - + var data: Data? { guard let nsData = property(forKey: .dataWrittenToMemoryStreamKey) as? NSData else { return nil } return Data(referencing: nsData) } - + } class CSVWriterTests: XCTestCase { - + static let allTests = [ ("testSingleFieldSingleRecord", testSingleFieldSingleRecord), ("testSingleFieldMultipleRecord", testSingleFieldMultipleRecord), @@ -39,18 +39,18 @@ class CSVWriterTests: XCTestCase { ("testUTF32BE", testUTF32BE), ("testUTF32LE", testUTF32LE) ] - + let str = "TEST-test-1234-😄😆👨‍👩‍👧‍👦" - + /// xxxx func testSingleFieldSingleRecord() { let stream = OutputStream(toMemory: ()) stream.open() - + let csv = try! CSVWriter(stream: stream) - csv.beginNewRecord() + csv.beginNewRow() try! csv.write(field: str) - + stream.close() let data = stream.data! let csvStr = String(data: data, encoding: .utf8)! @@ -63,34 +63,34 @@ class CSVWriterTests: XCTestCase { func testSingleFieldMultipleRecord() { let stream = OutputStream(toMemory: ()) stream.open() - + let csv = try! CSVWriter(stream: stream) - csv.beginNewRecord() + csv.beginNewRow() try! csv.write(field: str + "-1") - csv.beginNewRecord() + csv.beginNewRow() try! csv.write(field: str + "-2") - + stream.close() let data = stream.data! let csvStr = String(data: data, encoding: .utf8)! - + XCTAssertEqual(csvStr, "\(str)-1\n\(str)-2") } - + /// xxxx,xxxx func testMultipleFieldSingleRecord() { let stream = OutputStream(toMemory: ()) stream.open() - + let csv = try! CSVWriter(stream: stream) - csv.beginNewRecord() + csv.beginNewRow() try! csv.write(field: str + "-1") try! csv.write(field: str + "-2") stream.close() let data = stream.data! let csvStr = String(data: data, encoding: .utf8)! - + XCTAssertEqual(csvStr, "\(str)-1,\(str)-2") } @@ -99,88 +99,87 @@ class CSVWriterTests: XCTestCase { func testMultipleFieldMultipleRecord() { let stream = OutputStream(toMemory: ()) stream.open() - + let csv = try! CSVWriter(stream: stream) - csv.beginNewRecord() + csv.beginNewRow() try! csv.write(field: str + "-1-1") try! csv.write(field: str + "-1-2") - csv.beginNewRecord() + csv.beginNewRow() try! csv.write(field: str + "-2-1") try! csv.write(field: str + "-2-2") - + stream.close() let data = stream.data! let csvStr = String(data: data, encoding: .utf8)! - + XCTAssertEqual(csvStr, "\(str)-1-1,\(str)-1-2\n\(str)-2-1,\(str)-2-2") } - + /// "xxxx",xxxx func testQuoted() { let stream = OutputStream(toMemory: ()) stream.open() - + let csv = try! CSVWriter(stream: stream) - csv.beginNewRecord() + csv.beginNewRow() try! csv.write(field: str + "-1", quoted: true) try! csv.write(field: str + "-2") // quoted: false - + stream.close() let data = stream.data! let csvStr = String(data: data, encoding: .utf8)! - + XCTAssertEqual(csvStr, "\"\(str)-1\",\(str)-2") } - + /// xxxx,"xx\nxx" func testQuotedNewline() { let stream = OutputStream(toMemory: ()) stream.open() - + let csv = try! CSVWriter(stream: stream) - csv.beginNewRecord() + csv.beginNewRow() try! csv.write(field: str + "-1") // quoted: false try! csv.write(field: str + "-\n-2", quoted: true) - + stream.close() let data = stream.data! let csvStr = String(data: data, encoding: .utf8)! - + XCTAssertEqual(csvStr, "\(str)-1,\"\(str)-\n-2\"") } - + /// xxxx,"xx""xx" func testEscapeQuote() { let stream = OutputStream(toMemory: ()) stream.open() - + let csv = try! CSVWriter(stream: stream) - csv.beginNewRecord() + csv.beginNewRow() try! csv.write(field: str + "-1") // quoted: false try! csv.write(field: str + "-\"-2", quoted: true) - + stream.close() let data = stream.data! let csvStr = String(data: data, encoding: .utf8)! - + XCTAssertEqual(csvStr, "\(str)-1,\"\(str)-\"\"-2\"") } - + /// Test delimiter: "\t" func testDelimiter() { let stream = OutputStream(toMemory: ()) stream.open() - - let config = CSVWriter.Configuration(delimiter: "\t") - let csv = try! CSVWriter.init(stream: stream, configuration: config) - csv.beginNewRecord() + + let csv = try! CSVWriter.init(stream: stream, delimiter: "\t") + csv.beginNewRow() try! csv.write(field: str + "-1") try! csv.write(field: str + "-2") - + stream.close() let data = stream.data! let csvStr = String(data: data, encoding: .utf8)! - + XCTAssertEqual(csvStr, "\(str)-1\t\(str)-2") } @@ -188,34 +187,33 @@ class CSVWriterTests: XCTestCase { func testNewline() { let stream = OutputStream(toMemory: ()) stream.open() - - let config = CSVWriter.Configuration(newline: "\r\n") - let csv = try! CSVWriter.init(stream: stream, configuration: config) - csv.beginNewRecord() + + let csv = try! CSVWriter.init(stream: stream, newline: "\r\n") + csv.beginNewRow() try! csv.write(field: str + "-1") - csv.beginNewRecord() + csv.beginNewRow() try! csv.write(field: str + "-2") - + stream.close() let data = stream.data! let csvStr = String(data: data, encoding: .utf8)! - + XCTAssertEqual(csvStr, "\(str)-1\r\n\(str)-2") } - + /// UTF16 Big Endian func testUTF16BE() { let stream = OutputStream(toMemory: ()) stream.open() - + let csv = try! CSVWriter(stream: stream, codecType: UTF16.self, endian: .big) - csv.beginNewRecord() + csv.beginNewRow() try! csv.write(field: str) - + stream.close() let data = stream.data! let csvStr = String(data: data, encoding: .utf16BigEndian)! - + XCTAssertEqual(csvStr, str) } @@ -223,48 +221,48 @@ class CSVWriterTests: XCTestCase { func testUTF16LE() { let stream = OutputStream(toMemory: ()) stream.open() - + let csv = try! CSVWriter(stream: stream, codecType: UTF16.self, endian: .little) - csv.beginNewRecord() + csv.beginNewRow() try! csv.write(field: str) - + stream.close() let data = stream.data! let csvStr = String(data: data, encoding: .utf16LittleEndian)! - + XCTAssertEqual(csvStr, str) } - + /// UTF32 Big Endian func testUTF32BE() { let stream = OutputStream(toMemory: ()) stream.open() - + let csv = try! CSVWriter(stream: stream, codecType: UTF32.self, endian: .big) - csv.beginNewRecord() + csv.beginNewRow() try! csv.write(field: str) - + stream.close() let data = stream.data! let csvStr = String(data: data, encoding: .utf32BigEndian)! - + XCTAssertEqual(csvStr, str) } - + /// UTF32 Little Endian func testUTF32LE() { let stream = OutputStream(toMemory: ()) stream.open() - + let csv = try! CSVWriter(stream: stream, codecType: UTF32.self, endian: .little) - csv.beginNewRecord() + csv.beginNewRow() try! csv.write(field: str) - + stream.close() let data = stream.data! let csvStr = String(data: data, encoding: .utf32LittleEndian)! - + XCTAssertEqual(csvStr, str) } - + } diff --git a/Tests/CSVTests/LineBreakTests.swift b/Tests/CSVTests/LineBreakTests.swift index 37d04a0..b5383b7 100644 --- a/Tests/CSVTests/LineBreakTests.swift +++ b/Tests/CSVTests/LineBreakTests.swift @@ -126,8 +126,8 @@ class LineBreakTests: XCTestCase { private func parse(csv: String) -> [[String]] { let reader = try! CSVReader(string: csv) var records = [[String]]() - reader.enumerateRecords { (record, _, _) in - records.append(record) + try! reader.enumerateRows { (row, _, _) in + records.append(row) } return records } diff --git a/Tests/CSVTests/ReadmeTests.swift b/Tests/CSVTests/ReadmeTests.swift index cde14f4..a317c22 100644 --- a/Tests/CSVTests/ReadmeTests.swift +++ b/Tests/CSVTests/ReadmeTests.swift @@ -22,8 +22,8 @@ class ReadmeTests: XCTestCase { func testFromCSVString() { let csv = try! CSVReader(string: "1,foo\n2,bar") - csv.enumerateRecords { (record, _, _) in - print("\(record)") + try! csv.enumerateRows { (row, _, _) in + print("\(row)") // => ["1", "foo"] // => ["2", "bar"] } @@ -39,14 +39,14 @@ class ReadmeTests: XCTestCase { func testGettingTheHeaderRow() { let csvString = "id,name\n1,foo\n2,bar" - let config = CSVReader.Configuration(hasHeaderRecord: true) // It must be true. - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, + hasHeaderRow: true) // It must be true. - let headerRow = csv.headerRecord! + let headerRow = csv.headerRow! print("\(headerRow)") // => ["id", "name"] - csv.enumerateRecords { (record, _, _) in - print("\(record)") + try! csv.enumerateRows { (row, _, _) in + print("\(row)") // => ["1", "foo"] // => ["2", "bar"] } @@ -56,9 +56,9 @@ class ReadmeTests: XCTestCase { let csvString = "1,foo" let csv = try! CSVReader(string: csvString) - csv.enumerateRecords { (record, _, _) in - print("\(record[0])") // => "1" - print("\(record[1])") // => "foo" + try! csv.enumerateRows { (row, _, _) in + print("\(row[0])") // => "1" + print("\(row[1])") // => "foo" } } diff --git a/Tests/CSVTests/TrimFieldsTests.swift b/Tests/CSVTests/TrimFieldsTests.swift index 0a7719e..9176f60 100644 --- a/Tests/CSVTests/TrimFieldsTests.swift +++ b/Tests/CSVTests/TrimFieldsTests.swift @@ -29,8 +29,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields1() { let csvString = "abc,def,ghi" - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) for record in AnyIterator(csv) { XCTAssertEqual(record, ["abc", "def", "ghi"]) } @@ -38,8 +37,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields2() { let csvString = " abc, def, ghi" - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) for record in AnyIterator(csv) { XCTAssertEqual(record, ["abc", "def", "ghi"]) } @@ -47,8 +45,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields3() { let csvString = "abc ,def ,ghi " - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) for record in AnyIterator(csv) { XCTAssertEqual(record, ["abc", "def", "ghi"]) } @@ -56,8 +53,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields4() { let csvString = " abc , def , ghi " - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) for record in AnyIterator(csv) { XCTAssertEqual(record, ["abc", "def", "ghi"]) } @@ -65,8 +61,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields5() { let csvString = "\"abc\",\"def\",\"ghi\"" - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) for record in AnyIterator(csv) { XCTAssertEqual(record, ["abc", "def", "ghi"]) } @@ -74,8 +69,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields6() { let csvString = " \"abc\", \"def\", \"ghi\"" - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) for record in AnyIterator(csv) { XCTAssertEqual(record, ["abc", "def", "ghi"]) } @@ -83,8 +77,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields7() { let csvString = "\"abc\" ,\"def\" ,\"ghi\" " - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) for record in AnyIterator(csv) { XCTAssertEqual(record, ["abc", "def", "ghi"]) } @@ -92,8 +85,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields8() { let csvString = " \"abc\" , \"def\" , \"ghi\" " - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) for record in AnyIterator(csv) { XCTAssertEqual(record, ["abc", "def", "ghi"]) } @@ -101,8 +93,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields9() { let csvString = "\" abc \",\" def \",\" ghi \"" - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) for record in AnyIterator(csv) { XCTAssertEqual(record, [" abc ", " def ", " ghi "]) } @@ -110,8 +101,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields10() { let csvString = "\tabc,\t\tdef\t,ghi\t" - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) for record in AnyIterator(csv) { XCTAssertEqual(record, ["abc", "def", "ghi"]) } @@ -119,8 +109,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields11() { let csvString = " abc \n def " - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) let record1 = csv.next()! XCTAssertEqual(record1, ["abc"]) @@ -130,8 +119,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields12() { let csvString = " \"abc \" \n \" def\" " - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) let record1 = csv.next()! XCTAssertEqual(record1, ["abc "]) @@ -141,8 +129,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields13() { let csvString = " abc \t\tdef\t ghi " - let config = CSVReader.Configuration(trimFields: true, delimiter: UnicodeScalar("\t")!) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true, delimiter: "\t") for record in AnyIterator(csv) { XCTAssertEqual(record, ["abc", "", "def", "ghi"]) } @@ -150,8 +137,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields14() { let csvString = "" - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) let records = AnyIterator(csv).map { $0 } XCTAssertEqual(records.count, 0) @@ -159,8 +145,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields15() { let csvString = " " - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) let records = AnyIterator(csv).map { $0 } XCTAssertEqual(records.count, 1) @@ -169,8 +154,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields16() { let csvString = " , " - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) let records = AnyIterator(csv).map { $0 } XCTAssertEqual(records.count, 1) @@ -179,8 +163,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields17() { let csvString = " , \n" - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) let records = AnyIterator(csv).map { $0 } XCTAssertEqual(records.count, 1) @@ -189,8 +172,7 @@ class TrimFieldsTests: XCTestCase { func testTrimFields18() { let csvString = " , \n " - let config = CSVReader.Configuration(trimFields: true) - let csv = try! CSVReader(string: csvString, configuration: config) + let csv = try! CSVReader(string: csvString, trimFields: true) let records = AnyIterator(csv).map { $0 } XCTAssertEqual(records.count, 2) diff --git a/Tests/CSVTests/UnicodeTests.swift b/Tests/CSVTests/UnicodeTests.swift index 5369681..99a620b 100644 --- a/Tests/CSVTests/UnicodeTests.swift +++ b/Tests/CSVTests/UnicodeTests.swift @@ -113,7 +113,7 @@ class UnicodeTests: XCTestCase { private func getRecords(csv: CSVReader) -> [[String]] { var records = [[String]]() - csv.enumerateRecords { (record, _, _) in + try! csv.enumerateRows { (record, _, _) in records.append(record) } return records diff --git a/Tests/CSVTests/Version1Tests.swift b/Tests/CSVTests/Version1Tests.swift new file mode 100644 index 0000000..1cc0533 --- /dev/null +++ b/Tests/CSVTests/Version1Tests.swift @@ -0,0 +1,105 @@ +// +// Version1Tests.swift +// CSV +// +// Created by Yasuhiro Hatta on 2017/06/18. +// Copyright © 2017 yaslab. All rights reserved. +// + +import Foundation +import XCTest +@testable import CSV + +class Version1Tests: XCTestCase { + + func testV1() { + let str = "a,b,c\n1,2,3" + let data8 = str.data(using: .utf8)! + let data16 = str.data(using: .utf16BigEndian)! + let data32 = str.data(using: .utf32BigEndian)! + + let headerRow = ["a", "b", "c"] + let row = ["1", "2", "3"] + + do { + let stream = InputStream(data: data8) + var csv = try CSV(stream: stream, + codecType: UTF8.self, + hasHeaderRow: true, + trimFields: false, + delimiter: ",") + XCTAssertEqual(csv.headerRow!, headerRow) + XCTAssertEqual(csv.next()!, row) + XCTAssertEqual(csv["a"], row[0]) + } catch { + fatalError() + } + + do { + let stream = InputStream(data: data16) + var csv = try CSV(stream: stream, + codecType: UTF16.self, + endian: .big, + hasHeaderRow: true, + trimFields: false, + delimiter: ",") + XCTAssertEqual(csv.headerRow!, headerRow) + XCTAssertEqual(csv.next()!, row) + XCTAssertEqual(csv["a"], row[0]) + } catch { + fatalError() + } + + do { + let stream = InputStream(data: data32) + var csv = try CSV(stream: stream, + codecType: UTF32.self, + endian: .big, + hasHeaderRow: true, + trimFields: false, + delimiter: ",") + XCTAssertEqual(csv.headerRow!, headerRow) + XCTAssertEqual(csv.next()!, row) + XCTAssertEqual(csv["a"], row[0]) + } catch { + fatalError() + } + + do { + let stream = InputStream(data: data8) + var csv = try CSV(stream: stream, + hasHeaderRow: true, + trimFields: false, + delimiter: ",") + XCTAssertEqual(csv.headerRow!, headerRow) + XCTAssertEqual(csv.next()!, row) + XCTAssertEqual(csv["a"], row[0]) + } catch { + fatalError() + } + + do { + var csv = try CSV(string: str, + hasHeaderRow: true, + trimFields: false, + delimiter: ",") + XCTAssertEqual(csv.headerRow!, headerRow) + XCTAssertEqual(csv.next()!, row) + XCTAssertEqual(csv["a"], row[0]) + } catch { + fatalError() + } + + _ = CSVError.cannotOpenFile + _ = CSVError.cannotReadFile + _ = CSVError.streamErrorHasOccurred(error: NSError()) + _ = CSVError.cannotReadHeaderRow + _ = CSVError.stringEncodingMismatch + _ = CSVError.stringEndianMismatch + + _ = Endian.big + _ = Endian.little + _ = Endian.unknown + } + +} From cab93830fed030ac7dc82062810ab353d721829f Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 18 Jun 2017 17:49:45 +0900 Subject: [PATCH 32/36] Fix file misplacement --- CSV.xcodeproj/project.pbxproj | 20 ++++++++++---------- CSV.swift => Sources/CSV.swift | 0 2 files changed, 10 insertions(+), 10 deletions(-) rename CSV.swift => Sources/CSV.swift (100%) diff --git a/CSV.xcodeproj/project.pbxproj b/CSV.xcodeproj/project.pbxproj index 193f042..30fe0a0 100755 --- a/CSV.xcodeproj/project.pbxproj +++ b/CSV.xcodeproj/project.pbxproj @@ -44,10 +44,10 @@ 0EA2AB821D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; }; 0EA2AB831D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; }; 0EA2AB841D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; }; - 0EBC9E691EE064A500432A2D /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBC9E681EE064A500432A2D /* CSV.swift */; }; - 0EBC9E6A1EE064A500432A2D /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBC9E681EE064A500432A2D /* CSV.swift */; }; - 0EBC9E6B1EE064A500432A2D /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBC9E681EE064A500432A2D /* CSV.swift */; }; - 0EBC9E6C1EE064A500432A2D /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBC9E681EE064A500432A2D /* CSV.swift */; }; + 0EC628FE1EF675B300289554 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC628FD1EF675B300289554 /* CSV.swift */; }; + 0EC628FF1EF675FD00289554 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC628FD1EF675B300289554 /* CSV.swift */; }; + 0EC629001EF675FE00289554 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC628FD1EF675B300289554 /* CSV.swift */; }; + 0EC629011EF675FF00289554 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC628FD1EF675B300289554 /* CSV.swift */; }; 0EDF8ED71DDB73520068056A /* CSVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECD1DDB73370068056A /* CSVTests.swift */; }; 0EDF8ED81DDB73520068056A /* LineBreakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECE1DDB73370068056A /* LineBreakTests.swift */; }; 0EDF8ED91DDB73520068056A /* ReadmeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */; }; @@ -108,7 +108,7 @@ 0E7F657A1EF6437E00E1E1A0 /* Version1Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Version1Tests.swift; sourceTree = ""; }; 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinaryReader.swift; sourceTree = ""; }; 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeIterator.swift; sourceTree = ""; }; - 0EBC9E681EE064A500432A2D /* CSV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSV.swift; sourceTree = SOURCE_ROOT; }; + 0EC628FD1EF675B300289554 /* CSV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSV.swift; sourceTree = ""; }; 0EDF8ECD1DDB73370068056A /* CSVTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVTests.swift; sourceTree = ""; }; 0EDF8ECE1DDB73370068056A /* LineBreakTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineBreakTests.swift; sourceTree = ""; }; 0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadmeTests.swift; sourceTree = ""; }; @@ -200,7 +200,7 @@ isa = PBXGroup; children = ( 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */, - 0EBC9E681EE064A500432A2D /* CSV.swift */, + 0EC628FD1EF675B300289554 /* CSV.swift */, 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */, 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */, 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */, @@ -537,12 +537,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0EBC9E6A1EE064A500432A2D /* CSV.swift in Sources */, 0EA2AB821D183BA9003EC967 /* UnicodeIterator.swift in Sources */, 0E5402241EDA82220019C3ED /* CSVWriter.swift in Sources */, 0E7E8CA11D0BC7F10057A1C1 /* CSVReader.swift in Sources */, 0E0F160F1D197DB800C92580 /* Endian.swift in Sources */, 0E7E8CA21D0BC7F10057A1C1 /* CSVError.swift in Sources */, + 0EC628FF1EF675FD00289554 /* CSV.swift in Sources */, 0EA2AB7D1D183B45003EC967 /* BinaryReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -565,12 +565,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0EBC9E6C1EE064A500432A2D /* CSV.swift in Sources */, 0EA2AB841D183BA9003EC967 /* UnicodeIterator.swift in Sources */, 0E5402221EDA82220019C3ED /* CSVWriter.swift in Sources */, 0E7E8CBE1D0BC9D70057A1C1 /* CSVReader.swift in Sources */, 0E0F16111D197DB800C92580 /* Endian.swift in Sources */, 0E7E8CBF1D0BC9D70057A1C1 /* CSVError.swift in Sources */, + 0EC629011EF675FF00289554 /* CSV.swift in Sources */, 0EA2AB7F1D183B45003EC967 /* BinaryReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -579,12 +579,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0EBC9E691EE064A500432A2D /* CSV.swift in Sources */, 0EA2AB811D183BA9003EC967 /* UnicodeIterator.swift in Sources */, 0E5402251EDA82230019C3ED /* CSVWriter.swift in Sources */, 0E7E8CE01D0BCA8E0057A1C1 /* CSVReader.swift in Sources */, 0E0F160E1D197DB800C92580 /* Endian.swift in Sources */, 0E7E8CE11D0BCA8E0057A1C1 /* CSVError.swift in Sources */, + 0EC628FE1EF675B300289554 /* CSV.swift in Sources */, 0EA2AB7C1D183B45003EC967 /* BinaryReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -607,12 +607,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0EBC9E6B1EE064A500432A2D /* CSV.swift in Sources */, 0EA2AB831D183BA9003EC967 /* UnicodeIterator.swift in Sources */, 0E5402231EDA82220019C3ED /* CSVWriter.swift in Sources */, 0E7E8D001D0BCDCF0057A1C1 /* CSVReader.swift in Sources */, 0E0F16101D197DB800C92580 /* Endian.swift in Sources */, 0E7E8D011D0BCDCF0057A1C1 /* CSVError.swift in Sources */, + 0EC629001EF675FE00289554 /* CSV.swift in Sources */, 0EA2AB7E1D183B45003EC967 /* BinaryReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/CSV.swift b/Sources/CSV.swift similarity index 100% rename from CSV.swift rename to Sources/CSV.swift From 7ad80457ab5a143d9b86d629ac6f839efd2446b3 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 18 Jun 2017 17:53:37 +0900 Subject: [PATCH 33/36] Fix test for Linux --- Tests/CSVTests/Version1Tests.swift | 9 +++++++-- Tests/LinuxMain.swift | 5 +++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Tests/CSVTests/Version1Tests.swift b/Tests/CSVTests/Version1Tests.swift index 1cc0533..ced4ea5 100644 --- a/Tests/CSVTests/Version1Tests.swift +++ b/Tests/CSVTests/Version1Tests.swift @@ -8,10 +8,15 @@ import Foundation import XCTest -@testable import CSV + +import CSV class Version1Tests: XCTestCase { + static let allTests = [ + ("testV1", testV1) + ] + func testV1() { let str = "a,b,c\n1,2,3" let data8 = str.data(using: .utf8)! @@ -92,7 +97,7 @@ class Version1Tests: XCTestCase { _ = CSVError.cannotOpenFile _ = CSVError.cannotReadFile - _ = CSVError.streamErrorHasOccurred(error: NSError()) + _ = CSVError.streamErrorHasOccurred(error: NSError(domain: "", code: 0, userInfo: nil)) _ = CSVError.cannotReadHeaderRow _ = CSVError.stringEncodingMismatch _ = CSVError.stringEndianMismatch diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 4359671..15d3947 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -3,7 +3,7 @@ // CSV // // Created by Yasuhiro Hatta on 2016/06/11. -// +// Copyright © 2016 yaslab. All rights reserved. // import XCTest @@ -15,5 +15,6 @@ XCTMain([ testCase(LineBreakTests.allTests), testCase(ReadmeTests.allTests), testCase(TrimFieldsTests.allTests), - testCase(UnicodeTests.allTests) + testCase(UnicodeTests.allTests), + testCase(Version1Tests.allTests) ]) From 0ef7b0eb0e6f87fdf0fb51ab47b76476960fb880 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Mon, 19 Jun 2017 02:22:53 +0900 Subject: [PATCH 34/36] Bump version 2.0.0 --- CSV.swift.podspec | 2 +- README.md | 94 +++++++++++++++++++++++-------- Sources/Info.plist | 2 +- Tests/CSVTests/ReadmeTests.swift | 97 +++++++++++++++++++++----------- 4 files changed, 135 insertions(+), 60 deletions(-) diff --git a/CSV.swift.podspec b/CSV.swift.podspec index 7a1198c..5ce431b 100644 --- a/CSV.swift.podspec +++ b/CSV.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'CSV.swift' - s.version = '1.1.2' + s.version = '2.0.0' s.license = 'MIT' s.summary = 'CSV reading library written in Swift.' s.homepage = 'https://github.com/yaslab/CSV.swift' diff --git a/README.md b/README.md index 3f447d2..6d29fbf 100644 --- a/README.md +++ b/README.md @@ -2,30 +2,33 @@ [![Build Status](https://travis-ci.org/yaslab/CSV.swift.svg?branch=master)](https://travis-ci.org/yaslab/CSV.swift) -CSV reading library written in Swift. +CSV reading and writing library written in Swift. -## Usage +## Usage for reading CSV -### From CSV string +### From string ```swift import CSV -for row in try! CSV(string: "1,foo\n2,bar") { +let csvString = "1,foo\n2,bar" +let csv = try! CSVReader(string: csvString) +while let row = csv.next() { print("\(row)") - // => ["1", "foo"] - // => ["2", "bar"] } +// => ["1", "foo"] +// => ["2", "bar"] ``` -### From file path +### From file ```swift import Foundation import CSV let stream = InputStream(fileAtPath: "/path/to/file.csv")! -for row in try! CSV(stream: stream) { +let csv = try! CSVReader(stream: stream) +while let row = csv.next() { print("\(row)") } ``` @@ -35,18 +38,18 @@ for row in try! CSV(stream: stream) { ```swift import CSV -let csv = try! CSV( - string: "id,name\n1,foo\n2,bar", - hasHeaderRow: true) // default: false +let csvString = "id,name\n1,foo\n2,bar" +let csv = try! CSVReader(string: csvString, + hasHeaderRow: true) // It must be true. let headerRow = csv.headerRow! print("\(headerRow)") // => ["id", "name"] -for row in csv { +while let row = csv.next() { print("\(row)") - // => ["1", "foo"] - // => ["2", "bar"] } +// => ["1", "foo"] +// => ["2", "bar"] ``` ### Get the field value using subscript @@ -54,11 +57,11 @@ for row in csv { ```swift import CSV -var csv = try! CSV( - string: "id,name\n1,foo", - hasHeaderRow: true) // It must be true. +let csvString = "id,name\n1,foo" +let csv = try! CSVReader(string: csvString, + hasHeaderRow: true) // It must be true. -while let _ = csv.next() { +while csv.next() != nil { print("\(csv["id"]!)") // => "1" print("\(csv["name"]!)") // => "foo" } @@ -72,10 +75,51 @@ If you use a file path, you can provide the character encoding to initializer. import Foundation import CSV -let csv = try! CSV( - stream: InputStream(fileAtPath: "/path/to/file.csv")!, - codecType: UTF16.self, - endian: .big) +let stream = InputStream(fileAtPath: "/path/to/file.csv")! +let csv = try! CSV(stream: stream, + codecType: UTF16.self, + endian: .big) +``` + +## Usage for writing CSV + +### Write to memory and get a CSV String + +```swift +let stream = OutputStream(toMemory: ()) +let csv = try! CSVWriter(stream: stream) + +// Write a row +try! csv.write(row: ["id", "name"]) + +// Write fields separately +csv.beginNewRow() +try! csv.write(field: "1") +try! csv.write(field: "foo") +csv.beginNewRow() +try! csv.write(field: "2") +try! csv.write(field: "bar") + +csv.stream.close() + +// Get a String +let csvData = stream.property(forKey: .dataWrittenToMemoryStreamKey) as! NSData +let csvString = String(data: Data(referencing: csvData), encoding: .utf8)! +print(csvString) +// => "id,name\n1,foo\n2,bar" +``` + +### Write to file + +```swift +let stream = OutputStream(toFileAtPath: "/path/to/file.csv", append: false)! +let csv = try! CSVWriter(stream: stream) + +try! csv.write(row: ["id", "name"]) +try! csv.write(row: ["1", "foo"]) +try! csv.write(row: ["1", "bar"]) + +csv.stream.close() ``` ## Installation @@ -83,13 +127,13 @@ let csv = try! CSV( ### CocoaPods ```ruby -pod 'CSV.swift', '~> 1.1' +pod 'CSV.swift', '~> 2.0' ``` ### Carthage ``` -github "yaslab/CSV.swift" ~> 1.1 +github "yaslab/CSV.swift" ~> 2.0 ``` ### Swift Package Manager @@ -100,7 +144,7 @@ import PackageDescription let package = Package( name: "PackageName", dependencies: [ - .Package(url: "https://github.com/yaslab/CSV.swift.git", majorVersion: 1, minor: 1) + .Package(url: "https://github.com/yaslab/CSV.swift.git", majorVersion: 2, minor: 0) ] ) ``` diff --git a/Sources/Info.plist b/Sources/Info.plist index 3034b22..7e7479f 100644 --- a/Sources/Info.plist +++ b/Sources/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.1.2 + 2.0.0 CFBundleSignature ???? CFBundleVersion diff --git a/Tests/CSVTests/ReadmeTests.swift b/Tests/CSVTests/ReadmeTests.swift index a317c22..ce24aa7 100644 --- a/Tests/CSVTests/ReadmeTests.swift +++ b/Tests/CSVTests/ReadmeTests.swift @@ -15,24 +15,28 @@ class ReadmeTests: XCTestCase { ("testFromCSVString", testFromCSVString), ("testFromFile", testFromFile), ("testGettingTheHeaderRow", testGettingTheHeaderRow), - ("testGetTheFieldValueUsingIndex", testGetTheFieldValueUsingIndex), ("testGetTheFieldValueUsingKey", testGetTheFieldValueUsingKey), - ("testProvideTheCharacterEncoding", testProvideTheCharacterEncoding) + ("testProvideTheCharacterEncoding", testProvideTheCharacterEncoding), + ("testWriteToMemory", testWriteToMemory), + ("testWriteToFile", testWriteToFile) ] + + // MARK: - Reading func testFromCSVString() { - let csv = try! CSVReader(string: "1,foo\n2,bar") - try! csv.enumerateRows { (row, _, _) in + let csvString = "1,foo\n2,bar" + let csv = try! CSVReader(string: csvString) + while let row = csv.next() { print("\(row)") - // => ["1", "foo"] - // => ["2", "bar"] } + // => ["1", "foo"] + // => ["2", "bar"] } func testFromFile() { // let stream = InputStream(fileAtPath: "/path/to/file.csv")! -// let csv = try! CSV(stream: stream) -// for row in csv { +// let csv = try! CSVReader(stream: stream) +// while let row = csv.next() { // print("\(row)") // } } @@ -45,39 +49,66 @@ class ReadmeTests: XCTestCase { let headerRow = csv.headerRow! print("\(headerRow)") // => ["id", "name"] - try! csv.enumerateRows { (row, _, _) in + while let row = csv.next() { print("\(row)") - // => ["1", "foo"] - // => ["2", "bar"] - } - } - - func testGetTheFieldValueUsingIndex() { - let csvString = "1,foo" - let csv = try! CSVReader(string: csvString) - - try! csv.enumerateRows { (row, _, _) in - print("\(row[0])") // => "1" - print("\(row[1])") // => "foo" } + // => ["1", "foo"] + // => ["2", "bar"] } func testGetTheFieldValueUsingKey() { -// let csvString = "id,name\n1,foo" -// let config = CSVReader.Configuration(hasHeaderRow: true) // It must be true. -// let csv = try! CSVReader(string: csvString, configuration: config) -// -// csv.enumerateRecords { (record, _, _) in -// print("\(record["id"]!)") // => "1" -// print("\(record["name"]!)") // => "foo" -// } + let csvString = "id,name\n1,foo" + let csv = try! CSVReader(string: csvString, + hasHeaderRow: true) // It must be true. + + while csv.next() != nil { + print("\(csv["id"]!)") // => "1" + print("\(csv["name"]!)") // => "foo" + } } func testProvideTheCharacterEncoding() { -// let csv = try! CSV( -// stream: InputStream(fileAtPath: "/path/to/file.csv")!, -// codecType: UTF16.self, -// endian: .big) +// let stream = InputStream(fileAtPath: "/path/to/file.csv")! +// let csv = try! CSV(stream: stream, +// codecType: UTF16.self, +// endian: .big) } + // MARK: - Writing + + func testWriteToMemory() { + let stream = OutputStream(toMemory: ()) + let csv = try! CSVWriter(stream: stream) + + // Write a row + try! csv.write(row: ["id", "name"]) + + // Write fields separately + csv.beginNewRow() + try! csv.write(field: "1") + try! csv.write(field: "foo") + csv.beginNewRow() + try! csv.write(field: "2") + try! csv.write(field: "bar") + + csv.stream.close() + + // Get a String + let csvData = stream.property(forKey: .dataWrittenToMemoryStreamKey) as! NSData + let csvString = String(data: Data(referencing: csvData), encoding: .utf8)! + print(csvString) + // => "id,name\n1,foo\n2,bar" + } + + func testWriteToFile() { +// let stream = OutputStream(toFileAtPath: "/path/to/file.csv", append: false)! +// let csv = try! CSVWriter(stream: stream) +// +// try! csv.write(row: ["id", "name"]) +// try! csv.write(row: ["1", "foo"]) +// try! csv.write(row: ["1", "bar"]) +// +// csv.stream.close() + } + } From 015caedd64179c9b8a2ac2681e1251961ea22753 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Mon, 19 Jun 2017 02:28:42 +0900 Subject: [PATCH 35/36] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 6d29fbf..50e5a68 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,9 @@ let csv = try! CSV(stream: stream, ### Write to memory and get a CSV String ```swift +import Foundation +import CSV + let stream = OutputStream(toMemory: ()) let csv = try! CSVWriter(stream: stream) @@ -112,6 +115,9 @@ print(csvString) ### Write to file ```swift +import Foundation +import CSV + let stream = OutputStream(toFileAtPath: "/path/to/file.csv", append: false)! let csv = try! CSVWriter(stream: stream) From 3639a75c33f926a6a89279c05544d3acd5ac5afa Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Mon, 19 Jun 2017 02:42:35 +0900 Subject: [PATCH 36/36] Add enum Newline for Writer --- Sources/CSVReader.swift | 33 ++++++++++++++--------------- Sources/CSVWriter.swift | 25 ++++++++++++++++------ Tests/CSVTests/CSVWriterTests.swift | 2 +- Tests/CSVTests/LineBreakTests.swift | 11 +++++----- Tests/CSVTests/UnicodeTests.swift | 11 +++++----- 5 files changed, 48 insertions(+), 34 deletions(-) diff --git a/Sources/CSVReader.swift b/Sources/CSVReader.swift index e2cc9b3..5530373 100644 --- a/Sources/CSVReader.swift +++ b/Sources/CSVReader.swift @@ -19,7 +19,6 @@ internal let defaultHasHeaderRow = false internal let defaultTrimFields = false internal let defaultDelimiter: UnicodeScalar = "," internal let defaultWhitespaces = CharacterSet.whitespaces -internal let defaultNewline: UnicodeScalar = LF /// No overview available. public class CSVReader { @@ -353,22 +352,22 @@ extension CSVReader { } -extension CSVReader { - - public func enumerateRows(_ block: (([String], [String]?, inout Bool) throws -> Void)) throws { - var stop = false - while let row = readRow() { - try block(row, headerRow, &stop) - if stop { - break - } - } - if let error = error { - throw error - } - } - -} +//extension CSVReader { +// +// public func enumerateRows(_ block: (([String], [String]?, inout Bool) throws -> Void)) throws { +// var stop = false +// while let row = readRow() { +// try block(row, headerRow, &stop) +// if stop { +// break +// } +// } +// if let error = error { +// throw error +// } +// } +// +//} extension CSVReader: IteratorProtocol { diff --git a/Sources/CSVWriter.swift b/Sources/CSVWriter.swift index 97f8bc0..d538e04 100644 --- a/Sources/CSVWriter.swift +++ b/Sources/CSVWriter.swift @@ -15,13 +15,26 @@ public class CSVWriter { public var delimiter: String public var newline: String - internal init(delimiter: String, newline: String) { + internal init(delimiter: String, newline: Newline) { self.delimiter = delimiter - self.newline = newline + + switch newline { + case .lf: self.newline = String(LF) + case .crlf: self.newline = String(CR) + String(LF) + } } } + public enum Newline { + + /// "\n" + case lf + /// "\r\n" + case crlf + + } + public let stream: OutputStream public let configuration: Configuration fileprivate let writeScalar: ((UnicodeScalar) throws -> Void) @@ -59,7 +72,7 @@ extension CSVWriter { public convenience init( stream: OutputStream, delimiter: String = String(defaultDelimiter), - newline: String = String(defaultNewline) + newline: Newline = .lf ) throws { try self.init(stream: stream, codecType: UTF8.self, delimiter: delimiter, newline: newline) @@ -69,7 +82,7 @@ extension CSVWriter { stream: OutputStream, codecType: T.Type, delimiter: String = String(defaultDelimiter), - newline: String = String(defaultNewline) + newline: Newline = .lf ) throws where T.CodeUnit == UInt8 { let config = Configuration(delimiter: delimiter, newline: newline) @@ -93,7 +106,7 @@ extension CSVWriter { codecType: T.Type, endian: Endian = .big, delimiter: String = String(defaultDelimiter), - newline: String = String(defaultNewline) + newline: Newline = .lf ) throws where T.CodeUnit == UInt16 { let config = Configuration(delimiter: delimiter, newline: newline) @@ -120,7 +133,7 @@ extension CSVWriter { codecType: T.Type, endian: Endian = .big, delimiter: String = String(defaultDelimiter), - newline: String = String(defaultNewline) + newline: Newline = .lf ) throws where T.CodeUnit == UInt32 { let config = Configuration(delimiter: delimiter, newline: newline) diff --git a/Tests/CSVTests/CSVWriterTests.swift b/Tests/CSVTests/CSVWriterTests.swift index 4dddb38..f16e69b 100644 --- a/Tests/CSVTests/CSVWriterTests.swift +++ b/Tests/CSVTests/CSVWriterTests.swift @@ -188,7 +188,7 @@ class CSVWriterTests: XCTestCase { let stream = OutputStream(toMemory: ()) stream.open() - let csv = try! CSVWriter.init(stream: stream, newline: "\r\n") + let csv = try! CSVWriter.init(stream: stream, newline: .crlf) csv.beginNewRow() try! csv.write(field: str + "-1") csv.beginNewRow() diff --git a/Tests/CSVTests/LineBreakTests.swift b/Tests/CSVTests/LineBreakTests.swift index b5383b7..8186137 100644 --- a/Tests/CSVTests/LineBreakTests.swift +++ b/Tests/CSVTests/LineBreakTests.swift @@ -125,11 +125,12 @@ class LineBreakTests: XCTestCase { private func parse(csv: String) -> [[String]] { let reader = try! CSVReader(string: csv) - var records = [[String]]() - try! reader.enumerateRows { (row, _, _) in - records.append(row) - } - return records + return reader.map { $0 } +// var records = [[String]]() +// try! reader.enumerateRows { (row, _, _) in +// records.append(row) +// } +// return records } } diff --git a/Tests/CSVTests/UnicodeTests.swift b/Tests/CSVTests/UnicodeTests.swift index 99a620b..0bba796 100644 --- a/Tests/CSVTests/UnicodeTests.swift +++ b/Tests/CSVTests/UnicodeTests.swift @@ -112,11 +112,12 @@ class UnicodeTests: XCTestCase { } private func getRecords(csv: CSVReader) -> [[String]] { - var records = [[String]]() - try! csv.enumerateRows { (record, _, _) in - records.append(record) - } - return records + return csv.map { $0 } +// var records = [[String]]() +// try! csv.enumerateRows { (record, _, _) in +// records.append(record) +// } +// return records } }