Add `struct CSV.Row`
This commit is contained in:
parent
479913f0f0
commit
44b4857396
|
@ -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 = "<group>"; };
|
||||
0E3CE36B1DB5281E00FA45CF /* UnicodeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeTests.swift; sourceTree = "<group>"; };
|
||||
0E3CE36F1DB529D700FA45CF /* TrimFieldsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrimFieldsTests.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
|
@ -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 */,
|
||||
);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<UnicodeScalar>
|
||||
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<T: IteratorProtocol>(
|
||||
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<T: UnicodeCodec>(
|
||||
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<T: UnicodeCodec>(
|
||||
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<T: UnicodeCodec>(
|
||||
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<T: UnicodeCodec>(
|
||||
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<T: UnicodeCodec>(
|
||||
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<T: UnicodeCodec>(
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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"])
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue