Add CSVWriter class
This commit is contained in:
parent
7b9caaf9a8
commit
3bf2eab076
|
@ -15,6 +15,13 @@
|
|||
0E47EEC21DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; };
|
||||
0E47EEC31DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; };
|
||||
0E47EEC41DBCDB1800EBF783 /* CSV+iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */; };
|
||||
0E5402161ED9DC960019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5402151ED9DC960019C3ED /* CSVWriter.swift */; };
|
||||
0E5402171ED9DC960019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5402151ED9DC960019C3ED /* CSVWriter.swift */; };
|
||||
0E5402181ED9DC960019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5402151ED9DC960019C3ED /* CSVWriter.swift */; };
|
||||
0E5402191ED9DC960019C3ED /* CSVWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5402151ED9DC960019C3ED /* CSVWriter.swift */; };
|
||||
0E54021B1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; };
|
||||
0E54021C1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; };
|
||||
0E54021D1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */; };
|
||||
0E7E8C8C1D0BC7BB0057A1C1 /* CSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E7E8C811D0BC7BB0057A1C1 /* CSV.framework */; };
|
||||
0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9D1D0BC7F10057A1C1 /* CSV.swift */; };
|
||||
0E7E8CA21D0BC7F10057A1C1 /* CSVError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E8C9E1D0BC7F10057A1C1 /* CSVError.swift */; };
|
||||
|
@ -90,6 +97,8 @@
|
|||
/* Begin PBXFileReference section */
|
||||
0E0F160D1D197DB800C92580 /* Endian.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Endian.swift; sourceTree = "<group>"; };
|
||||
0E47EEC01DBCDB1800EBF783 /* CSV+iterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSV+iterator.swift"; sourceTree = "<group>"; };
|
||||
0E5402151ED9DC960019C3ED /* CSVWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVWriter.swift; sourceTree = SOURCE_ROOT; };
|
||||
0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSVWriterTests.swift; sourceTree = "<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>"; };
|
||||
|
@ -206,6 +215,7 @@
|
|||
0E0F160D1D197DB800C92580 /* Endian.swift */,
|
||||
0E7E8CAC1D0BC8610057A1C1 /* Info.plist */,
|
||||
0EA2AB801D183BA9003EC967 /* UnicodeIterator.swift */,
|
||||
0E5402151ED9DC960019C3ED /* CSVWriter.swift */,
|
||||
);
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
|
@ -227,6 +237,7 @@
|
|||
0EDF8ECF1DDB73370068056A /* ReadmeTests.swift */,
|
||||
0EDF8ED01DDB73370068056A /* TrimFieldsTests.swift */,
|
||||
0EDF8ED11DDB73370068056A /* UnicodeTests.swift */,
|
||||
0E54021A1ED9DDF40019C3ED /* CSVWriterTests.swift */,
|
||||
);
|
||||
path = CSVTests;
|
||||
sourceTree = "<group>";
|
||||
|
@ -536,6 +547,7 @@
|
|||
0E9317D51D0DB2F200AC20A0 /* CSV+init.swift in Sources */,
|
||||
0EA2AB821D183BA9003EC967 /* UnicodeIterator.swift in Sources */,
|
||||
0E47EEC21DBCDB1800EBF783 /* CSV+iterator.swift in Sources */,
|
||||
0E5402171ED9DC960019C3ED /* CSVWriter.swift in Sources */,
|
||||
0E7E8CA11D0BC7F10057A1C1 /* CSV.swift in Sources */,
|
||||
0E0F160F1D197DB800C92580 /* Endian.swift in Sources */,
|
||||
0EDF8EB01DDB6DAC0068056A /* CSVConfiguration.swift in Sources */,
|
||||
|
@ -553,6 +565,7 @@
|
|||
0EDF8EDF1DDB73520068056A /* TrimFieldsTests.swift in Sources */,
|
||||
0EDF8EE01DDB73520068056A /* UnicodeTests.swift in Sources */,
|
||||
0EDF8EDD1DDB73520068056A /* LineBreakTests.swift in Sources */,
|
||||
0E54021C1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -563,6 +576,7 @@
|
|||
0E9317D71D0DB2F200AC20A0 /* CSV+init.swift in Sources */,
|
||||
0EA2AB841D183BA9003EC967 /* UnicodeIterator.swift in Sources */,
|
||||
0E47EEC41DBCDB1800EBF783 /* CSV+iterator.swift in Sources */,
|
||||
0E5402191ED9DC960019C3ED /* CSVWriter.swift in Sources */,
|
||||
0E7E8CBE1D0BC9D70057A1C1 /* CSV.swift in Sources */,
|
||||
0E0F16111D197DB800C92580 /* Endian.swift in Sources */,
|
||||
0EDF8EB21DDB6DAD0068056A /* CSVConfiguration.swift in Sources */,
|
||||
|
@ -578,6 +592,7 @@
|
|||
0E9317D41D0DB2F200AC20A0 /* CSV+init.swift in Sources */,
|
||||
0EA2AB811D183BA9003EC967 /* UnicodeIterator.swift in Sources */,
|
||||
0E47EEC11DBCDB1800EBF783 /* CSV+iterator.swift in Sources */,
|
||||
0E5402161ED9DC960019C3ED /* CSVWriter.swift in Sources */,
|
||||
0E7E8CE01D0BCA8E0057A1C1 /* CSV.swift in Sources */,
|
||||
0E0F160E1D197DB800C92580 /* Endian.swift in Sources */,
|
||||
0EDF8EAF1DDB6D620068056A /* CSVConfiguration.swift in Sources */,
|
||||
|
@ -595,6 +610,7 @@
|
|||
0EDF8EDA1DDB73520068056A /* TrimFieldsTests.swift in Sources */,
|
||||
0EDF8EDB1DDB73520068056A /* UnicodeTests.swift in Sources */,
|
||||
0EDF8ED81DDB73520068056A /* LineBreakTests.swift in Sources */,
|
||||
0E54021B1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -605,6 +621,7 @@
|
|||
0E9317D61D0DB2F200AC20A0 /* CSV+init.swift in Sources */,
|
||||
0EA2AB831D183BA9003EC967 /* UnicodeIterator.swift in Sources */,
|
||||
0E47EEC31DBCDB1800EBF783 /* CSV+iterator.swift in Sources */,
|
||||
0E5402181ED9DC960019C3ED /* CSVWriter.swift in Sources */,
|
||||
0E7E8D001D0BCDCF0057A1C1 /* CSV.swift in Sources */,
|
||||
0E0F16101D197DB800C92580 /* Endian.swift in Sources */,
|
||||
0EDF8EB11DDB6DAC0068056A /* CSVConfiguration.swift in Sources */,
|
||||
|
@ -622,6 +639,7 @@
|
|||
0EDF8EE41DDB73530068056A /* TrimFieldsTests.swift in Sources */,
|
||||
0EDF8EE51DDB73530068056A /* UnicodeTests.swift in Sources */,
|
||||
0EDF8EE21DDB73530068056A /* LineBreakTests.swift in Sources */,
|
||||
0E54021D1ED9DDF40019C3ED /* CSVWriterTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
//
|
||||
// CSVWriter.swift
|
||||
// CSV
|
||||
//
|
||||
// Created by Yasuhiro Hatta on 2017/05/28.
|
||||
// Copyright © 2017 yaslab. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class CSVWriter {
|
||||
|
||||
public struct Configuration {
|
||||
|
||||
public var delimiter: String
|
||||
public var newline: String
|
||||
|
||||
public init(delimiter: String = String(defaultDelimiter), newline: String = String(LF)) {
|
||||
self.delimiter = delimiter
|
||||
self.newline = newline
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public let stream: OutputStream
|
||||
public let configuration: Configuration
|
||||
fileprivate let writeScalar: ((Unicode.Scalar) -> Void)
|
||||
|
||||
fileprivate var isFirstRecord: Bool = true
|
||||
fileprivate var isFirstField: Bool = true
|
||||
|
||||
fileprivate init(
|
||||
stream: OutputStream,
|
||||
configuration: Configuration,
|
||||
writeScalar: @escaping ((Unicode.Scalar) -> Void)) {
|
||||
|
||||
self.stream = stream
|
||||
self.configuration = configuration
|
||||
self.writeScalar = writeScalar
|
||||
|
||||
if stream.streamStatus == .notOpen {
|
||||
stream.open()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension CSVWriter {
|
||||
|
||||
public convenience init(
|
||||
stream: OutputStream,
|
||||
configuration: Configuration = Configuration()) {
|
||||
|
||||
self.init(stream: stream, codecType: UTF8.self, configuration: configuration)
|
||||
}
|
||||
|
||||
public convenience init<T: UnicodeCodec>(
|
||||
stream: OutputStream,
|
||||
codecType: T.Type,
|
||||
configuration: Configuration = Configuration()
|
||||
) where T.CodeUnit == UInt8 {
|
||||
|
||||
self.init(stream: stream, configuration: configuration) { (scalar: Unicode.Scalar) in
|
||||
codecType.encode(scalar) { (code: UInt8) in
|
||||
var code = code
|
||||
let count = stream.write(&code, maxLength: 1)
|
||||
if count != 1 {
|
||||
// FIXME: Error
|
||||
print("ERROR: count != 1")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public convenience init<T: UnicodeCodec>(
|
||||
stream: OutputStream,
|
||||
codecType: T.Type,
|
||||
endian: Endian = .big,
|
||||
configuration: Configuration = Configuration()
|
||||
) where T.CodeUnit == UInt16 {
|
||||
|
||||
self.init(stream: stream, configuration: configuration) { (scalar: Unicode.Scalar) in
|
||||
codecType.encode(scalar) { (code: UInt16) in
|
||||
var code = (endian == .big) ? code.bigEndian : code.littleEndian
|
||||
let count = withUnsafeBytes(of: &code) { (buffer) -> Int in
|
||||
return stream.write(buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), maxLength: buffer.count)
|
||||
}
|
||||
if count != 2 {
|
||||
// FIXME: Error
|
||||
print("ERROR: count != 2")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public convenience init<T: UnicodeCodec>(
|
||||
stream: OutputStream,
|
||||
codecType: T.Type,
|
||||
endian: Endian = .big,
|
||||
configuration: Configuration = Configuration()
|
||||
) where T.CodeUnit == UInt32 {
|
||||
|
||||
self.init(stream: stream, configuration: configuration) { (scalar: Unicode.Scalar) in
|
||||
codecType.encode(scalar) { (code: UInt32) in
|
||||
var code = (endian == .big) ? code.bigEndian : code.littleEndian
|
||||
let count = withUnsafeBytes(of: &code) { (buffer) -> Int in
|
||||
return stream.write(buffer.baseAddress!.assumingMemoryBound(to: UInt8.self), maxLength: buffer.count)
|
||||
}
|
||||
if count != 4 {
|
||||
// FIXME: Error
|
||||
print("ERROR: count != 4")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension CSVWriter {
|
||||
|
||||
public func beginNewRecord() {
|
||||
isFirstField = true
|
||||
}
|
||||
|
||||
public func write(field value: String, quoted: Bool = false) {
|
||||
if isFirstRecord {
|
||||
isFirstRecord = false
|
||||
} else {
|
||||
if isFirstField {
|
||||
configuration.newline.unicodeScalars.forEach(writeScalar)
|
||||
}
|
||||
}
|
||||
|
||||
if isFirstField {
|
||||
isFirstField = false
|
||||
} else {
|
||||
configuration.delimiter.unicodeScalars.forEach(writeScalar)
|
||||
}
|
||||
|
||||
var value = value
|
||||
|
||||
if quoted {
|
||||
value = value.replacingOccurrences(of: DQUOTE_STR, with: DQUOTE2_STR)
|
||||
writeScalar(DQUOTE)
|
||||
}
|
||||
|
||||
value.unicodeScalars.forEach(writeScalar)
|
||||
|
||||
if quoted {
|
||||
writeScalar(DQUOTE)
|
||||
}
|
||||
}
|
||||
|
||||
public func write(row values: [String], quotedAtIndex: ((Int) -> Bool) = { _ in false }) {
|
||||
for (i, value) in values.enumerated() {
|
||||
write(field: value, quoted: quotedAtIndex(i))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -8,9 +8,12 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
private let LF: UnicodeScalar = "\n"
|
||||
private let CR: UnicodeScalar = "\r"
|
||||
private let DQUOTE: UnicodeScalar = "\""
|
||||
internal let LF: UnicodeScalar = "\n"
|
||||
internal let CR: UnicodeScalar = "\r"
|
||||
internal let DQUOTE: UnicodeScalar = "\""
|
||||
|
||||
internal let DQUOTE_STR: String = "\""
|
||||
internal let DQUOTE2_STR: String = "\"\""
|
||||
|
||||
/// No overview available.
|
||||
public class CSV {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// CSVWriterTests.swift
|
||||
// CSV
|
||||
//
|
||||
// Created by Yasuhiro Hatta on 2017/05/28.
|
||||
// Copyright © 2017年 yaslab. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
import CSV
|
||||
|
||||
class CSVWriterTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testSample", testSample)
|
||||
]
|
||||
|
||||
func testSample() {
|
||||
let stream = OutputStream(toMemory: ())
|
||||
let csv = CSVWriter(stream: stream, codecType: UTF8.self)
|
||||
|
||||
for i in 0 ..< 10 {
|
||||
csv.beginNewRecord()
|
||||
csv.write(field: "\(i)")
|
||||
csv.write(field: "\(i)-text", quoted: true)
|
||||
}
|
||||
|
||||
let _data = stream.property(forKey: .dataWrittenToMemoryStreamKey)
|
||||
guard let data = _data as? Data else {
|
||||
return
|
||||
}
|
||||
let str = String(data: data, encoding: .utf8)!
|
||||
print(str)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue