Rename CSV to CSVReader

This commit is contained in:
Yasuhiro Hatta 2017-05-29 01:33:24 +09:00
parent 03c2d8bce3
commit 7d56f466a9
11 changed files with 405 additions and 496 deletions

View File

@ -11,10 +11,6 @@
0E0F160F1D197DB800C92580 /* Endian.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F160D1D197DB800C92580 /* Endian.swift */; };
0E0F16101D197DB800C92580 /* Endian.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F160D1D197DB800C92580 /* Endian.swift */; };
0E0F16111D197DB800C92580 /* Endian.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F160D1D197DB800C92580 /* Endian.swift */; };
0E47EEC11DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; };
0E47EEC21DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; };
0E47EEC31DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; };
0E47EEC41DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; };
0E54021B1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; };
0E54021C1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; };
0E54021D1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; };
@ -23,24 +19,20 @@
0E5402241EDA82220019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */; };
0E5402251EDA82230019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021E1EDA81E80019C3ED /* CSVWriter.swift */; };
0E7E8C8C1D0BC7BB0057A1C1 /* CSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E7E8C811D0BC7BB0057A1C1 /* CSV.framework */; };
0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; };
0E7E8CA11D0BC7F10057A1C1 /* CSVReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */; };
0E7E8CA21D0BC7F10057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; };
0E7E8CA31D0BC7F10057A1C1 /* CSVVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */; settings = {ATTRIBUTES = (Public, ); }; };
0E7E8CBE1D0BC9D70057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; };
0E7E8CBE1D0BC9D70057A1C1 /* CSVReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */; };
0E7E8CBF1D0BC9D70057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; };
0E7E8CC01D0BC9D70057A1C1 /* CSVVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */; settings = {ATTRIBUTES = (Public, ); }; };
0E7E8CD01D0BCA2A0057A1C1 /* CSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E7E8CC61D0BCA2A0057A1C1 /* CSV.framework */; };
0E7E8CE01D0BCA8E0057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; };
0E7E8CE01D0BCA8E0057A1C1 /* CSVReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */; };
0E7E8CE11D0BCA8E0057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; };
0E7E8CE21D0BCA8E0057A1C1 /* CSVVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */; settings = {ATTRIBUTES = (Public, ); }; };
0E7E8CF21D0BCD0B0057A1C1 /* CSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E7E8CE81D0BCD0B0057A1C1 /* CSV.framework */; };
0E7E8D001D0BCDCF0057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; };
0E7E8D001D0BCDCF0057A1C1 /* CSVReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */; };
0E7E8D011D0BCDCF0057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; };
0E7E8D021D0BCDCF0057A1C1 /* CSVVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */; settings = {ATTRIBUTES = (Public, ); }; };
0E9317D41D0DB2F200AC20A0 /* CSV+init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */; };
0E9317D51D0DB2F200AC20A0 /* CSV+init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */; };
0E9317D61D0DB2F200AC20A0 /* CSV+init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */; };
0E9317D71D0DB2F200AC20A0 /* CSV+init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */; };
0EA2AB7C1D183B45003EC967 /* BinaryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */; };
0EA2AB7D1D183B45003EC967 /* BinaryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */; };
0EA2AB7E1D183B45003EC967 /* BinaryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */; };
@ -49,10 +41,6 @@
0EA2AB821D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; };
0EA2AB831D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; };
0EA2AB841D183BA9003EC967 /* UnicodeIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */; };
0EDF8EAF1DDB6D620068056A /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */; };
0EDF8EB01DDB6DAC0068056A /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */; };
0EDF8EB11DDB6DAC0068056A /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */; };
0EDF8EB21DDB6DAD0068056A /* CSVConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */; };
0EDF8ED71DDB73520068056A /* CSVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECD1DDB73370068056A /* CSVTests.swift */; };
0EDF8ED81DDB73520068056A /* LineBreakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECE1DDB73370068056A /* LineBreakTests.swift */; };
0EDF8ED91DDB73520068056A /* ReadmeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */; };
@ -96,12 +84,11 @@
/* Begin PBXFileReference section */
0E0F160D1D197DB800C92580 /* Endian.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Endian.swift; sourceTree = "<group>"; };
0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSV+iterator.swift"; sourceTree = "<group>"; };
0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVWriterTests.swift; sourceTree = "<group>"; };
0E54021E1EDA81E80019C3ED /* CSVWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVWriter.swift; sourceTree = "<group>"; };
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 = "<group>"; };
0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVReader.swift; sourceTree = "<group>"; };
0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVError.swift; sourceTree = "<group>"; };
0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSVVersion.h; sourceTree = "<group>"; };
0E7E8CAC1D0BC8610057A1C1 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -111,10 +98,8 @@
0E7E8CCF1D0BCA2A0057A1C1 /* CSVTests-OSX.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CSVTests-OSX.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
0E7E8CE81D0BCD0B0057A1C1 /* CSV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CSV.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0E7E8CF11D0BCD0B0057A1C1 /* CSVTests-tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CSVTests-tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSV+init.swift"; sourceTree = "<group>"; };
0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinaryReader.swift; sourceTree = "<group>"; };
0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeIterator.swift; sourceTree = "<group>"; };
0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVConfiguration.swift; sourceTree = "<group>"; };
0EDF8ECD1DDB73370068056A /* CSVTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVTests.swift; sourceTree = "<group>"; };
0EDF8ECE1DDB73370068056A /* LineBreakTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineBreakTests.swift; sourceTree = "<group>"; };
0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadmeTests.swift; sourceTree = "<group>"; };
@ -206,11 +191,8 @@
isa = PBXGroup;
children = (
0EA2AB7B1D183B45003EC967 /* BinaryReader.swift */,
0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */,
0E9317D31D0DB2F200AC20A0 /* CSV+init.swift */,
0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */,
0EDF8EAE1DDB6D620068056A /* CSVConfiguration.swift */,
0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */,
0E7E8C9D1D0BC7F10057A1C1 /* CSVReader.swift */,
0E7E8C9F1D0BC7F10057A1C1 /* CSVVersion.h */,
0E54021E1EDA81E80019C3ED /* CSVWriter.swift */,
0E0F160D1D197DB800C92580 /* Endian.swift */,
@ -544,13 +526,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0E9317D51D0DB2F200AC20A0 /* CSV+init.swift in Sources */,
0EA2AB821D183BA9003EC967 /* UnicodeIterator.swift in Sources */,
0E47EEC21DBCDB1800EBF783 /* CSV+iterator.swift in Sources */,
0E5402241EDA82220019C3ED /* CSVWriter.swift in Sources */,
0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */,
0E7E8CA11D0BC7F10057A1C1 /* CSVReader.swift in Sources */,
0E0F160F1D197DB800C92580 /* Endian.swift in Sources */,
0EDF8EB01DDB6DAC0068056A /* CSVConfiguration.swift in Sources */,
0E7E8CA21D0BC7F10057A1C1 /* CSVError.swift in Sources */,
0EA2AB7D1D183B45003EC967 /* BinaryReader.swift in Sources */,
);
@ -573,13 +552,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0E9317D71D0DB2F200AC20A0 /* CSV+init.swift in Sources */,
0EA2AB841D183BA9003EC967 /* UnicodeIterator.swift in Sources */,
0E47EEC41DBCDB1800EBF783 /* CSV+iterator.swift in Sources */,
0E5402221EDA82220019C3ED /* CSVWriter.swift in Sources */,
0E7E8CBE1D0BC9D70057A1C1 /* CSV.swift in Sources */,
0E7E8CBE1D0BC9D70057A1C1 /* CSVReader.swift in Sources */,
0E0F16111D197DB800C92580 /* Endian.swift in Sources */,
0EDF8EB21DDB6DAD0068056A /* CSVConfiguration.swift in Sources */,
0E7E8CBF1D0BC9D70057A1C1 /* CSVError.swift in Sources */,
0EA2AB7F1D183B45003EC967 /* BinaryReader.swift in Sources */,
);
@ -589,13 +565,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0E9317D41D0DB2F200AC20A0 /* CSV+init.swift in Sources */,
0EA2AB811D183BA9003EC967 /* UnicodeIterator.swift in Sources */,
0E47EEC11DBCDB1800EBF783 /* CSV+iterator.swift in Sources */,
0E5402251EDA82230019C3ED /* CSVWriter.swift in Sources */,
0E7E8CE01D0BCA8E0057A1C1 /* CSV.swift in Sources */,
0E7E8CE01D0BCA8E0057A1C1 /* CSVReader.swift in Sources */,
0E0F160E1D197DB800C92580 /* Endian.swift in Sources */,
0EDF8EAF1DDB6D620068056A /* CSVConfiguration.swift in Sources */,
0E7E8CE11D0BCA8E0057A1C1 /* CSVError.swift in Sources */,
0EA2AB7C1D183B45003EC967 /* BinaryReader.swift in Sources */,
);
@ -618,13 +591,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0E9317D61D0DB2F200AC20A0 /* CSV+init.swift in Sources */,
0EA2AB831D183BA9003EC967 /* UnicodeIterator.swift in Sources */,
0E47EEC31DBCDB1800EBF783 /* CSV+iterator.swift in Sources */,
0E5402231EDA82220019C3ED /* CSVWriter.swift in Sources */,
0E7E8D001D0BCDCF0057A1C1 /* CSV.swift in Sources */,
0E7E8D001D0BCDCF0057A1C1 /* CSVReader.swift in Sources */,
0E0F16101D197DB800C92580 /* Endian.swift in Sources */,
0EDF8EB11DDB6DAC0068056A /* CSVConfiguration.swift in Sources */,
0E7E8D011D0BCDCF0057A1C1 /* CSVError.swift in Sources */,
0EA2AB7E1D183B45003EC967 /* BinaryReader.swift in Sources */,
);

View File

@ -1,41 +0,0 @@
//
// CSV+init.swift
// CSV
//
// Created by Yasuhiro Hatta on 2016/06/13.
// Copyright © 2016 yaslab. All rights reserved.
//
import Foundation
extension CSV {
/// Create an instance with `InputStream`.
///
/// - parameter stream: An `InputStream` object. If the stream is not open,
/// initializer opens automatically.
/// - parameter config: CSV configuration.
public convenience init(
stream: InputStream,
config: CSVConfiguration = CSVConfiguration()) throws {
try self.init(stream: stream, codecType: UTF8.self, config: config)
}
}
extension CSV {
/// Create an instance with CSV string.
///
/// - parameter string: An CSV string.
/// - parameter config: CSV configuration.
public convenience init(
string: String,
config: CSVConfiguration = CSVConfiguration()) throws {
let iterator = string.unicodeScalars.makeIterator()
try self.init(iterator: iterator, config: config)
}
}

View File

@ -1,94 +0,0 @@
//
// CSV+iterator.swift
// CSV
//
// Created by Yasuhiro Hatta on 2016/10/23.
// Copyright © 2016 yaslab. All rights reserved.
//
extension CSV: IteratorProtocol, Sequence {
/// No overview available.
public func next() -> Row? {
guard let row = readRow() else {
return nil
}
return Row(data: row, headerRow: headerRow)
}
}
extension CSV {
/// No overview available.
public class Row: RandomAccessCollection {
private let data: [String]
private let headerRow: [String]?
internal init(data: [String], headerRow: [String]?) {
self.data = data
self.headerRow = headerRow
}
// MARK: - RandomAccessCollection
/// No overview available.
public var startIndex: Int {
return data.startIndex
}
/// No overview available.
public var endIndex: Int {
return data.endIndex
}
/// No overview available.
public func index(before i: Int) -> Int {
return data.index(before: i)
}
/// No overview available.
public func index(after i: Int) -> Int {
return data.index(after: i)
}
/// No overview available.
public subscript(index: Int) -> String {
return data[index]
}
// MARK: - Public method
/// No overview available.
public subscript(key: String) -> String? {
assert(headerRow != nil, "CSVConfiguration.hasHeaderRow must be true")
guard let index = headerRow!.index(of: key) else {
return nil
}
guard (data.startIndex ..< data.endIndex).contains(index) else {
return nil
}
return data[index]
}
/// No overview available.
public func toArray() -> [String] {
return data
}
/// No overview available.
public func toDictionary() -> [String : String] {
assert(headerRow != nil, "CSVConfiguration.hasHeaderRow must be true")
var dictionary: [String : String] = [:]
for (key, value) in zip(headerRow!, data) {
if !dictionary.keys.contains(key) {
dictionary[key] = value
}
}
return dictionary
}
}
}

View File

@ -1,46 +0,0 @@
//
// CSVConfiguration.swift
// CSV
//
// Created by Yasuhiro Hatta on 2016/10/22.
// Copyright © 2016 yaslab. All rights reserved.
//
import Foundation
internal let defaultHasHeaderRow = false
internal let defaultTrimFields = false
internal let defaultDelimiter: UnicodeScalar = ","
internal let defaultWhitespaces = CharacterSet.whitespaces
/// No overview available.
public class CSVConfiguration {
public var fileInputErrorHandler: ((Error, Int, Int) -> Void)? = nil
/// `true` if the CSV has a header row, otherwise `false`. Default: `false`.
public let hasHeaderRow: Bool
/// No overview available.
public let trimFields: Bool
/// Default: `","`.
public let delimiter: UnicodeScalar
/// No overview available.
public let whitespaces: CharacterSet
/// No overview available.
public init(
hasHeaderRow: Bool = defaultHasHeaderRow,
trimFields: Bool = defaultTrimFields,
delimiter: UnicodeScalar = defaultDelimiter,
whitespaces: CharacterSet = defaultWhitespaces) {
self.hasHeaderRow = hasHeaderRow
self.trimFields = trimFields
self.delimiter = delimiter
var whitespaces = whitespaces
_ = whitespaces.remove(delimiter)
self.whitespaces = whitespaces
}
}

View File

@ -20,7 +20,7 @@ public enum CSVError: Error {
/// No overview available.
case unicodeDecoding
/// No overview available.
case cannotReadHeaderRow
case cannotReadHeaderRecord
/// No overview available.
case stringEncodingMismatch
/// No overview available.

View File

@ -1,5 +1,5 @@
//
// CSV.swift
// CSVReader.swift
// CSV
//
// Created by Yasuhiro Hatta on 2016/06/11.
@ -15,38 +15,82 @@ internal let DQUOTE: UnicodeScalar = "\""
internal let DQUOTE_STR: String = "\""
internal let DQUOTE2_STR: String = "\"\""
internal let defaultHasHeaderRecord = false
internal let defaultTrimFields = false
internal let defaultDelimiter: UnicodeScalar = ","
internal let defaultWhitespaces = CharacterSet.whitespaces
/// No overview available.
public class CSV {
public class CSVReader {
/// No overview available.
public struct Configuration {
public var fileInputErrorHandler: ((Error, Int, Int) -> Void)? = nil
/// `true` if the CSV has a header record, otherwise `false`. Default: `false`.
public var hasHeaderRecord: Bool
/// No overview available.
public var trimFields: Bool
/// Default: `","`.
public var delimiter: UnicodeScalar
/// No overview available.
public var whitespaces: CharacterSet
/// No overview available.
public init(
hasHeaderRecord: Bool = defaultHasHeaderRecord,
trimFields: Bool = defaultTrimFields,
delimiter: UnicodeScalar = defaultDelimiter,
whitespaces: CharacterSet = defaultWhitespaces) {
self.hasHeaderRecord = hasHeaderRecord
self.trimFields = trimFields
self.delimiter = delimiter
var whitespaces = whitespaces
_ = whitespaces.remove(delimiter)
self.whitespaces = whitespaces
}
}
private var iterator: AnyIterator<UnicodeScalar>
private let config: CSVConfiguration
fileprivate var iterator: AnyIterator<UnicodeScalar>
//public let stream: InputStream?
public let configuration: Configuration
private var back: UnicodeScalar? = nil
private var fieldBuffer = String.UnicodeScalarView()
fileprivate var back: UnicodeScalar? = nil
fileprivate var fieldBuffer = String.UnicodeScalarView()
private var currentRowIndex: Int = 0
private var currentFieldIndex: Int = 0
fileprivate var currentRecordIndex: Int = 0
fileprivate var currentFieldIndex: Int = 0
/// CSV header row. To set a value for this property,
/// you set `true` to `hasHeaerRow` in initializer.
public private(set) var headerRow: [String]? = nil
/// CSV header record. To set a value for this property,
/// you set `true` to `headerRecord` in initializer.
public private (set) var headerRecord: [String]? = nil
public fileprivate (set) var currentRecord: [String]? = nil
internal init<T: IteratorProtocol>(
iterator: T,
config: CSVConfiguration
configuration: Configuration
) throws where T.Element == UnicodeScalar {
self.iterator = AnyIterator(iterator)
self.config = config
self.configuration = configuration
if config.hasHeaderRow {
guard let headerRow = readRow() else {
throw CSVError.cannotReadHeaderRow
if configuration.hasHeaderRecord {
guard let headerRecord = readRecord() else {
throw CSVError.cannotReadHeaderRecord
}
self.headerRow = headerRow
self.headerRecord = headerRecord
}
}
}
extension CSVReader {
/// Create an instance with `InputStream`.
///
/// - parameter stream: An `InputStream` object. If the stream is not open,
@ -56,13 +100,13 @@ public class CSV {
public convenience init<T: UnicodeCodec>(
stream: InputStream,
codecType: T.Type,
config: CSVConfiguration = CSVConfiguration()
configuration: Configuration = Configuration()
) throws where T.CodeUnit == UInt8 {
let reader = try BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true)
let input = reader.makeUInt8Iterator()
let iterator = UnicodeIterator(input: input, inputEncodingType: codecType)
try self.init(iterator: iterator, config: config)
try self.init(iterator: iterator, configuration: configuration)
input.errorHandler = { [unowned self] in self.errorHandler(error: $0) }
iterator.errorHandler = { [unowned self] in self.errorHandler(error: $0) }
}
@ -78,13 +122,13 @@ public class CSV {
stream: InputStream,
codecType: T.Type,
endian: Endian = .big,
config: CSVConfiguration = CSVConfiguration()
configuration: Configuration = Configuration()
) throws where T.CodeUnit == UInt16 {
let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true)
let input = reader.makeUInt16Iterator()
let iterator = UnicodeIterator(input: input, inputEncodingType: codecType)
try self.init(iterator: iterator, config: config)
try self.init(iterator: iterator, configuration: configuration)
input.errorHandler = { [unowned self] in self.errorHandler(error: $0) }
iterator.errorHandler = { [unowned self] in self.errorHandler(error: $0) }
}
@ -100,20 +144,52 @@ public class CSV {
stream: InputStream,
codecType: T.Type,
endian: Endian = .big,
config: CSVConfiguration = CSVConfiguration()
configuration: Configuration = Configuration()
) throws where T.CodeUnit == UInt32 {
let reader = try BinaryReader(stream: stream, endian: endian, closeOnDeinit: true)
let input = reader.makeUInt32Iterator()
let iterator = UnicodeIterator(input: input, inputEncodingType: codecType)
try self.init(iterator: iterator, config: config)
try self.init(iterator: iterator, configuration: configuration)
input.errorHandler = { [unowned self] in self.errorHandler(error: $0) }
iterator.errorHandler = { [unowned self] in self.errorHandler(error: $0) }
}
/// Create an instance with `InputStream`.
///
/// - parameter stream: An `InputStream` object. If the stream is not open,
/// initializer opens automatically.
/// - parameter config: CSV configuration.
public convenience init(
stream: InputStream,
configuration: Configuration = Configuration()) throws {
try self.init(stream: stream, codecType: UTF8.self, configuration: configuration)
}
/// Create an instance with CSV string.
///
/// - parameter string: An CSV string.
/// - parameter config: CSV configuration.
public convenience init(
string: String,
configuration: Configuration = Configuration()) throws {
let iterator = string.unicodeScalars.makeIterator()
try self.init(iterator: iterator, configuration: configuration)
}
// MARK: - Parse CSV
private func errorHandler(error: Error) {
configuration.fileInputErrorHandler?(error, currentRecordIndex, currentFieldIndex)
}
}
internal func readRow() -> [String]? {
// MARK: - Parse CSV
extension CSVReader {
fileprivate func readRecord() -> [String]? {
currentFieldIndex = 0
var c = moveNext()
@ -121,13 +197,13 @@ public class CSV {
return nil
}
var row = [String]()
var record = [String]()
var field: String
var end: Bool
while true {
if config.trimFields {
if configuration.trimFields {
// Trim the leading spaces
while c != nil && config.whitespaces.contains(c!) {
while c != nil && configuration.whitespaces.contains(c!) {
c = moveNext()
}
}
@ -140,12 +216,12 @@ public class CSV {
back = c
(field, end) = readField(quoted: false)
if config.trimFields {
if configuration.trimFields {
// Trim the trailing spaces
field = field.trimmingCharacters(in: config.whitespaces)
field = field.trimmingCharacters(in: configuration.whitespaces)
}
}
row.append(field)
record.append(field)
if end {
break
}
@ -155,9 +231,10 @@ public class CSV {
c = moveNext()
}
currentRowIndex += 1
currentRecordIndex += 1
return row
currentRecord = record
return record
}
private func readField(quoted: Bool) -> (String, Bool) {
@ -168,9 +245,9 @@ public class CSV {
if c == DQUOTE {
var cNext = moveNext()
if config.trimFields {
if configuration.trimFields {
// Trim the trailing spaces
while cNext != nil && config.whitespaces.contains(cNext!) {
while cNext != nil && configuration.whitespaces.contains(cNext!) {
cNext = moveNext()
}
}
@ -182,9 +259,9 @@ public class CSV {
back = cNextNext
}
}
// END ROW
// END RECORD
return (String(fieldBuffer), true)
} else if cNext == config.delimiter {
} else if cNext == configuration.delimiter {
// END FIELD
return (String(fieldBuffer), false)
} else if cNext == DQUOTE {
@ -205,9 +282,9 @@ public class CSV {
back = cNext
}
}
// END ROW
// END RECORD
return (String(fieldBuffer), true)
} else if c == config.delimiter {
} else if c == configuration.delimiter {
// END FIELD
return (String(fieldBuffer), false)
} else {
@ -230,18 +307,61 @@ public class CSV {
return iterator.next()
}
private func errorHandler(error: Error) {
config.fileInputErrorHandler?(error, currentRowIndex, currentFieldIndex)
}
extension CSVReader {
public func enumerateRecords(_ block: (([String], [String]?, inout Bool) throws -> Void)) rethrows {
var stop = false
while let record = readRecord() {
try block(record, headerRecord, &stop)
if stop {
break
}
}
}
}
// MARK: - deprecated
extension CSVReader: IteratorProtocol {
@discardableResult
public func next() -> [String]? {
return readRecord()
}
}
extension CSVReader {
public subscript(key: String) -> String? {
guard let header = headerRecord else {
fatalError("CSVReader.headerRecord must not be nil")
}
guard let index = header.index(of: key) else {
return nil
}
guard let record = currentRecord else {
fatalError("CSVReader.currentRecord must not be nil")
}
if index >= record.count {
return nil
}
return record[index]
}
}
// MARK: - deprecated
extension CSVReader {
/// Unavailable.
@available(*, unavailable, message: "Use init(stream:codecType:config:) instead")
public convenience init<T: UnicodeCodec>(
stream: InputStream,
codecType: T.Type,
hasHeaderRow: Bool = defaultHasHeaderRow,
hasHeaderRow: Bool = defaultHasHeaderRecord,
trimFields: Bool = defaultTrimFields,
delimiter: UnicodeScalar = defaultDelimiter
) throws where T.CodeUnit == UInt8 {
@ -251,12 +371,12 @@ public class CSV {
input: reader.makeUInt8Iterator(),
inputEncodingType: codecType
)
let config = CSVConfiguration(
hasHeaderRow: hasHeaderRow,
let config = Configuration(
hasHeaderRecord: hasHeaderRow,
trimFields: trimFields,
delimiter: delimiter
)
try self.init(iterator: iterator, config: config)
try self.init(iterator: iterator, configuration: config)
}
/// Unavailable.
@ -265,7 +385,7 @@ public class CSV {
stream: InputStream,
codecType: T.Type,
endian: Endian = .big,
hasHeaderRow: Bool = defaultHasHeaderRow,
hasHeaderRow: Bool = defaultHasHeaderRecord,
trimFields: Bool = defaultTrimFields,
delimiter: UnicodeScalar = defaultDelimiter
) throws where T.CodeUnit == UInt16 {
@ -275,12 +395,12 @@ public class CSV {
input: reader.makeUInt16Iterator(),
inputEncodingType: codecType
)
let config = CSVConfiguration(
hasHeaderRow: hasHeaderRow,
let config = Configuration(
hasHeaderRecord: hasHeaderRow,
trimFields: trimFields,
delimiter: delimiter
)
try self.init(iterator: iterator, config: config)
try self.init(iterator: iterator, configuration: config)
}
/// Unavailable.
@ -289,7 +409,7 @@ public class CSV {
stream: InputStream,
codecType: T.Type,
endian: Endian = .big,
hasHeaderRow: Bool = defaultHasHeaderRow,
hasHeaderRow: Bool = defaultHasHeaderRecord,
trimFields: Bool = defaultTrimFields,
delimiter: UnicodeScalar = defaultDelimiter
) throws where T.CodeUnit == UInt32 {
@ -299,51 +419,52 @@ public class CSV {
input: reader.makeUInt32Iterator(),
inputEncodingType: codecType
)
let config = CSVConfiguration(
hasHeaderRow: hasHeaderRow,
let config = Configuration(
hasHeaderRecord: hasHeaderRow,
trimFields: trimFields,
delimiter: delimiter
)
try self.init(iterator: iterator, config: config)
try self.init(iterator: iterator, configuration: config)
}
/// Unavailable.
@available(*, unavailable, message: "Use init(stream:config:) instead")
public convenience init(
stream: InputStream,
hasHeaderRow: Bool = defaultHasHeaderRow,
hasHeaderRow: Bool = defaultHasHeaderRecord,
trimFields: Bool = defaultTrimFields,
delimiter: UnicodeScalar = defaultDelimiter) throws {
let config = CSVConfiguration(
hasHeaderRow: hasHeaderRow,
let config = Configuration(
hasHeaderRecord: hasHeaderRow,
trimFields: trimFields,
delimiter: delimiter
)
try self.init(stream: stream, codecType: UTF8.self, config: config)
try self.init(stream: stream, codecType: UTF8.self, configuration: config)
}
/// Unavailable.
@available(*, unavailable, message: "Use init(string:config:) instead")
public convenience init(
string: String,
hasHeaderRow: Bool = defaultHasHeaderRow,
hasHeaderRow: Bool = defaultHasHeaderRecord,
trimFields: Bool = defaultTrimFields,
delimiter: UnicodeScalar = defaultDelimiter) throws {
let iterator = string.unicodeScalars.makeIterator()
let config = CSVConfiguration(
hasHeaderRow: hasHeaderRow,
let config = Configuration(
hasHeaderRecord: hasHeaderRow,
trimFields: trimFields,
delimiter: delimiter
)
try self.init(iterator: iterator, config: config)
try self.init(iterator: iterator, configuration: config)
}
/// Unavailable
@available(*, unavailable, message: "Use CSV.Row.subscript(String) instead")
public subscript(key: String) -> String? {
return nil
}
// @available(*, unavailable, message: "Use CSV.Row.subscript(String) instead")
// public subscript(key: String) -> String? {
// // FIXME:
// return nil
// }
}

View File

@ -27,16 +27,17 @@ class CSVTests: XCTestCase {
("testSubscriptString1", testSubscriptString1),
("testSubscriptString2", testSubscriptString2),
("testToArray", testToArray),
("testToDictionary1", testToDictionary1),
("testToDictionary2", testToDictionary2)
//("testToDictionary1", testToDictionary1),
//("testToDictionary2", testToDictionary2)
]
func testOneLine() {
let csv = "\"abc\",1,2"
var i = 0
for row in try! CSV(string: csv) {
for record in AnyIterator(try! CSVReader(string: csv)) {
switch i {
case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"])
case 0: XCTAssertEqual(record, ["abc", "1", "2"])
default: break
}
i += 1
@ -47,10 +48,10 @@ class CSVTests: XCTestCase {
func testTwoLines() {
let csv = "\"abc\",1,2\n\"cde\",3,4"
var i = 0
for row in try! CSV(string: csv) {
for record in AnyIterator(try! CSVReader(string: csv)) {
switch i {
case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"])
case 1: XCTAssertEqual(row.toArray(), ["cde", "3", "4"])
case 0: XCTAssertEqual(record, ["abc", "1", "2"])
case 1: XCTAssertEqual(record, ["cde", "3", "4"])
default: break
}
i += 1
@ -61,10 +62,10 @@ class CSVTests: XCTestCase {
func testLastLineIsEmpty() {
let csv = "\"abc\",1,2\n\"cde\",3,4\n"
var i = 0
for row in try! CSV(string: csv) {
for record in AnyIterator(try! CSVReader(string: csv)) {
switch i {
case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"])
case 1: XCTAssertEqual(row.toArray(), ["cde", "3", "4"])
case 0: XCTAssertEqual(record, ["abc", "1", "2"])
case 1: XCTAssertEqual(record, ["cde", "3", "4"])
default: break
}
i += 1
@ -75,11 +76,11 @@ class CSVTests: XCTestCase {
func testLastLineIsWhiteSpace() {
let csv = "\"abc\",1,2\n\"cde\",3,4\n "
var i = 0
for row in try! CSV(string: csv) {
for record in AnyIterator(try! CSVReader(string: csv)) {
switch i {
case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"])
case 1: XCTAssertEqual(row.toArray(), ["cde", "3", "4"])
case 2: XCTAssertEqual(row.toArray(), [" "])
case 0: XCTAssertEqual(record, ["abc", "1", "2"])
case 1: XCTAssertEqual(record, ["cde", "3", "4"])
case 2: XCTAssertEqual(record, [" "])
default: break
}
i += 1
@ -90,11 +91,11 @@ class CSVTests: XCTestCase {
func testMiddleLineIsEmpty() {
let csv = "\"abc\",1,2\n\n\"cde\",3,4"
var i = 0
for row in try! CSV(string: csv) {
for record in AnyIterator(try! CSVReader(string: csv)) {
switch i {
case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"])
case 1: XCTAssertEqual(row.toArray(), [""])
case 2: XCTAssertEqual(row.toArray(), ["cde", "3", "4"])
case 0: XCTAssertEqual(record, ["abc", "1", "2"])
case 1: XCTAssertEqual(record, [""])
case 2: XCTAssertEqual(record, ["cde", "3", "4"])
default: break
}
i += 1
@ -104,46 +105,46 @@ class CSVTests: XCTestCase {
func testCommaInQuotationMarks() {
let csvString = "abab,\"cd,cd\",efef"
let csv = try! CSV(string: csvString)
let row = csv.next()!
XCTAssertEqual(row.toArray(), ["abab", "cd,cd", "efef"])
let csv = try! CSVReader(string: csvString)
let record = csv.next()!
XCTAssertEqual(record, ["abab", "cd,cd", "efef"])
}
func testEscapedQuotationMark1() {
let csvString = "abab,\"\"\"cdcd\",efef\r\nzxcv,asdf,qwer"
let csv = try! CSV(string: csvString)
var row = csv.next()!
XCTAssertEqual(row.toArray(), ["abab", "\"cdcd", "efef"])
row = csv.next()!
XCTAssertEqual(row.toArray(), ["zxcv", "asdf", "qwer"])
let csv = try! CSVReader(string: csvString)
var record = csv.next()!
XCTAssertEqual(record, ["abab", "\"cdcd", "efef"])
record = csv.next()!
XCTAssertEqual(record, ["zxcv", "asdf", "qwer"])
}
func testEscapedQuotationMark2() {
let csvString = "abab,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\""
let csv = try! CSV(string: csvString)
var row = csv.next()!
XCTAssertEqual(row.toArray(), ["abab", "cdcd", "efef"])
row = csv.next()!
XCTAssertEqual(row.toArray(), ["zxcv", "asdf", "qw\"er"])
let csv = try! CSVReader(string: csvString)
var record = csv.next()!
XCTAssertEqual(record, ["abab", "cdcd", "efef"])
record = csv.next()!
XCTAssertEqual(record, ["zxcv", "asdf", "qw\"er"])
}
func testEmptyField() {
let csvString = "abab,,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\","
let csv = try! CSV(string: csvString)
var row = csv.next()!
XCTAssertEqual(row.toArray(), ["abab", "", "cdcd", "efef"])
row = csv.next()!
XCTAssertEqual(row.toArray(), ["zxcv", "asdf", "qw\"er", ""])
let csv = try! CSVReader(string: csvString)
var record = csv.next()!
XCTAssertEqual(record, ["abab", "", "cdcd", "efef"])
record = csv.next()!
XCTAssertEqual(record, ["zxcv", "asdf", "qw\"er", ""])
}
func testDoubleQuoteBeforeLineBreak() {
let csv = "\"abc\",1,\"2\"\n\n\"cde\",3,\"4\""
var i = 0
for row in try! CSV(string: csv) {
for record in AnyIterator(try! CSVReader(string: csv)) {
switch i {
case 0: XCTAssertEqual(row.toArray(), ["abc", "1", "2"])
case 1: XCTAssertEqual(row.toArray(), [""])
case 2: XCTAssertEqual(row.toArray(), ["cde", "3", "4"])
case 0: XCTAssertEqual(record, ["abc", "1", "2"])
case 1: XCTAssertEqual(record, [""])
case 2: XCTAssertEqual(record, ["cde", "3", "4"])
default: break
}
i += 1
@ -153,82 +154,80 @@ class CSVTests: XCTestCase {
func testCSVState1() {
let it = "あ,い1,\"\",えお\n,,x,".unicodeScalars.makeIterator()
let csv = try! CSV(iterator: it, config: CSVConfiguration())
let csv = try! CSVReader(iterator: it, configuration: CSVReader.Configuration())
var rows = [[String]]()
var records = [[String]]()
while let row = csv.next() {
rows.append(row.toArray())
while let record = csv.next() {
records.append(record)
}
XCTAssertEqual(rows.count, 2)
XCTAssertEqual(rows[0], ["", "い1", "", "えお"])
XCTAssertEqual(rows[1], ["", "", "x", ""])
XCTAssertEqual(records.count, 2)
XCTAssertEqual(records[0], ["", "い1", "", "えお"])
XCTAssertEqual(records[1], ["", "", "x", ""])
}
func testSubscriptInt() {
let csvString = "a,bb,ccc"
let csv = try! CSV(string: csvString)
for row in csv {
XCTAssertEqual(row[0], "a")
XCTAssertEqual(row[1], "bb")
XCTAssertEqual(row[2], "ccc")
let csv = try! CSVReader(string: csvString)
for record in AnyIterator(csv) {
XCTAssertEqual(record[0], "a")
XCTAssertEqual(record[1], "bb")
XCTAssertEqual(record[2], "ccc")
}
}
func testSubscriptString1() {
let csvString = "key1,key2\nvalue1,value2"
let config = CSVConfiguration(hasHeaderRow: true)
let csv = try! CSV(string: csvString, config: config)
for row in csv {
XCTAssertEqual(row["key1"], "value1")
XCTAssertEqual(row["key2"], "value2")
XCTAssertNil(row["key9"])
}
let config = CSVReader.Configuration(hasHeaderRecord: true)
let csv = try! CSVReader(string: csvString, configuration: config)
csv.next()
XCTAssertEqual(csv["key1"], "value1")
XCTAssertEqual(csv["key2"], "value2")
XCTAssertNil(csv["key9"])
}
func testSubscriptString2() {
let csvString = "key1,key2\nvalue1"
let config = CSVConfiguration(hasHeaderRow: true)
let csv = try! CSV(string: csvString, config: config)
for row in csv {
XCTAssertEqual(row["key1"], "value1")
XCTAssertNil(row["key2"])
XCTAssertNil(row["key9"])
}
let config = CSVReader.Configuration(hasHeaderRecord: true)
let csv = try! CSVReader(string: csvString, configuration: config)
csv.next()
XCTAssertEqual(csv["key1"], "value1")
XCTAssertNil(csv["key2"])
XCTAssertNil(csv["key9"])
}
func testToArray() {
let csvString = "1,2,3,4,5\n6,7,8,9,0"
let csv = try! CSV(string: csvString)
let rows = csv.map { $0.toArray() }
XCTAssertEqual(rows[0], ["1", "2", "3", "4", "5"])
XCTAssertEqual(rows[1], ["6", "7", "8", "9", "0"])
let csv = try! CSVReader(string: csvString)
let records = AnyIterator(csv).map { $0 }
XCTAssertEqual(records[0], ["1", "2", "3", "4", "5"])
XCTAssertEqual(records[1], ["6", "7", "8", "9", "0"])
}
func testToDictionary1() {
let csvString = "id,name\n1,name1\n2,name2"
let config = CSVConfiguration(hasHeaderRow: true)
let csv = try! CSV(string: csvString, config: config)
let rows = csv.map { $0.toDictionary() }
XCTAssertEqual(rows[0]["id"], "1")
XCTAssertEqual(rows[0]["name"], "name1")
XCTAssertNil(rows[0]["xxx"])
XCTAssertEqual(rows[1]["id"], "2")
XCTAssertEqual(rows[1]["name"], "name2")
XCTAssertNil(rows[1]["yyy"])
}
// func testToDictionary1() {
// let csvString = "id,name\n1,name1\n2,name2"
// let config = CSVReader.Configuration(hasHeaderRow: true)
// let csv = try! CSVReader(string: csvString, configuration: config)
// let rows = AnyIterator(csv).map { $0.toDictionary() }
// XCTAssertEqual(rows[0]["id"], "1")
// XCTAssertEqual(rows[0]["name"], "name1")
// XCTAssertNil(rows[0]["xxx"])
// XCTAssertEqual(rows[1]["id"], "2")
// XCTAssertEqual(rows[1]["name"], "name2")
// XCTAssertNil(rows[1]["yyy"])
// }
func testToDictionary2() {
let csvString = "id,name,id\n1,name1,11\n2,name2,22"
let config = CSVConfiguration(hasHeaderRow: true)
let csv = try! CSV(string: csvString, config: config)
let rows = csv.map { $0.toDictionary() }
XCTAssertEqual(rows[0]["id"], "1")
XCTAssertEqual(rows[0]["name"], "name1")
XCTAssertNil(rows[0]["xxx"])
XCTAssertEqual(rows[1]["id"], "2")
XCTAssertEqual(rows[1]["name"], "name2")
XCTAssertNil(rows[1]["yyy"])
}
// func testToDictionary2() {
// let csvString = "id,name,id\n1,name1,11\n2,name2,22"
// let config = CSVReader.Configuration(hasHeaderRow: true)
// let csv = try! CSVReader(string: csvString, configuration: config)
// let rows = AnyIterator(csv).map { $0.toDictionary() }
// XCTAssertEqual(rows[0]["id"], "1")
// XCTAssertEqual(rows[0]["name"], "name1")
// XCTAssertNil(rows[0]["xxx"])
// XCTAssertEqual(rows[1]["id"], "2")
// XCTAssertEqual(rows[1]["name"], "name2")
// XCTAssertNil(rows[1]["yyy"])
// }
}

View File

@ -124,10 +124,10 @@ class LineBreakTests: XCTestCase {
}
private func parse(csv: String) -> [[String]] {
let reader = try! CSV(string: csv)
let reader = try! CSVReader(string: csv)
var records = [[String]]()
for row in reader {
records.append(row.toArray())
reader.enumerateRecords { (record, _, _) in
records.append(record)
}
return records
}

View File

@ -21,9 +21,9 @@ class ReadmeTests: XCTestCase {
]
func testFromCSVString() {
let csv = try! CSV(string: "1,foo\n2,bar")
for row in csv {
print("\(row)")
let csv = try! CSVReader(string: "1,foo\n2,bar")
csv.enumerateRecords { (record, _, _) in
print("\(record)")
// => ["1", "foo"]
// => ["2", "bar"]
}
@ -39,14 +39,14 @@ class ReadmeTests: XCTestCase {
func testGettingTheHeaderRow() {
let csvString = "id,name\n1,foo\n2,bar"
let config = CSVConfiguration(hasHeaderRow: true) // It must be true.
let csv = try! CSV(string: csvString, config: config)
let config = CSVReader.Configuration(hasHeaderRecord: true) // It must be true.
let csv = try! CSVReader(string: csvString, configuration: config)
let headerRow = csv.headerRow!
let headerRow = csv.headerRecord!
print("\(headerRow)") // => ["id", "name"]
for row in csv {
print("\(row)")
csv.enumerateRecords { (record, _, _) in
print("\(record)")
// => ["1", "foo"]
// => ["2", "bar"]
}
@ -54,23 +54,23 @@ class ReadmeTests: XCTestCase {
func testGetTheFieldValueUsingIndex() {
let csvString = "1,foo"
let csv = try! CSV(string: csvString)
let csv = try! CSVReader(string: csvString)
for row in csv {
print("\(row[0])") // => "1"
print("\(row[1])") // => "foo"
csv.enumerateRecords { (record, _, _) in
print("\(record[0])") // => "1"
print("\(record[1])") // => "foo"
}
}
func testGetTheFieldValueUsingKey() {
let csvString = "id,name\n1,foo"
let config = CSVConfiguration(hasHeaderRow: true) // It must be true.
let csv = try! CSV(string: csvString, config: config)
for row in csv {
print("\(row["id"]!)") // => "1"
print("\(row["name"]!)") // => "foo"
}
// let csvString = "id,name\n1,foo"
// let config = CSVReader.Configuration(hasHeaderRow: true) // It must be true.
// let csv = try! CSVReader(string: csvString, configuration: config)
//
// csv.enumerateRecords { (record, _, _) in
// print("\(record["id"]!)") // => "1"
// print("\(record["name"]!)") // => "foo"
// }
}
func testProvideTheCharacterEncoding() {

View File

@ -29,173 +29,173 @@ class TrimFieldsTests: XCTestCase {
func testTrimFields1() {
let csvString = "abc,def,ghi"
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
for row in csv {
XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"])
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
for record in AnyIterator(csv) {
XCTAssertEqual(record, ["abc", "def", "ghi"])
}
}
func testTrimFields2() {
let csvString = " abc, def, ghi"
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
for row in csv {
XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"])
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
for record in AnyIterator(csv) {
XCTAssertEqual(record, ["abc", "def", "ghi"])
}
}
func testTrimFields3() {
let csvString = "abc ,def ,ghi "
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
for row in csv {
XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"])
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
for record in AnyIterator(csv) {
XCTAssertEqual(record, ["abc", "def", "ghi"])
}
}
func testTrimFields4() {
let csvString = " abc , def , ghi "
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
for row in csv {
XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"])
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
for record in AnyIterator(csv) {
XCTAssertEqual(record, ["abc", "def", "ghi"])
}
}
func testTrimFields5() {
let csvString = "\"abc\",\"def\",\"ghi\""
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
for row in csv {
XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"])
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
for record in AnyIterator(csv) {
XCTAssertEqual(record, ["abc", "def", "ghi"])
}
}
func testTrimFields6() {
let csvString = " \"abc\", \"def\", \"ghi\""
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
for row in csv {
XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"])
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
for record in AnyIterator(csv) {
XCTAssertEqual(record, ["abc", "def", "ghi"])
}
}
func testTrimFields7() {
let csvString = "\"abc\" ,\"def\" ,\"ghi\" "
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
for row in csv {
XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"])
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
for record in AnyIterator(csv) {
XCTAssertEqual(record, ["abc", "def", "ghi"])
}
}
func testTrimFields8() {
let csvString = " \"abc\" , \"def\" , \"ghi\" "
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
for row in csv {
XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"])
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
for record in AnyIterator(csv) {
XCTAssertEqual(record, ["abc", "def", "ghi"])
}
}
func testTrimFields9() {
let csvString = "\" abc \",\" def \",\" ghi \""
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
for row in csv {
XCTAssertEqual(row.toArray(), [" abc ", " def ", " ghi "])
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
for record in AnyIterator(csv) {
XCTAssertEqual(record, [" abc ", " def ", " ghi "])
}
}
func testTrimFields10() {
let csvString = "\tabc,\t\tdef\t,ghi\t"
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
for row in csv {
XCTAssertEqual(row.toArray(), ["abc", "def", "ghi"])
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
for record in AnyIterator(csv) {
XCTAssertEqual(record, ["abc", "def", "ghi"])
}
}
func testTrimFields11() {
let csvString = " abc \n def "
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
let row1 = csv.next()!
XCTAssertEqual(row1.toArray(), ["abc"])
let row2 = csv.next()!
XCTAssertEqual(row2.toArray(), ["def"])
let record1 = csv.next()!
XCTAssertEqual(record1, ["abc"])
let record2 = csv.next()!
XCTAssertEqual(record2, ["def"])
}
func testTrimFields12() {
let csvString = " \"abc \" \n \" def\" "
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
let row1 = csv.next()!
XCTAssertEqual(row1.toArray(), ["abc "])
let row2 = csv.next()!
XCTAssertEqual(row2.toArray(), [" def"])
let record1 = csv.next()!
XCTAssertEqual(record1, ["abc "])
let record2 = csv.next()!
XCTAssertEqual(record2, [" def"])
}
func testTrimFields13() {
let csvString = " abc \t\tdef\t ghi "
let config = CSVConfiguration(trimFields: true, delimiter: UnicodeScalar("\t")!)
let csv = try! CSV(string: csvString, config: config)
for row in csv {
XCTAssertEqual(row.toArray(), ["abc", "", "def", "ghi"])
let config = CSVReader.Configuration(trimFields: true, delimiter: UnicodeScalar("\t")!)
let csv = try! CSVReader(string: csvString, configuration: config)
for record in AnyIterator(csv) {
XCTAssertEqual(record, ["abc", "", "def", "ghi"])
}
}
func testTrimFields14() {
let csvString = ""
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
let rows = csv.map { $0.toArray() }
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
let records = AnyIterator(csv).map { $0 }
XCTAssertEqual(rows.count, 0)
XCTAssertEqual(records.count, 0)
}
func testTrimFields15() {
let csvString = " "
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
let rows = csv.map { $0.toArray() }
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
let records = AnyIterator(csv).map { $0 }
XCTAssertEqual(rows.count, 1)
XCTAssertEqual(rows[0], [""])
XCTAssertEqual(records.count, 1)
XCTAssertEqual(records[0], [""])
}
func testTrimFields16() {
let csvString = " , "
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
let rows = csv.map { $0.toArray() }
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
let records = AnyIterator(csv).map { $0 }
XCTAssertEqual(rows.count, 1)
XCTAssertEqual(rows[0], ["", ""])
XCTAssertEqual(records.count, 1)
XCTAssertEqual(records[0], ["", ""])
}
func testTrimFields17() {
let csvString = " , \n"
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
let rows = csv.map { $0.toArray() }
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
let records = AnyIterator(csv).map { $0 }
XCTAssertEqual(rows.count, 1)
XCTAssertEqual(rows[0], ["", ""])
XCTAssertEqual(records.count, 1)
XCTAssertEqual(records[0], ["", ""])
}
func testTrimFields18() {
let csvString = " , \n "
let config = CSVConfiguration(trimFields: true)
let csv = try! CSV(string: csvString, config: config)
let rows = csv.map { $0.toArray() }
let config = CSVReader.Configuration(trimFields: true)
let csv = try! CSVReader(string: csvString, configuration: config)
let records = AnyIterator(csv).map { $0 }
XCTAssertEqual(rows.count, 2)
XCTAssertEqual(rows[0], ["", ""])
XCTAssertEqual(rows[1], [""])
XCTAssertEqual(records.count, 2)
XCTAssertEqual(records[0], ["", ""])
XCTAssertEqual(records[1], [""])
}
}

View File

@ -29,7 +29,7 @@ class UnicodeTests: XCTestCase {
mutableData.append(utf8BOM, count: utf8BOM.count)
mutableData.append(csvString.data(using: encoding)!)
let stream = InputStream(data: mutableData)
let csv = try! CSV(stream: stream, codecType: UTF8.self)
let csv = try! CSVReader(stream: stream, codecType: UTF8.self)
let records = getRecords(csv: csv)
XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"])
XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""])
@ -41,7 +41,7 @@ class UnicodeTests: XCTestCase {
var mutableData = Data()
mutableData.append(csvString.data(using: encoding)!)
let stream = InputStream(data: mutableData as Data)
let csv = try! CSV(stream: stream, codecType: UTF16.self, endian: .unknown)
let csv = try! CSVReader(stream: stream, codecType: UTF16.self, endian: .unknown)
let records = getRecords(csv: csv)
XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"])
XCTAssertEqual(records[1], ["zxcv", "😆asdf", "qw\"er", ""])
@ -54,7 +54,7 @@ class UnicodeTests: XCTestCase {
mutableData.append(utf16BigEndianBOM, count: utf16BigEndianBOM.count)
mutableData.append(csvString.data(using: encoding)!)
let stream = InputStream(data: mutableData as Data)
let csv = try! CSV(stream: stream, codecType: UTF16.self, endian: .big)
let csv = try! CSVReader(stream: stream, codecType: UTF16.self, endian: .big)
let records = getRecords(csv: csv)
XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"])
XCTAssertEqual(records[1], ["😆zxcv", "asdf", "qw\"er", ""])
@ -67,7 +67,7 @@ class UnicodeTests: XCTestCase {
mutableData.append(utf16LittleEndianBOM, count: utf16LittleEndianBOM.count)
mutableData.append(csvString.data(using: encoding)!)
let stream = InputStream(data: mutableData as Data)
let csv = try! CSV(stream: stream, codecType: UTF16.self, endian: .little)
let csv = try! CSVReader(stream: stream, codecType: UTF16.self, endian: .little)
let records = getRecords(csv: csv)
XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"])
XCTAssertEqual(records[1], ["zxcv😆", "asdf", "qw\"er", ""])
@ -79,7 +79,7 @@ class UnicodeTests: XCTestCase {
var mutableData = Data()
mutableData.append(csvString.data(using: encoding)!)
let stream = InputStream(data: mutableData as Data)
let csv = try! CSV(stream: stream, codecType: UTF32.self, endian: .unknown)
let csv = try! CSVReader(stream: stream, codecType: UTF32.self, endian: .unknown)
let records = getRecords(csv: csv)
XCTAssertEqual(records[0], ["😆abab", "", "cdcd", "efef"])
XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""])
@ -92,7 +92,7 @@ class UnicodeTests: XCTestCase {
mutableData.append(utf32BigEndianBOM, count: utf32BigEndianBOM.count)
mutableData.append(csvString.data(using: encoding)!)
let stream = InputStream(data: mutableData as Data)
let csv = try! CSV(stream: stream, codecType: UTF32.self, endian: .big)
let csv = try! CSVReader(stream: stream, codecType: UTF32.self, endian: .big)
let records = getRecords(csv: csv)
XCTAssertEqual(records[0], ["abab", "", "cd😆cd", "efef"])
XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""])
@ -105,16 +105,16 @@ class UnicodeTests: XCTestCase {
mutableData.append(utf32LittleEndianBOM, count: utf32LittleEndianBOM.count)
mutableData.append(csvString.data(using: encoding)!)
let stream = InputStream(data: mutableData as Data)
let csv = try! CSV(stream: stream, codecType: UTF32.self, endian: .little)
let csv = try! CSVReader(stream: stream, codecType: UTF32.self, endian: .little)
let records = getRecords(csv: csv)
XCTAssertEqual(records[0], ["abab", "", "cdcd", "ef😆ef"])
XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""])
}
private func getRecords(csv: CSV) -> [[String]] {
private func getRecords(csv: CSVReader) -> [[String]] {
var records = [[String]]()
for row in csv {
records.append(row.toArray())
csv.enumerateRecords { (record, _, _) in
records.append(record)
}
return records
}