Add CSVWriter class

This commit is contained in:
Yasuhiro Hatta 2017-05-28 12:20:29 +09:00
parent 7b9caaf9a8
commit 3bf2eab076
4 changed files with 222 additions and 3 deletions

View File

@ -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;
};

160
CSVWriter.swift Normal file
View File

@ -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))
}
}
}

View File

@ -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 {

View File

@ -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)
}
}