Implement error handling

This commit is contained in:
Yasuhiro Hatta 2016-07-20 01:20:09 +09:00
parent 0dfc025b79
commit 85e1ece74c
4 changed files with 125 additions and 91 deletions

View File

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

View File

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

View File

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

View File

@ -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", ""])
}