From 44b485739666ee0e2ab64d98fa199c06c6d6bb43 Mon Sep 17 00:00:00 2001 From: Yasuhiro Hatta Date: Sun, 23 Oct 2016 00:02:31 +0900 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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 + } + +}