Implement error handling
This commit is contained in:
parent
0dfc025b79
commit
85e1ece74c
|
@ -45,21 +45,29 @@ internal class BinaryReader {
|
|||
private let endian: Endian
|
||||
private let closeOnDeinit: Bool
|
||||
|
||||
private var buffer = [UInt8].init(repeating: 0, count: 4)
|
||||
private let bufferSize = 4
|
||||
private var bufferOffset = 0
|
||||
private var buffer = [UInt8](repeating: 0, count: 4)
|
||||
|
||||
private var tempBuffer = [UInt8](repeating: 0, count: 4)
|
||||
private let tempBufferSize = 4
|
||||
private var tempBufferOffset = 0
|
||||
|
||||
internal init(stream: InputStream, endian: Endian = .unknown, closeOnDeinit: Bool = true) {
|
||||
internal init(stream: InputStream, endian: Endian = .unknown, closeOnDeinit: Bool = true) throws {
|
||||
var endian = endian
|
||||
|
||||
if stream.streamStatus == .notOpen {
|
||||
stream.open()
|
||||
}
|
||||
if stream.streamStatus != .open {
|
||||
throw CSVError.cannotOpenFile
|
||||
}
|
||||
|
||||
let readCount = stream.read(&buffer, maxLength: bufferSize)
|
||||
if let (e, l) = readBOM(buffer: &buffer, length: readCount) {
|
||||
let readCount = stream.read(&tempBuffer, maxLength: tempBufferSize)
|
||||
if let (e, l) = readBOM(buffer: &tempBuffer, length: readCount) {
|
||||
if endian != .unknown && endian != e {
|
||||
throw CSVError.stringEndianMismatch
|
||||
}
|
||||
endian = e
|
||||
bufferOffset = l
|
||||
tempBufferOffset = l
|
||||
}
|
||||
|
||||
self.stream = stream
|
||||
|
@ -73,12 +81,20 @@ internal class BinaryReader {
|
|||
}
|
||||
}
|
||||
|
||||
private func readStream(_ buffer: UnsafeMutablePointer<UInt8>, maxLength: Int) -> Int {
|
||||
internal var hasBytesAvailable: Bool {
|
||||
return stream.hasBytesAvailable
|
||||
}
|
||||
|
||||
private func readStream(_ buffer: UnsafeMutablePointer<UInt8>, maxLength: Int) throws -> Int {
|
||||
if stream.streamStatus != .open {
|
||||
throw CSVError.cannotReadFile
|
||||
}
|
||||
|
||||
var i = 0
|
||||
while bufferOffset < bufferSize {
|
||||
buffer[i] = self.buffer[bufferOffset]
|
||||
while tempBufferOffset < tempBufferSize {
|
||||
buffer[i] = tempBuffer[tempBufferOffset]
|
||||
i += 1
|
||||
bufferOffset += 1
|
||||
tempBufferOffset += 1
|
||||
if i >= maxLength {
|
||||
return i
|
||||
}
|
||||
|
@ -87,39 +103,25 @@ internal class BinaryReader {
|
|||
}
|
||||
|
||||
internal func readUInt8() throws -> UInt8 {
|
||||
// if stream.streamStatus == .Closed {
|
||||
// // ObjectDisposedException
|
||||
// throw NSError(domain: "", code: 0, userInfo: nil)
|
||||
// }
|
||||
// if stream.streamStatus == .AtEnd {
|
||||
// // EndOfStreamException
|
||||
// throw NSError(domain: "", code: 0, userInfo: nil)
|
||||
// }
|
||||
let bufferSize = 1
|
||||
var buffer = [UInt8](repeating: 0, count: bufferSize)
|
||||
let length = readStream(&buffer, maxLength: bufferSize)
|
||||
let length = try readStream(&buffer, maxLength: bufferSize)
|
||||
if length < 0 {
|
||||
// IOException
|
||||
throw NSError(domain: "", code: 0, userInfo: nil)
|
||||
throw CSVError.streamErrorHasOccurred(error: stream.streamError!)
|
||||
}
|
||||
if length != bufferSize {
|
||||
// EndOfStreamException
|
||||
throw NSError(domain: "", code: 0, userInfo: nil)
|
||||
throw CSVError.cannotReadFile
|
||||
}
|
||||
return buffer[0]
|
||||
}
|
||||
|
||||
internal func readUInt16() throws -> UInt16 {
|
||||
let bufferSize = 2
|
||||
var buffer = [UInt8](repeating: 0, count: bufferSize)
|
||||
let length = readStream(&buffer, maxLength: bufferSize)
|
||||
let length = try readStream(&buffer, maxLength: bufferSize)
|
||||
if length < 0 {
|
||||
// IOException
|
||||
throw NSError(domain: "", code: 0, userInfo: nil)
|
||||
throw CSVError.streamErrorHasOccurred(error: stream.streamError!)
|
||||
}
|
||||
if length != bufferSize {
|
||||
// EndOfStreamException
|
||||
throw NSError(domain: "", code: 0, userInfo: nil)
|
||||
throw CSVError.stringEncodingMismatch
|
||||
}
|
||||
let tmp = UnsafeMutablePointer<UInt16>(buffer)
|
||||
switch endian {
|
||||
|
@ -128,21 +130,18 @@ internal class BinaryReader {
|
|||
case .little:
|
||||
return CFSwapInt16LittleToHost(tmp[0])
|
||||
default:
|
||||
throw NSError(domain: "", code: 0, userInfo: nil)
|
||||
throw CSVError.stringEndianMismatch
|
||||
}
|
||||
}
|
||||
|
||||
internal func readUInt32() throws -> UInt32 {
|
||||
let bufferSize = 4
|
||||
var buffer = [UInt8](repeating: 0, count: bufferSize)
|
||||
let length = readStream(&buffer, maxLength: bufferSize)
|
||||
let length = try readStream(&buffer, maxLength: bufferSize)
|
||||
if length < 0 {
|
||||
// IOException
|
||||
throw NSError(domain: "", code: 0, userInfo: nil)
|
||||
throw CSVError.streamErrorHasOccurred(error: stream.streamError!)
|
||||
}
|
||||
if length != 4 {
|
||||
// EndOfStreamException
|
||||
throw NSError(domain: "", code: 0, userInfo: nil)
|
||||
throw CSVError.stringEncodingMismatch
|
||||
}
|
||||
let tmp = UnsafeMutablePointer<UInt32>(buffer)
|
||||
switch endian {
|
||||
|
@ -151,7 +150,7 @@ internal class BinaryReader {
|
|||
case .little:
|
||||
return CFSwapInt32LittleToHost(tmp[0])
|
||||
default:
|
||||
throw NSError(domain: "", code: 0, userInfo: nil)
|
||||
throw CSVError.stringEndianMismatch
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,7 +167,15 @@ extension BinaryReader {
|
|||
}
|
||||
|
||||
internal mutating func next() -> UInt8? {
|
||||
return try? reader.readUInt8()
|
||||
if !reader.hasBytesAvailable {
|
||||
return nil
|
||||
}
|
||||
do {
|
||||
return try reader.readUInt8()
|
||||
}
|
||||
catch /*let error*/ {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -190,9 +197,17 @@ extension BinaryReader {
|
|||
}
|
||||
|
||||
internal mutating func next() -> UInt16? {
|
||||
return try? reader.readUInt16()
|
||||
if !reader.hasBytesAvailable {
|
||||
return nil
|
||||
}
|
||||
do {
|
||||
return try reader.readUInt16()
|
||||
}
|
||||
catch /*let error*/ {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
internal func makeUInt16Iterator() -> UInt16Iterator {
|
||||
|
@ -212,7 +227,15 @@ extension BinaryReader {
|
|||
}
|
||||
|
||||
internal mutating func next() -> UInt32? {
|
||||
return try? reader.readUInt32()
|
||||
if !reader.hasBytesAvailable {
|
||||
return nil
|
||||
}
|
||||
do {
|
||||
return try reader.readUInt32()
|
||||
}
|
||||
catch /*let error*/ {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,26 +18,16 @@ internal let defaultDelimiter = ",".unicodeScalars.first!
|
|||
public struct CSV: IteratorProtocol, Sequence {
|
||||
|
||||
private var iterator: AnyIterator<UnicodeScalar>
|
||||
private var back: UnicodeScalar? = nil
|
||||
|
||||
private let delimiter: UnicodeScalar
|
||||
|
||||
private var back: UnicodeScalar? = nil
|
||||
|
||||
internal var currentRow: [String]? = nil
|
||||
|
||||
/**
|
||||
CSV header row. To set a value for this property, you set `true` to `hasHeaerRow` in initializer.
|
||||
*/
|
||||
|
||||
/// CSV header row. To set a value for this property, you set `true` to `hasHeaerRow` in initializer.
|
||||
public var headerRow: [String]? { return _headerRow }
|
||||
private var _headerRow: [String]? = nil
|
||||
|
||||
/**
|
||||
Create CSV instance with `NSInputStream`.
|
||||
|
||||
- parameter stream: An `NSInputStream` object. If the stream is not open, initializer opens automatically.
|
||||
- parameter encoding: The character encoding for `stream`. Default: `NSUTF8StringEncoding`.
|
||||
- parameter hasHeaderRow: `true` if the CSV has a header row, otherwise `false`. Default: `false`.
|
||||
- parameter delimiter: Default: `","`.
|
||||
*/
|
||||
|
||||
internal init<T: IteratorProtocol where T.Element == UnicodeScalar>(
|
||||
iterator: T,
|
||||
hasHeaderRow: Bool,
|
||||
|
@ -48,13 +38,19 @@ public struct CSV: IteratorProtocol, Sequence {
|
|||
self.delimiter = delimiter
|
||||
|
||||
if hasHeaderRow {
|
||||
guard let headerRow = next() else {
|
||||
throw CSVError.headerReadError
|
||||
guard let headerRow = next() else {
|
||||
throw CSVError.cannotReadHeaderRow
|
||||
}
|
||||
_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: `","`.
|
||||
public init<T: UnicodeCodec where T.CodeUnit == UInt8>(
|
||||
stream: InputStream,
|
||||
codecType: T.Type,
|
||||
|
@ -62,11 +58,18 @@ public struct CSV: IteratorProtocol, Sequence {
|
|||
delimiter: UnicodeScalar = defaultDelimiter)
|
||||
throws
|
||||
{
|
||||
let reader = BinaryReader(stream: stream, endian: .unknown, closeOnDeinit: true)
|
||||
let iterator = UnicodeIterator(input: reader.makeUInt8Iterator(), inputEncoding: codecType)
|
||||
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, delimiter: delimiter)
|
||||
}
|
||||
|
||||
|
||||
/// 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: `","`.
|
||||
public init<T: UnicodeCodec where T.CodeUnit == UInt16>(
|
||||
stream: InputStream,
|
||||
codecType: T.Type,
|
||||
|
@ -75,11 +78,18 @@ public struct CSV: IteratorProtocol, Sequence {
|
|||
delimiter: UnicodeScalar = defaultDelimiter)
|
||||
throws
|
||||
{
|
||||
let reader = BinaryReader(stream: stream, endian: endian, closeOnDeinit: true)
|
||||
let iterator = UnicodeIterator(input: reader.makeUInt16Iterator(), inputEncoding: codecType)
|
||||
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, delimiter: delimiter)
|
||||
}
|
||||
|
||||
|
||||
/// 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: `","`.
|
||||
public init<T: UnicodeCodec where T.CodeUnit == UInt32>(
|
||||
stream: InputStream,
|
||||
codecType: T.Type,
|
||||
|
@ -88,8 +98,8 @@ public struct CSV: IteratorProtocol, Sequence {
|
|||
delimiter: UnicodeScalar = defaultDelimiter)
|
||||
throws
|
||||
{
|
||||
let reader = BinaryReader(stream: stream, endian: endian, closeOnDeinit: true)
|
||||
let iterator = UnicodeIterator(input: reader.makeUInt32Iterator(), inputEncoding: codecType)
|
||||
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, delimiter: delimiter)
|
||||
}
|
||||
|
||||
|
|
|
@ -9,9 +9,10 @@
|
|||
import Foundation
|
||||
|
||||
public enum CSVError: ErrorProtocol {
|
||||
// case parameterError
|
||||
// case streamError
|
||||
case headerReadError
|
||||
// case memoryAllocationFailed
|
||||
// case stringEncodingMismatch
|
||||
case cannotOpenFile
|
||||
case cannotReadFile
|
||||
case streamErrorHasOccurred(error: NSError)
|
||||
case cannotReadHeaderRow
|
||||
case stringEncodingMismatch
|
||||
case stringEndianMismatch
|
||||
}
|
||||
|
|
|
@ -173,78 +173,78 @@ class CSVReaderTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testUTF16WithNativeEndianBOM() {
|
||||
let csvString = "abab,,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\","
|
||||
let csvString = "abab,,cdcd,efef\r\nzxcv,😆asdf,\"qw\"\"er\","
|
||||
let encoding = String.Encoding.utf16
|
||||
var mutableData = Data()
|
||||
mutableData.append(csvString.data(using: encoding)!)
|
||||
let stream = InputStream(data: mutableData)
|
||||
let csv = try! CSV(stream: stream, codecType: UTF16.self)
|
||||
let csv = try! CSV(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", ""])
|
||||
XCTAssertEqual(records[1], ["zxcv", "😆asdf", "qw\"er", ""])
|
||||
}
|
||||
|
||||
func testUTF16WithBigEndianBOM() {
|
||||
let csvString = "abab,,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\","
|
||||
let csvString = "abab,,cdcd,efef\r\n😆zxcv,asdf,\"qw\"\"er\","
|
||||
let encoding = String.Encoding.utf16BigEndian
|
||||
var mutableData = Data()
|
||||
mutableData.append(utf16BigEndianBOM, count: utf16BigEndianBOM.count)
|
||||
mutableData.append(csvString.data(using: encoding)!)
|
||||
let stream = InputStream(data: mutableData)
|
||||
let csv = try! CSV(stream: stream, codecType: UTF16.self, endian: .unknown)
|
||||
let csv = try! CSV(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", ""])
|
||||
XCTAssertEqual(records[1], ["😆zxcv", "asdf", "qw\"er", ""])
|
||||
}
|
||||
|
||||
func testUTF16WithLittleEndianBOM() {
|
||||
let csvString = "abab,,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\","
|
||||
let csvString = "abab,,cdcd,efef\r\nzxcv😆,asdf,\"qw\"\"er\","
|
||||
let encoding = String.Encoding.utf16LittleEndian
|
||||
var mutableData = Data()
|
||||
mutableData.append(utf16LittleEndianBOM, count: utf16LittleEndianBOM.count)
|
||||
mutableData.append(csvString.data(using: encoding)!)
|
||||
let stream = InputStream(data: mutableData)
|
||||
let csv = try! CSV(stream: stream, codecType: UTF16.self, endian: .unknown)
|
||||
let csv = try! CSV(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", ""])
|
||||
XCTAssertEqual(records[1], ["zxcv😆", "asdf", "qw\"er", ""])
|
||||
}
|
||||
|
||||
func testUTF32WithNativeEndianBOM() {
|
||||
let csvString = "abab,,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\","
|
||||
let csvString = "😆abab,,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\","
|
||||
let encoding = String.Encoding.utf32
|
||||
var mutableData = Data()
|
||||
mutableData.append(csvString.data(using: encoding)!)
|
||||
let stream = InputStream(data: mutableData)
|
||||
let csv = try! CSV(stream: stream, codecType: UTF32.self)
|
||||
let csv = try! CSV(stream: stream, codecType: UTF32.self, endian: .unknown)
|
||||
let records = getRecords(csv: csv)
|
||||
XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"])
|
||||
XCTAssertEqual(records[0], ["😆abab", "", "cdcd", "efef"])
|
||||
XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""])
|
||||
}
|
||||
|
||||
func testUTF32WithBigEndianBOM() {
|
||||
let csvString = "abab,,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\","
|
||||
let csvString = "abab,,cd😆cd,efef\r\nzxcv,asdf,\"qw\"\"er\","
|
||||
let encoding = String.Encoding.utf32BigEndian
|
||||
var mutableData = Data()
|
||||
mutableData.append(utf32BigEndianBOM, count: utf32BigEndianBOM.count)
|
||||
mutableData.append(csvString.data(using: encoding)!)
|
||||
let stream = InputStream(data: mutableData)
|
||||
let csv = try! CSV(stream: stream, codecType: UTF32.self, endian: .unknown)
|
||||
let csv = try! CSV(stream: stream, codecType: UTF32.self, endian: .big)
|
||||
let records = getRecords(csv: csv)
|
||||
XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"])
|
||||
XCTAssertEqual(records[0], ["abab", "", "cd😆cd", "efef"])
|
||||
XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""])
|
||||
}
|
||||
|
||||
func testUTF32WithLittleEndianBOM() {
|
||||
let csvString = "abab,,cdcd,efef\r\nzxcv,asdf,\"qw\"\"er\","
|
||||
let csvString = "abab,,cdcd,ef😆ef\r\nzxcv,asdf,\"qw\"\"er\","
|
||||
let encoding = String.Encoding.utf32LittleEndian
|
||||
var mutableData = Data()
|
||||
mutableData.append(utf32LittleEndianBOM, count: utf32LittleEndianBOM.count)
|
||||
mutableData.append(csvString.data(using: encoding)!)
|
||||
let stream = InputStream(data: mutableData)
|
||||
let csv = try! CSV(stream: stream, codecType: UTF32.self, endian: .unknown)
|
||||
let csv = try! CSV(stream: stream, codecType: UTF32.self, endian: .little)
|
||||
let records = getRecords(csv: csv)
|
||||
XCTAssertEqual(records[0], ["abab", "", "cdcd", "efef"])
|
||||
XCTAssertEqual(records[0], ["abab", "", "cdcd", "ef😆ef"])
|
||||
XCTAssertEqual(records[1], ["zxcv", "asdf", "qw\"er", ""])
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue